运行时生成未知类型的事件处理程序

本文关键字:事件处理 程序 类型 未知 运行时 | 更新日期: 2023-09-27 18:29:07

运行时是否可以生成事件处理程序?我想做这样的事情:

public bool addCallback(string name, Delegate Callback)
{
   EventInfo ei = DataProxy.GetType().GetEvent(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
   if (ei == null)
      return false;
   ei.AddEventHandler(DataProxy, Callback);
   //now I want to add an Eventhandler, which removes the Callback and this new Eventhandler itsself
   return true;
}

运行时生成未知类型的事件处理程序

取决于您的平台和信任级别。最灵活的方法是使用Emit生成方法(请参见此处)。

然而,我发现了一个相对容易使用且很好的替代生成Linq表达式的方法(这里是命名空间帮助)。

这个想法很简单:

  1. 使用命名空间中可以看到的各种表达式派生类来定义回调的作用。在这种情况下,您希望在ei实例上生成调用.RemoveEventHandler的东西(我猜是这样)(具体地说,您将使用ConstantExpression创建对ei变量和回调参数的引用,并使用MethodCallExpression创建对RemoveDataHandler方法的调用)。

  2. 一旦你创建了满足你需要的表达式,你就需要从中创建一个委托(Lambda)(见这里)

  3. 差不多完成了。您仍然需要编译lambda,这是通过对从上一步得到的对象调用.Compile来完成的(请参阅此处)

编辑:这是一个动态生成的委托删除自身的Windows控制台示例。请注意,WP7Linq表达式支持比.NET4.0更受限制,因此您需要对其进行调整(制作将完成部分工作的辅助方法,并从表达式中调用它们,而不是我所做的)。

Edit2:BTW:lambda可以移除自身的机制是创建另一个lambda,该lambda返回该类型的局部变量。创建lambda后,将其保存到本地变量并运行代码(我不确定如果没有辅助lambda,这是否可行)

第三版:不,你必须使用委托技巧,否则,常量会被"冻结",不会按照你想要的方式更新。所以代码可以正常工作。

public class MyEventArgs : EventArgs
{
}
public class EventContainer
{
    public event EventHandler<MyEventArgs> MyEvent;
    public void Fire()
    {
        Console.WriteLine("Firing");
        if (MyEvent != null)
        {
            MyEvent(this, new MyEventArgs());
        }
        Console.WriteLine("Fired");
    }
}
class Program
{
    static void Main(string[] args)
    {
        EventContainer container = new EventContainer();
        var adder = container.GetType().GetMethod("add_MyEvent");
        var remover = container.GetType().GetMethod("remove_MyEvent");
        object self = null;
        Func<object> getSelf = () => self;
        var block = Expression.Block(
            // Call something to output to console.
            Expression.Call(
                null,
                typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }),
                Expression.Constant("Event called")),
            // Now call the remove_Event method.
            Expression.Call(
                Expression.Constant(container), // passing the container as "this"
                remover, // And the remover as the method info
                Expression.Convert( // we need to cast the result of getSelf to the correct type to pass as an argument
                    Expression.Invoke( // For the parameter (what to convert), we need to call getSelf
                        Expression.Constant(getSelf)), // So this is a ref to getSelf
                    adder.GetParameters()[0].ParameterType) // finally, say what to convert to.
               ) 
           );
        // Create a lambda of the correct type.
        var lambda = Expression.Lambda(
            adder.GetParameters()[0].ParameterType, 
            block, 
            Expression.Parameter(typeof(object)), 
            Expression.Parameter(typeof(MyEventArgs)));
        var del = lambda.Compile();
        // Make sure "self" knows what the delegate is (so our generated code can remove it)
        self = del;

        // Add the event.
        adder.Invoke(container, new object[] { del });
        // Fire once - see that delegate is being called.
        container.Fire();
        // Fire twice - see that the delegate was removed.
        container.Fire();
    }
}
public static bool addCallback(string name, Delegate Callback)
{
    if (DataProxy == null)
        GetDataProxy();
    EventInfo ei = DataProxy.GetType().GetEvent(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
    if (ei == null)
        return false;
    ei.AddEventHandler(DataProxy, Callback);
    Type handlerType = ei.EventHandlerType;
    MethodInfo invokeMethod = handlerType.GetMethod("Invoke");
    ParameterInfo[] parms = invokeMethod.GetParameters();
    Type[] parmTypes = new Type[parms.Length];
    for (int i = 0; i < parms.Length; i++)
    {
        parmTypes[i] = parms[i].ParameterType;
    }
    List<ParameterExpression> parameters = new List<ParameterExpression>();
    foreach(Type t in parmTypes)
    {
        parameters.Add(System.Linq.Expressions.Expression.Parameter(t));
    }
    ConstantExpression eventInfo = System.Linq.Expressions.Expression.Constant(ei, typeof(EventInfo));
    ConstantExpression eventCallback = System.Linq.Expressions.Expression.Constant(Callback, typeof(Delegate));
    ConstantExpression dataProxy = System.Linq.Expressions.Expression.Constant(DataProxy, typeof(MAServiceClient));
    MethodCallExpression call = System.Linq.Expressions.Expression.Call(eventInfo, ei.GetType().GetMethod("RemoveEventHandler"), dataProxy, eventCallback);
    //add to Expression.Body the call, which removes the new Eventhandler itsself
    ei.AddEventHandler(DataProxy, System.Linq.Expressions.Expression.Lambda(ei.EventHandlerType, call, parameters).Compile());
    return true;
}

这就是我现在的方法。只缺少一个步骤,即新的Eventhandler(由System.Linq.Expressions.Expression.Lambda(ei.EventHandlerType, call, parameters).Compile()创建)会自行删除(请参阅注释)。

多亏了Shahar Prish,我想出了以下代码:

using ex = System.Linq.Expressions;
using System.Linq.Expressions;
    public static bool addCallback(string name, Delegate Callback)
    {
        if (DataProxy == null)
            GetDataProxy();
        EventInfo ei = DataProxy.GetType().GetEvent(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
        if (ei == null)
            return false;
        ei.AddEventHandler(DataProxy, Callback);
        Type handlerType = ei.EventHandlerType;
        MethodInfo removeMethod = ei.GetType().GetMethod("RemoveEventHandler");
        MethodInfo invokeMethod = handlerType.GetMethod("Invoke");
        ParameterInfo[] parms = invokeMethod.GetParameters();
        Type[] parmTypes = new Type[parms.Length];
        for (int i = 0; i < parms.Length; i++)
        {
            parmTypes[i] = parms[i].ParameterType;
        }
        List<ParameterExpression> parameters = new List<ParameterExpression>();
        foreach(Type t in parmTypes)
        {
            parameters.Add(System.Linq.Expressions.Expression.Parameter(t));
        }
        Delegate self = null;
        Func<Delegate> getSelf = () => self;
        ConstantExpression eventInfo = ex.Expression.Constant(ei, typeof(EventInfo));
        ConstantExpression eventCallback = ex.Expression.Constant(Callback, typeof(Delegate));
        ConstantExpression dataProxy = ex.Expression.Constant(DataProxy, typeof(MAServiceClient));
        MethodCallExpression removeCallback = ex.Expression.Call(eventInfo, removeMethod, dataProxy, eventCallback);
        MethodCallExpression removeSelf = ex.Expression.Call(eventInfo, removeMethod, dataProxy, ex.Expression.Invoke(ex.Expression.Constant(getSelf)));
        BlockExpression block = ex.Expression.Block(removeCallback, removeSelf);
        LambdaExpression lambda = ex.Expression.Lambda(ei.EventHandlerType, block, parameters);
        Delegate del = lambda.Compile();
        self = del;
        ei.AddEventHandler(DataProxy, del);
        lambda = ex.Expression.Lambda(ei.EventHandlerType, block, parameters);
        return true;
    }

正如我之前所说,这个方法应该将Delegate Callback传递的Eventhandler添加到static MAServiceClient DataProxy的名为string nameEvent中,并在调用后将其移除(以及移除回调本身的Eventhandler)。