C#动态事件订阅

C#动态事件订阅

C# Dynamic Event Subscription

您将如何动态订阅C#事件,以便在给定对象实例和包含事件名称的字符串名称的情况下,订阅该事件并在触发该事件时执行某些操作(例如,写入控制台)?

似乎无法使用Reflection,并且我想避免不得不使用Reflection.Emit,因为目前(对我而言)这似乎是唯一的方法。

/ EDIT:我不知道事件所需的委托人的签名,这是问题的核心

/ EDIT 2:尽管委派矛盾似乎是一个好计划,但我无法做出使用此解决方案所必需的假设


您可以编译表达式树以使用不带任何参数的void方法作为任何类型事件的事件处理程序。为了适应其他事件处理程序类型,您必须以某种方式将事件处理程序的参数映射到事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
 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的签名不同):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    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!");
    }

可以使用反射订阅事件

1
2
var o = new SomeObjectWithEvent;
o.GetType().GetEvent("SomeEvent").AddEventHandler(...);

http://msdn.microsoft.com/zh-CN/library/system.reflection.eventinfo.addeventhandler.aspx

现在这将是您必须解决的问题。每个事件处理程序所需的委托将具有不同的签名。您将不得不寻找动态创建这些方法的途径,这可能意味着Reflection.Emit,或者您必须将自己限制为某个委托,以便可以使用已编译的代码来处理它。

希望这可以帮助。


尝试使用LinFu-它具有通用事件处理程序,可让您在运行时绑定到任何事件。例如,在这里您可以将处理程序绑定到动态按钮的Click事件:

1
2
3
4
5
6
7
8
9
10
11
// 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


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
}

我最近写了一系列博客文章,描述了单元测试事件,而我讨论的一种技术描述了动态事件订阅。我在动态方面使用了反射和MSIL(代码发出),但是这些都很好地包装了起来。使用DynamicEvent类,可以像下面这样动态地订阅事件:

1
2
3
4
5
6
7
8
9
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/


此方法在事件中添加了动态处理程序,该处理程序调用方法OnRaised,并将事件参数作为对象数组传递:

1
2
3
4
5
6
7
8
9
10
11
12
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具有此签名:

1
void OnRaised(string name, object[] parameters);

您可以使用依赖注入来实现。例如,Microsoft Composite UI应用程序块完全符合您的描述


您的意思是:

1
2
3
4
5
6
7
//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一起使用。


推荐阅读

    linux命令行执行py?

    linux命令行执行py?,系统,环境,官网,一致,文件,程序,脚本,源文件,后台,终端,l

    linux查看执行命令?

    linux查看执行命令?,系统,服务,情况,信息,命令,暂停,标准,概念,实时,第一,lin

    linux命令连续执行?

    linux命令连续执行?,连续,通信,工具,数据,代码,命令,设备,系统,发行,情况,如

    linux执行命令卡住?

    linux执行命令卡住?,系统,环境,密码,数据,信息,分析,软件,异常,服务,命令,Lin

    linux拼接字符串命令?

    linux拼接字符串命令?,系统,工作,代码,工具,名称,信息,地址,时间,数据,命令,l

    linux命令执行不动了?

    linux命令执行不动了?,系统,电脑,数据,管理,信息,密码,命令,环境,地方,分析,l

    linux无效对象的命令?

    linux无效对象的命令?,软件,系统,单位,网络,管理,术语,检测,电脑,环境,风险,l

    linux脚步中执行命令?

    linux脚步中执行命令?,工具,代码,命令,名称,系统,连续,环境,发行,文件,终端,l

    linux后台执行命令?

    linux后台执行命令?,暂停,状态,系统,服务,标准,命令,后台,地方,进程,终端,lin

    linux执行权限命令行?

    linux执行权限命令行?,地址,电脑,系统,数字,工作,权限,目录,文件,新增,信息,L

    linux命令的执行时间?

    linux命令的执行时间?,时间,系统,周期,信息,命令,设备,环境,地址,基础,进程,l

    添加字符串命令linux?

    添加字符串命令linux?,情况,名称,文件,位置,名字,地方,连续,信息,命令,内容,L

    程序执行linux命令?

    程序执行linux命令?,系统,工作,地址,环境,信息,管理,命令,文件,目录,程序,lin

    linux执行2个命令?

    linux执行2个命令?,工作,系统,基础,命令,基础知识,信息,管理,在线,概念,第一

    linux命令批量执行?

    linux命令批量执行?,系统,代码,工作,周期性,数据,定期,环境,命令,文件,脚本,l

    linux二进制执行命令?

    linux二进制执行命令?,系统,工作,情况,代码,信息,位置,地址,命令,文件,目录,L

    linux执行退出命令?

    linux执行退出命令?,档案,状态,工作,命令,信息,地址,电脑,系统,编辑,文件,lin

    linux查看动态命令?

    linux查看动态命令?,系统,状态,工具,实时,时间,命令,工作,信息,地址,百分比,l

    linux中后台执行命令?

    linux中后台执行命令?,系统,状态,暂停,灵活,电脑,网络,服务,第一,名字,命令,l

    linux常用的执行命令?

    linux常用的执行命令?,系统,地址,工作,基础,标准,命令,工具,环境,信息,代码,L