C#动态事件订阅
我们将如何动态订阅Cevent,以便在给定一个Object实例和一个包含事件名称的字符串名称的情况下,我们订阅该事件并在该事件被触发后做一些事情(例如,写入控制台)?
似乎无法使用Reflection,并且我想避免不得不使用Reflection.Emit,因为目前(对我而言)这似乎是唯一的方法。
/ EDIT:我不知道事件所需的委托人的签名,这是问题的核心
/ EDIT 2:尽管委派矛盾似乎是一个不错的计划,但我无法做出使用此解决方案所必需的假设
解决方案
回答
我们可以使用依赖项注入来实现所需的功能。例如,Microsoft Composite UI应用程序块完全符合描述
回答
可以使用反射订阅事件
var o = new SomeObjectWithEvent; o.GetType().GetEvent("SomeEvent").AddEventHandler(...);
http://msdn.microsoft.com/zh-CN/library/system.reflection.eventinfo.addeventhandler.aspx
现在这将是我们必须解决的问题。每个事件处理程序所需的委托将具有不同的签名。我们将不得不寻找动态创建这些方法的方法,这可能意味着Reflection.Emit,或者我们必须将自身限制为某个委托,以便可以使用已编译的代码来处理它。
希望这可以帮助。
回答
你的意思是这样的吗:
//reflect out the method to fire as a delegate EventHandler eventDelegate = ( EventHandler ) Delegate.CreateDelegate( typeof( EventHandler ), //type of event delegate objectWithEventSubscriber, //instance of the object with the matching method eventSubscriberMethodName, //the name of the method true );
这不会进行订阅,但会提供调用方法。
编辑:
此答案后澄清了帖子,如果我们不知道类型,我的示例将无济于事。
但是,.Net中的所有事件都应遵循默认的事件模式,因此只要我们遵循它,它将与基本的EventHandler一起使用。
回答
public TestForm() { Button b = new Button(); this.Controls.Add(b); MethodInfo method = typeof(TestForm).GetMethod("Clickbutton", BindingFlags.NonPublic | BindingFlags.Instance); Type type = typeof(EventHandler); Delegate handler = Delegate.CreateDelegate(type, this, method); EventInfo eventInfo = cbo.GetType().GetEvent("Click"); eventInfo.AddEventHandler(b, handler); } void Clickbutton(object sender, System.EventArgs e) { // Code here }
回答
我们可以编译表达式树以使用不带任何参数的void方法作为任何类型事件的事件处理程序。为了适应其他事件处理程序类型,我们必须以某种方式将事件处理程序的参数映射到事件。
using System; using System.Linq; using System.Linq.Expressions; using System.Reflection; class ExampleEventArgs : EventArgs { public int IntArg {get; set;} } class EventRaiser { public event EventHandler SomethingHappened; public event EventHandler<ExampleEventArgs> SomethingHappenedWithArg; public void RaiseEvents() { if (SomethingHappened!=null) SomethingHappened(this, EventArgs.Empty); if (SomethingHappenedWithArg!=null) { SomethingHappenedWithArg(this, new ExampleEventArgs{IntArg = 5}); } } } class Handler { public void HandleEvent() { Console.WriteLine("Handler.HandleEvent() called.");} public void HandleEventWithArg(int arg) { Console.WriteLine("Arg: {0}",arg); } } static class EventProxy { //void delegates with no parameters static public Delegate Create(EventInfo evt, Action d) { var handlerType = evt.EventHandlerType; var eventParams = handlerType.GetMethod("Invoke").GetParameters(); //lambda: (object x0, EventArgs x1) => d() var parameters = eventParams.Select(p=>Expression.Parameter(p.ParameterType,"x")); var body = Expression.Call(Expression.Constant(d),d.GetType().GetMethod("Invoke")); var lambda = Expression.Lambda(body,parameters.ToArray()); return Delegate.CreateDelegate(handlerType, lambda.Compile(), "Invoke", false); } //void delegate with one parameter static public Delegate Create<T>(EventInfo evt, Action<T> d) { var handlerType = evt.EventHandlerType; var eventParams = handlerType.GetMethod("Invoke").GetParameters(); //lambda: (object x0, ExampleEventArgs x1) => d(x1.IntArg) var parameters = eventParams.Select(p=>Expression.Parameter(p.ParameterType,"x")).ToArray(); var arg = getArgExpression(parameters[1], typeof(T)); var body = Expression.Call(Expression.Constant(d),d.GetType().GetMethod("Invoke"), arg); var lambda = Expression.Lambda(body,parameters); return Delegate.CreateDelegate(handlerType, lambda.Compile(), "Invoke", false); } //returns an expression that represents an argument to be passed to the delegate static Expression getArgExpression(ParameterExpression eventArgs, Type handlerArgType) { if (eventArgs.Type==typeof(ExampleEventArgs) && handlerArgType==typeof(int)) { //"x1.IntArg" var memberInfo = eventArgs.Type.GetMember("IntArg")[0]; return Expression.MakeMemberAccess(eventArgs,memberInfo); } throw new NotSupportedException(eventArgs+"->"+handlerArgType); } } static class Test { public static void Main() { var raiser = new EventRaiser(); var handler = new Handler(); //void delegate with no parameters string eventName = "SomethingHappened"; var eventinfo = raiser.GetType().GetEvent(eventName); eventinfo.AddEventHandler(raiser,EventProxy.Create(eventinfo,handler.HandleEvent)); //void delegate with one parameter string eventName2 = "SomethingHappenedWithArg"; var eventInfo2 = raiser.GetType().GetEvent(eventName2); eventInfo2.AddEventHandler(raiser,EventProxy.Create<int>(eventInfo2,handler.HandleEventWithArg)); //or even just: eventinfo.AddEventHandler(raiser,EventProxy.Create(eventinfo,()=>Console.WriteLine("!"))); eventInfo2.AddEventHandler(raiser,EventProxy.Create<int>(eventInfo2,i=>Console.WriteLine(i+"!"))); raiser.RaiseEvents(); } }
回答
这不是一个完全通用的解决方案,但是如果我们所有的事件都是以下形式
void Foo(object o,T args),其中T是从EventArgs派生的,则可以使用委托自变量来摆脱它。像这样(其中KeyDown的签名与Click的签名不同):
public Form1() { Button b = new Button(); TextBox tb = new TextBox(); this.Controls.Add(b); this.Controls.Add(tb); WireUp(b, "Click", "Clickbutton"); WireUp(tb, "KeyDown", "Clickbutton"); } void WireUp(object o, string eventname, string methodname) { EventInfo ei = o.GetType().GetEvent(eventname); MethodInfo mi = this.GetType().GetMethod(methodname, BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic); Delegate del = Delegate.CreateDelegate(ei.EventHandlerType, this, mi); ei.AddEventHandler(o, del); } void Clickbutton(object sender, System.EventArgs e) { MessageBox.Show("hello!"); }
回答
尝试使用LinFu-它具有通用事件处理程序,可让我们在运行时绑定到任何事件。例如,在这里我们可以将处理程序绑定到动态按钮的Click事件:
// Note: The CustomDelegate signature is defined as: // public delegate object CustomDelegate(params object[] args); CustomDelegate handler = delegate { Console.WriteLine("Button Clicked!"); return null; }; Button myButton = new Button(); // Connect the handler to the event EventBinder.BindToEvent("Click", myButton, handler);
LinFu允许我们将处理程序绑定到任何事件,无论委托签名如何。享受!
你可以在这里找到它:
http://www.codeproject.com/KB/cs/LinFuPart3.aspx
回答
我最近写了一系列描述单元测试事件的博客文章,而我讨论的一种技术描述了动态事件订阅。我在动态方面使用了反射和MSIL(代码发出),但是这些都很好地包装了起来。使用DynamicEvent类,可以像下面这样动态地订阅事件:
EventPublisher publisher = new EventPublisher(); foreach (EventInfo eventInfo in publisher.GetType().GetEvents()) { DynamicEvent.Subscribe(eventInfo, publisher, (sender, e, eventName) => { Console.WriteLine("Event raised: " + eventName); }); }
我实现的模式的功能之一是将事件名称注入事件处理程序的调用中,以便我们知道引发了哪个事件。对于单元测试非常有用。
该博客文章相当长,因为它描述了事件单元测试技术,但是提供了完整的源代码和测试,并且在上一篇文章中详细介绍了如何实现动态事件订阅。
http://gojisoft.com/blog/2010/04/22/event-sequence-unit-testing-part-1/