2008-09-05 24 views
30

Wie würden Sie ein C# -Ereignis dynamisch abonnieren, so dass Sie bei einer Objektinstanz und einem String-Namen, der den Namen des Ereignisses enthält, dieses Ereignis abonnieren und etwas tun (z. B. in die Konsole schreiben) Dieses Ereignis wurde gefeuert?C# Dynamic Event Subskription

Es scheint, mit Reflexion ist dies nicht möglich, und ich möchte vermeiden, Reflection.Emit wenn möglich zu verwenden, wie dies derzeit (für mich) scheint die einzige Möglichkeit, es zu tun.

/EDIT: ich die Unterschrift des Delegierten für die Veranstaltung benötigt nicht wissen, ist dies der Kern des Problems

/EDIT 2: Obwohl Delegierte Kontra wie ein guter Plan scheint, ich kann die Annahme nicht notwendig machen, diese Lösung zu verwenden

Antwort

28

können Sie Ausdrucksbäume kompilieren für Veranstaltungen jeglicher Art ohne Argumente als Event-Handler Leere Methoden zu verwenden. Um anderen Event-Handler-Typen Rechnung zu tragen, müssen Sie die Event-Handler-Parameter den Ereignissen irgendwie 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(); 
    } 
} 
+0

Hölle, Ausdruck Bäume sind so cool. Ich habe einen ähnlichen Code einmal über Reflection.Emit geschrieben. Was für ein Schmerz. –

+0

Tolles Stück Code. Kannst du vielleicht zeigen, wie man es ändert, um Argumente zu unterstützen? Ich habe es geändert, um die Methode mit den Argumenten zu erhalten, aber ich erhalte "Variable 'x' vom Typ 'System.String', auf den von Scope '' verwiesen wird, aber es ist nicht definiert", wenn ich versuche, den Delegaten zu erstellen. Danke –

+0

Done-I fügte ein weiteres Beispiel hinzu. –

2

Es ist möglich, ein Ereignis abonnieren Reflexion mit

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

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

hier Jetzt geht das Problem sein, das Sie lösen gehen zu müssen, zu . Die für jeden Ereignishandler erforderlichen Delegaten haben unterschiedliche Signaturen. Sie müssen diese Methoden dynamisch erstellen, was wahrscheinlich Reflection.Emit bedeutet, oder Sie müssen sich selbst auf einen bestimmten Delegaten beschränken, so dass Sie mit kompiliertem Code umgehen können.

Hoffe, das hilft.

-1

Meinen Sie 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); 

Dies das Abonnement nicht tun, sondern werden das Verfahren geben zu nennen.

Edit:

Beitrag nach dieser Antwort geklärt wurde, wird mein Beispiel nicht helfen, wenn Sie den Typ nicht kennen.

Alle Ereignisse in .Net sollten jedoch dem Standardereignismuster folgen, so lange Sie es befolgt haben, wird dies mit dem grundlegenden EventHandler funktionieren.

1
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 
} 
8

Es ist nicht eine völlig allgemeine Lösung, aber wenn alle Ereignisse des Formulars sind Leere Foo (object o, T args), wobei T von EventArgs leitet, dann können Sie delegieren Kontra verwenden wegzukommen mit es. Wie folgt aus (in dem die Unterschrift von KeyDown ist nicht das gleiche wie die von 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!"); 
    } 
2

Linfu Versuchen - es verfügt über einen universellen Event-Handler, die Sie zur Laufzeit binden jeden Fall läßt.Zum Beispiel, hier ist man Ihnen einen Handler zum Click-Ereignisse eines dynamischen Taste binden können:

 
// 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 kann Sie Ihre Handler jeden Fall binden, unabhängig von den Delegierten Unterschrift. Genießen!

Sie können es hier finden: http://www.codeproject.com/KB/cs/LinFuPart3.aspx

1

ich vor kurzem eine Reihe von Blog-Posts beschreibt Unit-Tests Ereignisse geschrieben, und eine der Techniken, die ich beschrieben Abonnement dynamische Veranstaltung diskutieren. Ich habe Reflection und MSIL (Code Emitting) für die dynamischen Aspekte verwendet, aber das ist alles gut verpackt. Mit der DynamicEvent Klasse können Ereignisse dynamisch gezeichnet werden, um etwa so:

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 Musters war ich realisiert, dass es den Ereignisnamen in dem Aufruf der Ereignisbehandlungsroutine injiziert, so dass Sie wissen, welches Ereignis hat wurde angehoben. Sehr nützlich für Unit-Tests. Der Blogartikel ist ziemlich lang, da er eine Testmethode für Ereigniseinheiten beschreibt, aber vollständiger Quellcode und Tests werden bereitgestellt. Eine detaillierte Beschreibung der Implementierung des dynamischen Ereignisabonnements finden Sie im letzten Beitrag.

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

+0

404 auf Ihrem Blog-Link dort –

+0

@Robert Prost für Kommentare, wir hatten gestern einige Ausfall nach dem Patchen unseres Webservers. –