运行时生成未知类型的事件处理程序
本文关键字:事件处理 程序 类型 未知 运行时 | 更新日期: 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表达式的方法(这里是命名空间帮助)。
这个想法很简单:
-
使用命名空间中可以看到的各种表达式派生类来定义回调的作用。在这种情况下,您希望在
ei
实例上生成调用.RemoveEventHandler
的东西(我猜是这样)(具体地说,您将使用ConstantExpression创建对ei
变量和回调参数的引用,并使用MethodCallExpression创建对RemoveDataHandler
方法的调用)。 -
一旦你创建了满足你需要的表达式,你就需要从中创建一个委托(Lambda)(见这里)
-
差不多完成了。您仍然需要编译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 name
的Event
中,并在调用后将其移除(以及移除回调本身的Eventhandler
)。