2015-05-07 11 views
6

Ich habe this answer gelesen und daraus den spezifischen Fall, den es hervorhebt, was ist, wenn Sie ein Lambda in einem anderen Lambda haben und Sie nicht versehentlich das innere Lambda auch mit dem äußeren Lambda kompilieren wollen. Wenn der äußere Lambda-Ausdruck kompiliert wird, soll der innere Lambda-Ausdruck ein Ausdrucksbaum bleiben. Da ist es ja sinnvoll, den inneren Lambda-Ausdruck zu zitieren.Warum würden Sie eine LambdaExpression zitieren?

Aber das ist es, glaube ich. Gibt es einen anderen Anwendungsfall für die Angabe eines Lambda-Ausdrucks?

Und wenn es nicht, warum all LINQ-Operatoren zu tun, das heißt, die Erweiterungen auf IQueryable<T>, die die Prädikate oder Lambda-Ausdrücke in dem Queryable Klasse Zitat deklariert werden sie als Argument erhalten, wenn sie in den MethodCallExpression diese Informationen verpacken.

Ich habe ein Beispiel (und ein paar andere in den letzten Tagen) versucht und es scheint keinen Sinn zu machen, ein Lambda in diesem Fall zu zitieren.

Hier ist ein Methodenaufruf-Ausdruck für eine Methode, die einen Lambda-Ausdruck (und keine Delegate-Instanz) als einzigen Parameter erwartet.

Ich kompiliere dann die MethodCallExpression durch Einwickeln in ein Lambda.

Aber das kompiliert nicht die innere LambdaExpression (das Argument zu der GimmeExpression Methode) auch. Er hinterlässt den inneren Lambda-Ausdruck als Ausdrucksbaum und macht keine Delegierteninstanz daraus.

In der Tat funktioniert es gut, ohne es zu zitieren.

Und wenn ich das Argument zitieren, bricht es und gibt mir einen Fehler, der angibt, dass ich die falsche Art von Argument an die GimmeExpression Methode übergeben.

Was ist das Geschäft? Worum geht es bei diesem Zitat?

private static void TestMethodCallCompilation() 
{ 
    var methodInfo = typeof(Program).GetMethod("GimmeExpression", 
     BindingFlags.NonPublic | BindingFlags.Static); 

    var lambdaExpression = Expression.Lambda<Func<bool>>(Expression.Constant(true)); 

    var methodCallExpression = Expression.Call(null, methodInfo, lambdaExpression); 

    var wrapperLambda = Expression.Lambda(methodCallExpression); 
    wrapperLambda.Compile().DynamicInvoke(); 
} 

private static void GimmeExpression(Expression<Func<bool>> exp) 
{ 
    Console.WriteLine(exp.GetType()); 
    Console.WriteLine("Compiling and executing expression..."); 
    Console.WriteLine(exp.Compile().Invoke()); 
} 

Antwort

4

Sie haben das Argument als ConstantExpression weitergeben müssen:

private static void TestMethodCallCompilation() 
{ 
    var methodInfo = typeof(Program).GetMethod("GimmeExpression", 
     BindingFlags.NonPublic | BindingFlags.Static); 

    var lambdaExpression = Expression.Lambda<Func<bool>>(Expression.Constant(true)); 

    var methodCallExpression = 
     Expression.Call(null, methodInfo, Expression.Constant(lambdaExpression)); 

    var wrapperLambda = Expression.Lambda(methodCallExpression); 
    wrapperLambda.Compile().DynamicInvoke(); 
} 

private static void GimmeExpression(Expression<Func<bool>> exp) 
{ 
    Console.WriteLine(exp.GetType()); 
    Console.WriteLine("Compiling and executing expression..."); 
    Console.WriteLine(exp.Compile().Invoke()); 
} 

