2009-02-19 16 views
10

Ich schaute auf die Verwendung eines Lamba-Ausdruckes, um zu ermöglichen, dass Ereignisse stark typisiert verdrahtet werden, aber mit einem Hörer in der Mitte, z. die folgenden Klassen gegebenEreignisse in Lambda-Ausdrücken - C# -Compiler-Fehler?

class Producer 
{ 
    public event EventHandler MyEvent; 
} 

class Consumer 
{ 
    public void MyHandler(object sender, EventArgs e) { /* ... */ } 
} 

class Listener 
{ 
    public static void WireUp<TProducer, TConsumer>(
     Expression<Action<TProducer, TConsumer>> expr) { /* ... */ } 
} 

Ein Ereignis wie verdrahtet werden würde:

Listener.WireUp<Producer, Consumer>((p, c) => p.MyEvent += c.MyHandler); 

jedoch ergibt dies einen Compiler-Fehler:

CS0832: An expression tree may not contain an assignment operator

nun zunächst das scheint vernünftig, vor allem nach reading the explanation about why expression trees cannot contain assignments . trotz der C# Syntax, jedoch ist die += keine Zuordnung, ist es ein Aufruf der Producer::add_MyEvent Verfahren, wie wir aus der CIL sehen können, die erzeugt wird, wenn wir in der Regel nur das Ereignis verdrahten:

L_0001: newobj instance void LambdaEvents.Producer::.ctor() 
L_0007: newobj instance void LambdaEvents.Consumer::.ctor() 
L_000f: ldftn instance void LambdaEvents.Consumer::MyHandler(object, class [mscorlib]System.EventArgs) 
L_0015: newobj instance void [mscorlib]System.EventHandler::.ctor(object, native int) 
L_001a: callvirt instance void LambdaEvents.Producer::add_MyEvent(class [mscorlib]System.EventHandler) 

So sieht es für mich aus, als wäre dies ein Compiler-Fehler, da er sich darüber beschwert, dass Zuweisungen nicht erlaubt sind, aber es findet keine Zuweisung statt, nur ein Methodenaufruf. Oder vermisse ich etwas ...?

Edit:

Bitte beachten Sie, dass sich die Frage: "Ist dieses Verhalten ein Compiler Fehler?". Entschuldigung, wenn ich nicht klar darüber bin, was ich gefragt habe.

Edit 2

Nach Antwort Inferis lesen, wo er sagt, ‚an diesem Punkt die + = gilt als Zuordnung‘ dies hat einige sinnvoll, da an dieser Stelle der Compiler wohl nicht der Fall ist weiß, dass es in CIL verwandelt wird.

aber ich bin nicht die explizite Methodenaufruf Form zu schreiben erlaubt:

Listener.WireUp<Producer, Consumer>(
    (p, c) => p.add_MyEvent(new EventHandler(c.MyHandler))); 

Gibt:

CS0571: 'Producer.MyEvent.add': cannot explicitly call operator or accessor

Also, ich denke, die Frage zu kommt auf, was += bedeutet tatsächlich im Zusammenhang mit der C# -Ereignisse. Bedeutet es "rufe die Add-Methode für dieses Event auf" oder bedeutet "add to this event in noch nicht definierter Weise". Wenn es das erstere ist, dann scheint mir das ein Compiler-Bug zu sein, während es, wenn es das letztere ist, etwas unintuitiv ist, aber wohl kein Bug. Gedanken?

+1

+ = bedeutet "den Ereignisaccessor für das angegebene Ereignis aufrufen". Ich denke, es ist kein Fehler, sondern eine leicht nervige und wohl unnötige Einschränkung. –

Antwort

5

In der Spezifikation, Abschnitt 7.16.3, werden die Operatoren + = und - = "Event-Zuweisung" genannt, was sie sicherlich wie einen Zuweisungsoperator klingen lässt. Die Tatsache, dass es in Abschnitt 7.16 ("Zuweisungsoperatoren") ist, ist ein ziemlich großer Hinweis :) Aus diesem Blickwinkel macht der Compilerfehler Sinn.

Allerdings stimme ich zu, dass es übermäßig restriktiv ist, da es für einen Ausdrucksbaum durchaus möglich ist, die durch den Lambda-Ausdruck gegebene Funktionalität darzustellen.

Ich verdächtige die Sprache Designer ging für die "etwas restriktiver, aber konsequenter in Operator Beschreibung" -Ansatz, auf Kosten von Situationen wie diese, fürchte ich.

+0

