2010-10-08 10 views
15

Na przykład:W jaki sposób LINQ jest wkompilowany w CIL?

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

Jak to przełożyć raz to jest skompilowany? Co dzieje się za kulisami?

+0

Zakładam, że jest to zapytanie LINQ-SQL , a nie tylko filtr w kolekcji? Ci pierwsi będą oczywiście robić znacznie więcej pracy niż ta druga, oczywiście. – mquander

+1

Właściwie, przejdźmy do filtra LINQ-to-Objects w kolekcji. – Liggi

Odpowiedz

26

jest skompilowany w następujący sposób:

  1. pierwsze, wyrażenie kwerendy LINQ przekształca się metodą wzywa:

    public static void Main() 
    { 
        var query = db.Cars.Select<Car, Car>(c => c); 
        foreach (Car aCar in query) 
        { 
         Console.WriteLine(aCar.Name); 
        } 
    } 
    
  2. Jeśli db.Cars jest typu IEnumerable<Car> (co to jest za LINQ-to-Objects), następnie wyrażenie lambda zmienia się w oddzielną metodę:

    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 // ... 
    } 
    

    W rzeczywistości metoda ta nie nazywa się lambda0, ale jest podobna do <Main>b__0 (gdzie Main jest nazwą metody zawierającej). Podobnie, buforowany delegat jest rzeczywiście nazywany CS$<>9__CachedAnonymousMethodDelegate1.

    Jeśli używasz LINQ-SQL, to db.Cars będzie typu IQueryable<Car> i ten krok jest bardzo różny. To zamiast kolei wyrażenie lambda do drzewa ekspresji:

    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. Pętla foreach przekształca się w try/finally bloku (to samo dla obu):

    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. Wreszcie, jest wkompilowane w IL w oczekiwany sposób. Poniżej zamieszczona jest na 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 
    

    Kod skompilowane dla wersji IQueryable<Car> jest również, jak oczekiwano. Oto ważny element, który różni się od powyższych (zmiennych lokalnych będą miały różne offsety i nazwy teraz, ale niech lekceważyć tego):

    // 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

bardzo dobra odpowiedź! Myślę, że krok 2 jest najważniejszy tutaj. Dla kompletności możesz dodać informację o tym, gdzie linq nie jest w trakcie (np. Linq2sql)? –

+0

@Preet: Nie jestem pewien, co masz na myśli, ale dodałem notatkę o LINQ do SQL. – Timwi

+0

@Timwi - dziękuję, co mam na myśli. –

0

Powinieneś skompilować go i uruchomić ildasm w stosunku do wynikowego pliku wykonywalnego, aby się dowiedzieć.

+0

Zrobiłem to, ale miałem nadzieję na pełniejsze i bardziej szczegółowe wyjaśnienie. :) – Liggi

+3

lub użyj reflektora, jeśli chcesz czegoś bardziej przyjaznego dla użytkownika. –

Powiązane problemy