Der Grund ist ziemlich offensichtlich sein sollte - Sie einen konstanten Wert vorbei sind, so ist es ein ConstantExpression sein muss. Wenn Sie den Ausdruck direkt übergeben, sagen Sie explizit "und erhalten den Wert exp von diesem komplizierten Ausdrucksbaum". Und da dieser Ausdrucksbaum tatsächlich keinen Wert Expression<Func<bool>> zurückgibt, erhalten Sie einen Fehler.

Der Weg IQueryable funktioniert hat nicht wirklich viel damit zu tun. Die Erweiterungsmethoden unter IQueryable müssen alle Informationen zu den Ausdrücken speichern - einschließlich der Typen und Referenzen der ParameterExpression s und ähnlichem. Das ist, weil sie nicht wirklich tun alles - sie nur den Ausdruck Baum bauen. Die eigentliche Arbeit passiert, wenn Sie queryable.Provider.Execute(expression) anrufen. Im Grunde genommen ist dies der Grund, warum der Polymorphismus beibehalten wird, obwohl wir Komposition und nicht Vererbung (/ Schnittstellenimplementierung) machen. Aber es bedeutet, dass die Erweiterungsmethoden IQueryable selbst keine Abkürzungen machen können - sie wissen nichts darüber, wie die IQueryProvider die Abfrage tatsächlich interpretiert, so dass sie nichts wegwerfen können.

Der wichtigste Vorteil, den Sie daraus ziehen, ist, dass Sie die Abfragen und Unterabfragen erstellen können.Betrachten Sie eine Abfrage wie folgt:

from item in dataSource 
where item.SomeRelatedItem.Where(subItem => subItem.SomeValue == 42).Count() > 2 
select item; 

Nun, dies zu so etwas wie dies übersetzt:

dataSource.Where(item => item.SomeRelatedItem.Where(subItem => subItem.SomeValue == 42).Count() > 2); 

Die äußeren Abfrage ist ziemlich offensichtlich - wir werden ein Where mit dem angegebenen Prädikat erhalten. Die innere Abfrage wird jedoch tatsächlich eine Call zu Where sein, wobei das tatsächliche Prädikat als ein Argument genommen wird.

indem sichergestellt wird, dass die tatsächlichen Anrufungen des Where Methode tatsächlich in eine Call des Verfahrens Where übersetzt, werden diesen beiden Fällen die gleichen, und Ihr LINQProvider ist, dass man etwas einfacher :)

Ich habe tatsächlich geschrieben LINQ-Provider, die IQueryable nicht implementieren, und die tatsächlich einige nützliche Logik in den Methoden wie Where haben. Es ist viel einfacher und effizienter, aber hat den oben beschriebenen Nachteil - die einzige Möglichkeit, Unterabfragen zu behandeln, wäre manuell Invoke die Call Ausdrücke, um den "echten" Prädikatausdruck zu erhalten. Huch - das ist ein Aufwand für eine einfache LINQ-Abfrage!

Und natürlich hilft es Ihnen, verschiedene abfragbare Anbieter zu komponieren, obwohl ich (m) keine Beispiele gesehen habe, zwei völlig unterschiedliche Anbieter in einer einzigen Abfrage zu verwenden.

Wie für den Unterschied zwischen Expression.Constant und Expression.Quote selbst scheinen sie ziemlich ähnlich. Der entscheidende Unterschied ist, dass Expression.Constant alle Verschlüsse als Konstanten statt Verschlüsse behandeln wird. Expression.Quote auf der anderen Seite, wird die "Schließfähigkeit" der Verschlüsse erhalten. Warum? Da der Verschluss Objekte sind selbst auch als Expression.Constant vergangen :) Und da IQueryable Bäume lambdas von Lambdas Lambdas tun [...], die Sie wirklich wollen nicht verlieren die Schließung Semantik an irgendeiner Stelle.

+0

Vielen Dank. Ich war jetzt seit mehreren Monaten hier. Es klickte schließlich nach vielen Beispielen und dachte viel in den letzten Monaten darüber nach. Ich hatte einige Theorien über das Warum und einige von ihnen hatten Recht. Ihre Antwort hat mir auch geholfen. –

Verwandte Themen