2010-10-08 7 views
15

Zum Beispiel:Wie wird LINQ in die CIL kompiliert?

var query = from c in db.Cars select c; 
foreach(Car aCar in query) 
{ 
    Console.WriteLine(aCar.Name); 
} 

Wie würden diese übersetzen, sobald es kompiliert wird? Was passiert hinter den Kulissen?

+0

Ich gehe davon aus, dass dies eine ist LINQ to SQL-Abfrage , und nicht nur ein Filter für eine Sammlung? Ersteres wird offensichtlich viel mehr hinter den Kulissen arbeiten als das letztere. – mquander

+1

Eigentlich wollen wir einen LINQ-to-Objects-Filter für eine Sammlung verwenden. – Liggi

Antwort

26

Es wird auf die folgende Art und Weise zusammengestellt:

  1. Zuerst wird der LINQ-Abfrage-Ausdruck in Methode transformiert ruft:

    public static void Main() 
    { 
        var query = db.Cars.Select<Car, Car>(c => c); 
        foreach (Car aCar in query) 
        { 
         Console.WriteLine(aCar.Name); 
        } 
    } 
    
  2. Wenn db.Cars vom Typ IEnumerable<Car> (die es für LINQ-to-Objects), dann wird der Lambda-Ausdruck in eine separate Methode umgewandelt:

    private Car lambda0(Car c) 
    { 
        return c; 
    } 
    private Func<Car, Car> CachedAnonymousMethodDelegate1; 
    public static void Main() 
    { 
        if (CachedAnonymousMethodDelegate1 == null) 
         CachedAnonymousMethodDelegate1 = new Func<Car, Car>(lambda0); 
        var query = db.Cars.Select<Car, Car>(CachedAnonymousMethodDelegate1); 
        foreach // ... 
    } 
    

    In Wirklichkeit heißt die Methode nicht lambda0, sondern etwa <Main>b__0 (wobei Main der Name der enthaltenden Methode ist). In ähnlicher Weise wird der zwischengespeicherte Delegat tatsächlich CS$<>9__CachedAnonymousMethodDelegate1 genannt. Wenn Sie LINQ-to-SQL verwenden, ist db.Cars vom Typ IQueryable<Car> und dieser Schritt ist sehr unterschiedlich. Es wäre statt dessen die Lambda-Ausdruck in einen Ausdrucksbaum drehen:

    public static void Main() 
    { 
        var parameter = Expression.Parameter(typeof(Car), "c"); 
        var lambda = Expression.Lambda<Func<Car, Car>>(parameter, new ParameterExpression[] { parameter })); 
        var query = db.Cars.Select<Car, Car>(lambda); 
        foreach // ... 
    } 
    
  3. Die foreach Schleife in einen try/finally Block transformiert wird (dies für beide gleich ist):

    IEnumerator<Car> enumerator = null; 
    try 
    { 
        enumerator = query.GetEnumerator(); 
        Car aCar; 
        while (enumerator.MoveNext()) 
        { 
         aCar = enumerator.Current; 
         Console.WriteLine(aCar.Name); 
        } 
    } 
    finally 
    { 
        if (enumerator != null) 
         ((IDisposable)enumerator).Dispose(); 
    } 
    
  4. Schließlich ist das in IL den erwarteten Weg zusammengestellt. Im Folgenden ist für IEnumerable<Car>:

    // Put db.Cars on the stack 
    L_0016: ldloc.0 
    L_0017: callvirt instance !0 DatabaseContext::get_Cars() 
    
    
    // “if” starts here 
    L_001c: ldsfld Func<Car, Car> Program::CachedAnonymousMethodDelegate1 
    L_0021: brtrue.s L_0034 
    L_0023: ldnull 
    L_0024: ldftn Car Program::lambda0(Car) 
    L_002a: newobj instance void Func<Car, Car>::.ctor(object, native int) 
    L_002f: stsfld Func<Car, Car> Program::CachedAnonymousMethodDelegate1 
    
    
    // Put the delegate for “c => c” on the stack 
    L_0034: ldsfld Func<Car, Car> Program::CachedAnonymousMethodDelegate1 
    
    
    // Call to Enumerable.Select() 
    L_0039: call IEnumerable<!!1> Enumerable::Select<Car, Car>(IEnumerable<!!0>, Func<!!0, !!1>) 
    L_003e: stloc.1 
    
    
    // “try” block starts here 
    L_003f: ldloc.1 
    L_0040: callvirt instance IEnumerator<!0> IEnumerable<Car>::GetEnumerator() 
    L_0045: stloc.3 
    
    
    // “while” inside try block starts here 
    L_0046: br.s L_005a 
    L_0048: ldloc.3 // body of while starts here 
    L_0049: callvirt instance !0 IEnumerator<Car>::get_Current() 
    L_004e: stloc.2 
    L_004f: ldloc.2 
    L_0050: ldfld string Car::Name 
    L_0055: call void Console::WriteLine(string) 
    L_005a: ldloc.3 // while condition starts here 
    L_005b: callvirt instance bool IEnumerator::MoveNext() 
    L_0060: brtrue.s L_0048 // end of while 
    L_0062: leave.s L_006e // end of try 
    
    
    // “finally” block starts here 
    L_0064: ldloc.3 
    L_0065: brfalse.s L_006d 
    L_0067: ldloc.3 
    L_0068: callvirt instance void IDisposable::Dispose() 
    L_006d: endfinally 
    

    Der kompilierte Code für die IQueryable<Car> Version wird auch als erwartet. Hier ist der wichtige Teil, die von den oben genannten verschieden ist (die lokalen Variablen werden nun verschiedene Offsets und Namen haben, aber wir wollen, dass außer Acht lassen):

    // typeof(Car) 
    L_0021: ldtoken Car 
    L_0026: call Type Type::GetTypeFromHandle(RuntimeTypeHandle) 
    
    
    // Expression.Parameter(typeof(Car), "c") 
    L_002b: ldstr "c" 
    L_0030: call ParameterExpression Expression::Parameter(Type, string) 
    L_0035: stloc.3 
    
    
    // Expression.Lambda(...) 
    L_0036: ldloc.3 
    L_0037: ldc.i4.1   // var paramArray = new ParameterExpression[1] 
    L_0038: newarr ParameterExpression 
    L_003d: stloc.s paramArray 
    L_003f: ldloc.s paramArray 
    L_0041: ldc.i4.0     // paramArray[0] = parameter; 
    L_0042: ldloc.3 
    L_0043: stelem.ref 
    L_0044: ldloc.s paramArray 
    L_0046: call Expression<!!0> Expression::Lambda<Func<Car, Car>>(Expression, ParameterExpression[]) 
    
    
    // var query = Queryable.Select(...); 
    L_004b: call IQueryable<!!1> Queryable::Select<Car, Car>(IQueryable<!!0>, Expression<Func<!!0, !!1>>) 
    L_0050: stloc.1 
    
+0

sehr gute Antwort! Ich denke, Schritt 2 ist hier der wichtigste. Der Vollständigkeit halber könnten Sie bitte Informationen darüber hinzufügen, wo die linq nicht in Bearbeitung ist (wie zB linq2sql). –

+0

@Preet: Nicht sicher, was Sie meinen, aber ich habe eine Notiz über LINQ to SQL hinzugefügt. – Timwi

+0

@Timwi - danke, was ich meine. –

0

Sie sollten es kompilieren und ildasm gegen die resultierende ausführbare Datei ausführen, um herauszufinden.

+0

Ich habe das getan, aber ich hatte auf eine ausführlichere Erklärung gehofft. :) – Liggi

+3

oder Reflektor verwenden, wenn Sie etwas benutzerfreundlicher möchten. –

Verwandte Themen