2012-05-24 3 views
6

Ich habe vor kurzem gelernt, C# -Erweiterungsmethoden zu verwenden, um das Aufrufen von Ereignissen zu vereinfachen, und ich benutze sie immer häufiger. Ich habe vor kurzem ein seltsames Problem gefunden, das ich nicht verstehe, und ich habe mich gefragt, ob jemand es erklären könnte.Ereignisse mit C# -Erweiterungsmethoden behandeln

Das Problem tritt auf, wenn Sie versuchen, eine Eventhandler-Erweiterungsmethode als Ereignishandler eines anderen Ereignisses festzulegen. Hier ist ein Beispiel dafür, was ich tue:

public static class EventHandlerExtensions 
{ 
    public static void Raise<TEventArgs>(
     this EventHandler<TEventArgs> eventHandler, 
     object sender, TEventArgs args) where TEventArgs:EventArgs 
    { 
     if (eventHandler != null) 
     { 
      eventHandler(sender, args); 
     } 
    } 
} 

public class Test 
{ 
    private event EventHandler<EventArgs> EventA; 
    private event EventHandler<EventArgs> EventB; 

    public Test() 
    { 
     Console.WriteLine("::Start"); 
     EventB += EventA.Raise; 
     EventA += (s, a) => Console.WriteLine("Event A raised"); 
     EventB.Raise(this, EventArgs.Empty); 
     Console.WriteLine("::End"); 
    } 
} 

In diesem Beispiel sollte Eventa als Ergebnis ausgelöst werden EventB ausgelöst wird. Wenn ich diesen Code ausführe, wird EventB jedoch ausgelöst, aber die Erweiterungsmethode für A findet keine Listener dafür.

Wenn ich den Auftrag um zu ändern, funktioniert alles einwandfrei:

Console.WriteLine("::Start"); 
EventA += (s, a) => Console.WriteLine("Event A raised"); 
EventB += EventA.Raise; 
EventB.Raise(this, EventArgs.Empty); 
Console.WriteLine("::End"); 

Auch EventA.Raise von einem Lambda-Aufruf funktioniert:

Console.WriteLine("::Start"); 
EventB += (s, a) => EventA.Raise(s, a); 
EventA += (s, a) => Console.WriteLine("Event A raised"); 
EventB.Raise(this, EventArgs.Empty); 
Console.WriteLine("::End"); 

Dies ist nur ein einfaches Beispiel, aber ich Ich versuche, eine Klasse zu erstellen, die Ereignisse von Ereignisquellen, die ihr hinzugefügt wurden, auf die sauberste Weise neu versenden kann. Ich möchte keine benannten Methoden nur für das Redispatching der gleichen Ereignisse erstellen, und ich möchte lieber keine Listen von Lambda-Funktionen speichern, die ich später aus den Event-Handlern aushängen kann. Meistens bin ich nur neugierig, warum das passiert?

Irgendwelche Ideen?

+0

Das könnte wirklich schlimm werden, wenn Sie viele Event-Handler haben, die Sie abonnieren. Gute Frage, obwohl !!! :) – IAbstract

+0

Wenn ich deinen Code in VS2010 aufschreibe, tippe 'EventA.R' direkt nach' Console.WriteLine ("Start"); 'zeigt nicht die Erweiterungsmethode in intelliSense an. Es tut wenn ich es kurz vor 'Console.WriteLine (" End ");'. Seltsam. – Guillaume

Antwort

4

Sie erfassen den alten Wert von EventA in der Schließung durch Ihre Raise-Funktion. Da Sie später + verwenden, ändert sich der Wert von EventA, aber Ihr Abschluss hat immer noch einen alten Wert.

Sie den Code ein:

EventB += EventA.Raise; 
EventA += (s, a) => Console.WriteLine("Event A raised"); 

in äquivalenten Code erweitert werden, die deutlich macht, warum Sie alte Delegierten erhalten:

var oldEventA = EventA; 
EventB += oldEventA.Raise; // captures old value here 
// now EventA changed to new value 
EventA = oldEventA + ((s, a) => Console.WriteLine("Event A raised");) 

Sie können folgende hinzufügen, bevor EventB += EventA.Raise diesen Code zu überprüfen, tatsächlich erhöht alt Ereignis für A:

EventA += (s, a) => Console.WriteLine("Old Event A raised"); 
2

Delegierter Objekte sind unveränderlich. Ähnlich wie Saiten. Wenn Sie also EventA zuweisen, erstellen Sie ein neues Objekt. EventB zielt immer noch auf den alten, dem, dem noch kein Event-Handler zugewiesen wurde. Sie müssen die beiden Anweisungen austauschen, um das Problem zu beheben.