Frage

Wie würden Sie ein C#-Ereignis dynamisch abonnieren, sodass Sie bei gegebener Objektinstanz und einem String-Namen, der den Namen des Ereignisses enthält, dieses Ereignis abonnieren und etwas tun (z. B. in die Konsole schreiben), wenn das Ereignis ausgelöst wurde?

Es scheint, dass dies mit Reflection nicht möglich ist, und ich möchte nach Möglichkeit vermeiden, Reflection.Emit verwenden zu müssen, da dies derzeit (für mich) die einzige Möglichkeit zu sein scheint.

/BEARBEITEN: Ich kenne die für die Veranstaltung benötigte Unterschrift des Delegierten nicht, das ist der Kern des Problems

/EDIT 2: Obwohl Delegierten-Kontravarianz ein guter Plan zu sein scheint, kann ich nicht davon ausgehen, dass diese Lösung erforderlich ist

War es hilfreich?

Lösung

Sie können Ausdrucksbäume kompilieren, um Void-Methoden ohne Argumente als Ereignishandler für Ereignisse beliebigen Typs zu verwenden.Um andere Event-Handler-Typen zu berücksichtigen, müssen Sie die Parameter des Event-Handlers irgendwie den Ereignissen zuordnen.

 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();
     }
 }

Andere Tipps

Es ist keine völlig allgemeine Lösung, aber wenn alle Ihre Ereignisse von der Form void foo (Objekt O, T Args) ausmachen, wobei T von EventArgs stammt, können Sie die Delegierte -Verhütung verwenden, um damit durchzukommen.So (wobei die Signatur von KeyDown nicht mit der von Click identisch ist):

    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!");
    }

Es ist möglich, eine Veranstaltung über Reflection zu abonnieren

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

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

Hier liegt nun das Problem, das Sie lösen müssen.Die für jeden Ereignishandler erforderlichen Delegaten verfügen über unterschiedliche Signaturen.Sie müssen lernen, diese Methoden dynamisch zu erstellen, was wahrscheinlich Reflection.Emit bedeutet, oder Sie müssen sich auf einen bestimmten Delegaten beschränken, damit Sie sie mit kompiliertem Code verarbeiten können.

Hoffe das hilft.

Probieren Sie LinFu aus – es verfügt über einen universellen Event-Handler, mit dem Sie zur Laufzeit eine Bindung zu jedem Event herstellen können.So können Sie beispielsweise einen Handler an das Click-Ereignis einer dynamischen Schaltfläche binden:

// 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);

Mit LinFu können Sie Ihre Handler an jedes Ereignis binden, unabhängig von der Signatur des Delegierten.Genießen!

Du findest es hier:http://www.codeproject.com/KB/cs/LinFuPart3.aspx

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
}

Ich habe kürzlich eine Reihe von Blogbeiträgen geschrieben, in denen Unit-Test-Ereignisse beschrieben werden, und eine der Techniken, die ich bespreche, beschreibt das dynamische Ereignisabonnement.Für die dynamischen Aspekte habe ich Reflektion und MSIL (Code Emitting) verwendet, aber das ist alles gut zusammengefasst.Mit der DynamicEvent-Klasse können Ereignisse wie folgt dynamisch abonniert werden:

EventPublisher publisher = new EventPublisher();

foreach (EventInfo eventInfo in publisher.GetType().GetEvents())
{
    DynamicEvent.Subscribe(eventInfo, publisher, (sender, e, eventName) =>
    {
        Console.WriteLine("Event raised: " + eventName);
    });
}

Eines der Merkmale des von mir implementierten Musters bestand darin, dass es den Ereignisnamen in den Aufruf des Ereignishandlers einfügt, sodass Sie wissen, welches Ereignis ausgelöst wurde.Sehr nützlich für Unit-Tests.

Der Blog-Artikel ist ziemlich lang, da er eine Technik zum Testen von Ereigniseinheiten beschreibt, es werden jedoch der vollständige Quellcode und die Tests bereitgestellt, und eine detaillierte Beschreibung, wie das dynamische Ereignisabonnement implementiert wurde, finden Sie im letzten Beitrag.

http://gojisoft.com/blog/2010/04/22/event-sequence-unit-testing-part-1/

Was Sie wollen, kann mit der Abhängigkeitsinjektion erreicht werden.Zum Beispiel Microsoft Composite UI-App-Block macht genau das, was du beschrieben hast

Diese Methode fügt einem Ereignis einen dynamischen Handler hinzu, der eine Methode aufruft OnRaised, Übergabe der Ereignisparameter als Objektarray:

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 hat diese Signatur:

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

Meinst du so etwas wie:

//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 );

Dadurch wird kein Abonnement erstellt, sondern die aufzurufende Methode wird übergeben.

Bearbeiten:

Der Beitrag wurde nach dieser Antwort geklärt. Mein Beispiel hilft nicht, wenn Sie den Typ nicht kennen.

Alle Ereignisse in .Net sollten jedoch dem Standardereignismuster folgen. Solange Sie es befolgt haben, funktioniert dies also mit dem grundlegenden EventHandler.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top