Die Vermutungen von Tomas sind ziemlich gut. Sein Denken über Erweiterungsmethoden ist genau richtig. Grundsätzlich benötigen wir, um Erweiterungsmethoden zu verwenden, dass die Aufrufstelle zur Laufzeit auf irgend eine Weise wissen, welche using-Direktiven zum Kompilierzeitpunkt in Kraft waren. Wir hatten einfach nicht genug Zeit oder Budget, um ein System zu entwickeln, mit dem diese Informationen in die Aufrufstelle persistiert werden könnten.
Bei Lambdas ist die Situation tatsächlich komplexer als das einfache Problem festzustellen, ob die Lambda in einen Ausdrucksbaum oder einen Delegaten umgewandelt wird. Betrachten Sie das Folgende:
d.M(123)
wo d ein Ausdruck vom Typ dynamic ist. *Welches Objekt sollte zur Laufzeit als Argument an die Aufrufstelle "M" übergeben werden? Offensichtlich verpacken wir 123 und übergeben das. Dann betrachtet der Überlastungsauflösungsalgorithmus im Laufzeitbinder den Laufzeittyp von d und den Kompilierzeittyp der Ganzzahl 123 und arbeitet damit.
Was ist, wenn es jetzt
d.M(x=>x.Foo())
Was sollen wir jetzt als Argument übergeben? Wir haben keine Möglichkeit, "Lambda-Methode einer Variablen, die eine unbekannte Funktion namens Foo aufruft, in welchem auch immer der Typ von x herausstellt" zu repräsentieren.
Angenommen, wir wollten dieses Feature implementieren: Was müssten wir implementieren? Zuerst bräuchten wir eine Möglichkeit, eine ungebundene Lambda zu repräsentieren. Ausdrucksbäume sind grundsätzlich nur für die Darstellung von Lambdas, in denen alle Typen und Methoden bekannt sind. Wir müssten eine neue Art von "untypisiertem" Ausdrucksbaum erfinden. Und dann müssten wir alle Regeln für die Lambda-Bindung im Laufzeitbinder implementieren.
Betrachten Sie diesen letzten Punkt. Lambdas können Anweisungen enthalten. Die Implementierung dieses Features erfordert, dass der Laufzeitbinder den gesamten semantischen Analyser für jede mögliche Anweisung in C# enthalten muss.
Das hätte weit über unser Budget hinausgehen würde. Wir würden immer noch an C# 4 arbeiten, wenn wir dieses Feature implementieren wollten.
Leider bedeutet dies, dass LINQ nicht sehr gut mit dynamic funktioniert, denn LINQ verwendet natürlich untypisierte Lambdas an vielen Stellen. Hoffentlich werden wir in einer hypothetischen zukünftigen Version von C# einen besser ausgestatteten Laufzeitbinder haben und die Fähigkeit haben, homoikonische Darstellungen von ungebundenen Lambdas zu machen. Aber ich würde nicht darauf warten, wenn ich du wäre.
UPDATE: Ein Kommentar fragt nach Klärung des Punktes zum semantischen Analyser.
Betrachten Sie die folgenden Überladungen:
class C {
public void M(Func f) { ... }
public void M(Func f) { ... }
...
}
und ein Aufruf
d.M(x=> { using(x) { return 123; } });
Angenommen, dass d zum Kompilierzeitpunkt Typ dynamic und zur Laufzeit Typ C ist. Was muss der Laufzeitbinder tun?
Der Laufzeitbinder muss zur Laufzeit bestimmen, ob der Ausdruck x=>{...}
in jeden der Delegattypen in jeder der Überladungen von M umgewandelt werden kann.
Um das zu tun, muss der Laufzeitbinder feststellen können, dass die zweite Überladung nicht anwendbar ist. Wenn sie anwendbar wäre, dann könnte man eine Ganzzahl als Argument für eine using-Anweisung haben, aber das Argument für eine using-Anweisung muss verwertbar sein. Das bedeutet, dass der Laufzeitbinder alle Regeln für die using-Anweisung kennen und korrekt angeben muss, ob jede mögliche Verwendung der using-Anweisung legal oder illegal ist.
Offensichtlich beschränkt sich dies nicht nur auf die using-Anweisung. Der Laufzeitbinder muss alle Regeln für alle C# kennen, um zu bestimmen, ob eine bestimmte Anweisungslambda in einen bestimmten Delegattyp umwandelbar ist.
Wir hatten keine Zeit, einen Laufzeitbinder zu schreiben, der im Wesentlichen einen völlig neuen C#-Compiler darstellt, der DLR-Bäume anstelle von IL generiert. Indem wir keine Lambdas zulassen, müssen wir nur einen Laufzeitbinder schreiben, der weiß, wie er Methodenaufrufe, arithmetische Ausdrücke und einige andere einfache Arten von Aufrufstellen bindet. Das Zulassen von Lambdas macht das Problem der Laufzeitbindung um einiges teurer, um zu implementieren, zu testen und zu warten.