Welcome toVigges Developer Community-Open, Learning,Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
377 views
in Technique[技术] by (71.8m points)

generics - C# reflection: How to subscribe to a event without knowing delegate signature?

I'd like to construct a little adapter class with which a run-time interpreter may intercept the occurance of events regardless of delegate type.

I've tried something like this (which fails with a runtime-exception in the constructor):

public class ModelEvent
{
    private object source;
    private EventInfo sourceEvent;
    private Delegate eventHandler;
    public event EventHandler OnEvent;


    public ModelEvent(object source, EventInfo sourceEvent)
    {
        this.source = source;
        this.sourceEvent = sourceEvent;

        // this line is responsible for the error,
        // the signatures of the eventHandler and delegate method don't match
        this.eventHandler = Delegate.CreateDelegate(
            this.sourceEvent.EventHandlerType,
            this,
            typeof(ModelEvent).GetMethod("OnSourceEvent"));
        // Alternative failed attempts
        // this.eventHandler = new Action<object, object> (this.OnSourceEvent);
        // this.eventHandler = Delegate.CreateDelegate(typeof(EventHandler), this, typeof(ModelEvent).GetMethod("OnSourceEvent"));

        this.sourceEvent.AddEventHandler(this.source, this.eventHandler);
    }

    private void OnSourceEvent(object sender, object eventArgs)
    {
        this.OnEvent?.Invoke(this, EventArgs.Empty);
    }

    // ... truncated for clarity
}

This crashes at runtime, since the (object, object) signature of my OnSourceEvent method does not match any (object, T) signature of the generic EventHandler<T> delegate of the given event sourceEvent.

So I guess I need to construct some sort of wrapper with the correct signature at runtime?

I'm quite new at generics in C# and would appreciate any help in finding a way to construct a viable Delegate, if my interpretation is correct. Thanks a lot!


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

Thanks for the reading material. However, I've found the solution to my problem in this SO-question.

The trick is to dynamically create a lambda-expressions at runtime to wrap my "parameterless" events and use that as the delegate.

The correct code looks like this. Using it, you can subscribe to any event and get notified when it is raised (although you won't get any EventArgs):

public class ModelEvent
{
    private object source;
    private EventInfo sourceEvent;
    private Delegate eventHandler;
    public event EventHandler OnEvent;

    public ModelEvent(object source, EventInfo sourceEvent)
    {
        this.source = source;
        this.sourceEvent = sourceEvent;
        ParameterExpression[] sourceEventHandlerParameters =
            sourceEvent
            .EventHandlerType
            .GetMethod("Invoke")
            .GetParameters()
            .Select(parameter => Expression.Parameter(parameter.ParameterType))
            .ToArray();
        MethodCallExpression fireOnEvent = Expression.Call(
            Expression.Constant(new Action(FireOnEvent)),
            "Invoke",
            Type.EmptyTypes);
        this.eventHandler = Expression.Lambda(
            sourceEvent.EventHandlerType,
            fireOnEvent,
            sourceEventHandlerParameters)
            .Compile();
        this.sourceEvent.AddEventHandler(this.source, this.eventHandler);
    }

    private void FireOnEvent()
    {
        this.OnEvent?.Invoke(this, EventArgs.Empty);
    }
}

If you know your way around the Expression class I guess you can eliminate the FireOnEvent method and somehow directly call this.OnEvent?.Invoke(this, EventArgs.Empty), but I was too lazy to figure out how to construct that call dynamically.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to Vigges Developer Community for programmer and developer-Open, Learning and Share
...