C# 反射:如何在不知道委托签名的情况下订阅事件?

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

我想构建一个小适配器 class,运行 时间解释器可以用它拦截事件的发生,而不管委托类型如何。

我试过这样的事情(失败并在构造函数中出现 运行time-exception):

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
}

这在 运行 时崩溃,因为我的 OnSourceEvent 方法的 (object, object) 签名与通用 EventHandler<T> 委托的任何 (object, T) 签名不匹配给定事件 sourceEvent.

所以我想我需要在 运行 时用正确的签名构造某种包装器?

我对 C# 中的泛型还很陌生,如果我的解释是正确的,我将不胜感激寻找构建可行委托的方法的任何帮助。非常感谢!

感谢阅读material。但是,我找到了解决问题的方法

诀窍是在运行时动态创建一个 lambda 表达式来包装我的“无参数”事件并将其用作委托。

正确的代码如下所示。使用它,您可以订阅任何事件并在事件发生时得到通知(尽管您不会收到任何 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);
    }
}

如果你知道 Expression class 我想你可以去掉 FireOnEvent 方法并以某种方式直接调用 this.OnEvent?.Invoke(this, EventArgs.Empty),但我懒得去找出如何动态构建该调用。