Jon - ja, das war, wo ich gerade hinkam (siehe meine Bearbeitung # 2). Also denke ich, was die Spezifikation anzeigt, ist, dass + = mit Ereignissen bedeutet "dem Ereignis in bisher noch undefinierter Weise zuzuweisen" anstatt "die Add-Methode für mich aufzurufen". In diesem Fall wäre es frustrierend, aber technisch gesehen kein Bug. –

+0

Ich glaube nicht, dass es noch "undefiniert" ist - es könnte die Methode identifizieren und einen Ausdrucksbaum aussenden, der das aufruft. Das wäre ein vernünftiges Verhalten, außer dass es einen Operator aushändigen würde, der immer noch - streng genommen - ein Zuweisungsoperator ist. –

+0

OK, du hast mich überzeugt; Ich denke, es ist technisch ein Zuweisungsoperator, was bedeutet, dass dies kein Fehler ist. Aber es ist frustrierend, wie wir alle * wissen * es ist wirklich ein Methodenaufruf unter ... Ah gut: -S –

1

+ = ist eine Zuweisung, egal was sie tut (z. B. ein Ereignis hinzufügen). Aus Sicht des Parsers ist es immer noch eine Aufgabe.

Haben Sie versucht,

Listener.WireUp<Producer, Consumer>((p, c) => { p.MyEvent += c.MyHandler; }); 
+0

Der Parser kann es als eine Zuweisung sehen, aber wie gesagt, semantisch und physikalisch ist es keine Zuweisung, es ist ein Methodenaufruf. Das ist etwas, was ich damit meinte, dass es ein Compiler-Bug zu sein scheint. Der Compiler sollte wissen, dass es sich in diesem Zusammenhang nicht um eine Zuweisung handelt. –

+0

Wenn geschweifte Klammern um den Ausdruck gesetzt werden, bedeutet dies, dass es kein Lambda mehr ist, sondern ein einfacher Delegat, was bedeutet, dass es nicht in einen Ausdrucksbaum konvertiert werden kann. –

+0

Ich habe den Punkt des gesamten Listener-Konstrukts an erster Stelle nicht bekommen:/ – Timbo

1

Warum wollen Sie die Expression-Klasse verwenden? Ändern Sie Expression<Action<TProducer, TConsumer>> in Ihrem Code einfach Action<TProducer, TConsumer> und alles sollte funktionieren, wie Sie möchten. Was Sie hier tun, zwingt den Compiler, den Lambda-Ausdruck als Ausdrucksbaum und nicht als Delegat zu behandeln, und ein Ausdrucksbaum kann solche Zuweisungen tatsächlich nicht enthalten (er wird als Zuweisung behandelt, weil Sie den + = Operator, den ich glaube, verwenden)). Nun kann ein Lambda-Ausdruck in eine Form umgewandelt werden (wie in [MSDN] [1] angegeben). Indem Sie einfach einen Delegaten verwenden (das ist alles, was die Action-Klasse ist), sind solche "Zuweisungen" perfekt gültig.Ich habe das Problem hier vielleicht missverstanden (vielleicht gibt es einen bestimmten Grund, warum Sie einen Ausdrucksbaum verwenden müssen?), Aber es scheint, als wäre die Lösung zum Glück so einfach!

Edit: Richtig, verstehe ich Ihr Problem ein wenig besser jetzt aus dem Kommentar. Gibt es einen Grund, warum Sie p.MyEvent und c.MyHandler nicht einfach als Argumente an die WireUp-Methode übergeben und den Event-Handler innerhalb der WireUp-Methode anhängen können (für mich sieht das unter Designgesichtspunkten auch besser aus) ... würde Das beseitigt nicht die Notwendigkeit für einen Ausdrucksbaum? Ich denke, es ist am besten, wenn Sie Expression-Bäume sowieso vermeiden, da sie im Vergleich zu Delegierten eher langsam sind.

+0

Weil ich auf den Ausdrucksbaum zugreifen muss, um greifen zu können, welches Ereignis und welche Handlermethode verdrahtet werden sollte. Ohne den Ausdrucksbaum können Sie das nicht tun. –

+0

Fair genug. Mein Post wird jetzt aktualisiert, um eine alternative Lösung bereitzustellen. – Noldorin

+1

Leider scheint es keine stark typisierte Möglichkeit zu geben, p.MyEvent zu erhalten - der Versuch, es ohne + = zu verwenden, führt zu einem Fehler CS0070: Das Ereignis 'Producer.MyEvent' kann nur auf der linken Seite erscheinen von + = oder - = (außer wenn aus dem Typ 'Producer' verwendet) –

0

Ich denke, das Problem ist, dass abgesehen von dem Expression<TDelegate> Objekt der Ausdrucksbaum aus der Perspektive des Compilers nicht statisch getippt wird. MethodCallExpression und Freunde legen keine statischen Tippinformationen offen.

Obwohl der Compiler alle Typen im Ausdruck kennt, werden diese Informationen bei der Konvertierung des Lambda-Ausdrucks in einen Ausdrucksbaum verworfen. (Werfen Sie einen Blick auf den Code generieren für Ausdruck Bäume)

Ich würde nichtsdestotrotz in Betracht ziehen, dies an Microsoft zu übermitteln.

1

Eigentlich ist der eine Zuordnung, was den Compiler betrifft. Der Operator + = ist überladen, aber der Compiler kümmert sich nicht darum. Schließlich erzeugen Sie einen Ausdruck durch das Lambda (das an einer Stelle zu tatsächlichem Code kompiliert wird) und keinen echten Code.

Also was der Compiler tut ist sagen: Erstellen Sie einen Ausdruck in dem Sie c.MyHandler zu dem aktuellen Wert p.MyEvent hinzufügen und den geänderten Wert zurück in p.MyEvent speichern. Und so machst du tatsächlich eine Aufgabe, selbst wenn du es am Ende nicht bist.

Gibt es einen Grund, warum die WireUp-Methode einen Ausdruck und nicht nur eine Aktion verwenden soll?

+0

Weil ich auf den Ausdrucksbaum zugreifen muss, um zu entscheiden, welches Ereignis und welche Handlermethode verdrahtet werden soll. Ohne den Ausdrucksbaum können Sie das nicht tun. –

+0

Aber ich sehe was du meinst, ich denke der Schlüssel dieser Antwort ist "an diesem Punkt" ... lass mich die Frage bearbeiten. –