C# 动态事件订阅
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/45779/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
C# Dynamic Event Subscription
提问by DAC
How would you dynamically subscribe to a C# event so that given a Object instance and a String name containing the name of the event, you subscribe to that event and do something (write to the console for example) when that event has been fired?
您将如何动态订阅 C# 事件,以便给定一个对象实例和一个包含事件名称的字符串名称,您订阅该事件并在该事件被触发时执行某些操作(例如写入控制台)?
It would seem using Reflection this isn't possible and I would like to avoid having to use Reflection.Emit if possible, as this currently (to me) seems like the only way of doing it.
使用 Reflection 这似乎是不可能的,如果可能的话,我想避免使用 Reflection.Emit,因为这目前(对我来说)似乎是唯一的方法。
/EDIT:I do not know the signature of the delegate needed for the event, this is the core of the problem
/编辑:我不知道事件所需的委托签名,这是问题的核心
/EDIT 2:Although delegate contravariance seems like a good plan, I can not make the assumption necessary to use this solution
/ 编辑 2:虽然委托逆变似乎是一个不错的计划,但我无法做出使用此解决方案所必需的假设
采纳答案by Mark Cidade
You can compile expression trees to use void methods without any arguments as event handlers for events of any type. To accommodate other event handler types, you have to map the event handler's parameters to the events somehow.
您可以编译表达式树以使用不带任何参数的 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();
}
}
回答by aku
What you want can be achieved using dependency injection. For example Microsoft Composite UI app blockdoes exactly what you described
使用依赖注入可以实现您想要的。例如,Microsoft Composite UI 应用程序块完全符合您的描述
回答by Nick Berardi
It is possible to subscribe to an event using Reflection
可以使用反射订阅事件
var o = new SomeObjectWithEvent;
o.GetType().GetEvent("SomeEvent").AddEventHandler(...);
http://msdn.microsoft.com/en-us/library/system.reflection.eventinfo.addeventhandler.aspx
http://msdn.microsoft.com/en-us/library/system.reflection.eventinfo.addeventhandler.aspx
Now here is going to be the problem that you are going to have to solve. The delegates required for each event handler will have different signatures. You are going to have to find away to create these methods dynamically, which probably means Reflection.Emit, or you are going to have to limit your self to a certain delegate so that you can handle it with compiled code.
现在这将是您必须解决的问题。每个事件处理程序所需的委托将具有不同的签名。您将不得不设法动态创建这些方法,这可能意味着 Reflection.Emit,或者您将不得不将自己限制为某个委托,以便您可以使用已编译的代码处理它。
Hope this helps.
希望这可以帮助。
回答by Keith
Do you mean something like:
你的意思是这样的:
//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 );
This doesn't do the subscription, but will give to the method to call.
这不会进行订阅,但会提供给调用的方法。
Edit:
编辑:
Post was clarified after this answer, my example won't help if you don't know the type.
在这个答案之后,帖子得到了澄清,如果你不知道类型,我的例子将无济于事。
However all events in .Net should follow the default event pattern, so as long as you've followed it this will work with the basic EventHandler.
然而,.Net 中的所有事件都应该遵循默认的事件模式,所以只要你遵循了它,这将适用于基本的 EventHandler。
回答by Erick Sgarbi
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
}
回答by Matt Bishop
It's not a completely general solution, but if all your events are of the form void Foo(object o, T args) , where T derives from EventArgs, then you can use delegate contravariance to get away with it. Like this (where the signature of KeyDown is not the same as that of Click) :
这不是一个完全通用的解决方案,但如果您的所有事件都是 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!");
}
回答by plaureano
Try LinFu--it has a universal event handler that lets you bind to any event at runtime. For example, here's you you can bind a handler to the Click event of a dynamic button:
试试 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 lets you bind your handlers to any event, regardless of the delegate signature. Enjoy!
LinFu 允许您将处理程序绑定到任何事件,而不管委托签名如何。享受!
You can find it here: http://www.codeproject.com/KB/cs/LinFuPart3.aspx
你可以在这里找到它:http: //www.codeproject.com/KB/cs/LinFuPart3.aspx
回答by Tim Lloyd
I recently wrote a series of blog posts describing unit testing events, and one of the techniques I discuss describes dynamic event subscription. I used reflection and MSIL (code emitting) for the dynamic aspects, but this is all wrapped up nicely. Using the DynamicEvent class, events can be subscribed to dynamically like so:
我最近写了一系列描述单元测试事件的博客文章,我讨论的其中一项技术描述了动态事件订阅。我在动态方面使用了反射和 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);
});
}
One of the features of the pattern I implemented was that it injects the event name into the call to the event handler so you know which event has been raised. Very useful for unit testing.
我实现的模式的特性之一是它将事件名称注入到对事件处理程序的调用中,以便您知道引发了哪个事件。对于单元测试非常有用。
The blog article is quite lengthy as it is describing an event unit testing technique, but full source code and tests are provided, and a detailed description of how dynamic event subscription was implemented is detailed in the last post.
博客文章很长,因为它描述了一种事件单元测试技术,但提供了完整的源代码和测试,并且在上一篇文章中详细描述了如何实现动态事件订阅。
http://gojisoft.com/blog/2010/04/22/event-sequence-unit-testing-part-1/
http://gojisoft.com/blog/2010/04/22/event-sequence-unit-testing-part-1/
回答by Edward Brey
This method adds to an event, a dynamic handler that calls a method OnRaised
, passing the event parameters as an object array:
此方法添加到一个事件,一个调用方法的动态处理程序OnRaised
,将事件参数作为对象数组传递:
void Subscribe(object source, EventInfo ev)
{
var eventParams = ev.EventHandlerType.GetMethod("Invoke").GetParameters().Select(p => Expression.Parameter(p.ParameterType)).ToArray();
var eventHandler = Expression.Lambda(ev.EventHandlerType,
Expression.Call(
instance: Expression.Constant(this),
method: typeof(EventSubscriber).GetMethod(nameof(OnRaised), BindingFlags.NonPublic | BindingFlags.Instance),
arg0: Expression.Constant(ev.Name),
arg1: Expression.NewArrayInit(typeof(object), eventParams.Select(p => Expression.Convert(p, typeof(object))))),
eventParams);
ev.AddEventHandler(source, eventHandler.Compile());
}
OnRaised
has this signature:
OnRaised
有这个签名:
void OnRaised(string name, object[] parameters);