Подтвердить что ты не робот

Подписка на динамические события С#

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

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

/EDIT: Я не знаю подпись делегата, необходимого для события, это ядро ​​проблемы

/EDIT 2: Несмотря на то, что контравариантность делегатов кажется хорошим планом, я не могу сделать предположение необходимым использовать это решение

4b9b3361

Ответ 1

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

Ответ 2

Это не вполне общее решение, но если все ваши события имеют форму 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

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

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

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

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

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

Ответ 4

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

Ответ 5

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
}

Ответ 6

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

Ответ 7

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

Ответ 8

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

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