C # динамическая подписка на события - PullRequest
31 голосов
/ 05 сентября 2008

Как бы вы динамически подписались на событие C #, чтобы при наличии экземпляра Object и имени String, содержащего имя события, вы подписывались на это событие и что-то делали (например, записывали в консоль), когда это событие было уволят?

Казалось бы, с помощью Reflection это невозможно, и я бы хотел избежать использования Reflection.Emit, если это возможно, поскольку это в настоящее время (для меня) кажется единственным способом сделать это.

/ РЕДАКТИРОВАТЬ: Я не знаю, подпись делегата, необходимого для события, это является ядром проблемы

/ РЕДАКТИРОВАТЬ 2: Несмотря на то, что противоречивость делегатов представляется хорошим планом, я не могу сделать предположение, необходимое для использования этого решения

Ответы [ 9 ]

28 голосов
/ 05 сентября 2008

Вы можете скомпилировать деревья выражений для использования методов 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();
     }
 }
8 голосов
/ 05 сентября 2008

Это не совсем общее решение, но если все ваши события имеют вид void Foo (объект 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!");
    }
3 голосов
/ 05 сентября 2008

Можно подписаться на событие, используя Reflection

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

http://msdn.microsoft.com/en-us/library/system.reflection.eventinfo.addeventhandler.aspx

Теперь здесь будет проблема, которую вам придется решить. Делегаты, необходимые для каждого обработчика событий, будут иметь разные подписи. Вам нужно будет найти способ динамически создавать эти методы, что, вероятно, означает Reflection.Emit, или вам придется ограничить себя определенным делегатом, чтобы вы могли обрабатывать его скомпилированным кодом.

Надеюсь, это поможет.

2 голосов
/ 18 декабря 2008

Попробуйте 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

1 голос
/ 22 апреля 2010

Я недавно написал серию постов в блоге, описывающих события модульного тестирования, и один из методов, которые я обсуждаю, описывает динамическую подписку на события. Я использовал рефлексию и 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/

1 голос
/ 05 сентября 2008
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
}
0 голосов
/ 11 апреля 2018

Этот метод добавляет к событию динамический обработчик, который вызывает метод 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 имеет эту подпись:

void OnRaised(string name, object[] parameters);
0 голосов
/ 05 сентября 2008

Вы имеете в виду что-то вроде:

//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.

0 голосов
/ 05 сентября 2008

То, что вы хотите, может быть достигнуто с помощью внедрения зависимостей. Например, Блок приложения Microsoft Composite UI делает именно то, что вы описали

...