2010-10-08 22 views
15

Par exemple:Comment LINQ est-il compilé dans le CIL?

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

Comment cela se traduit-il une fois qu'il est compilé? Qu'est-ce qui se passe dans les coulisses?

+0

Je suppose que c'est un LINQ to SQL requête , plutôt que juste un filtre sur une collection? Le premier fera beaucoup plus de travail en coulisse que ce dernier, évidemment. – mquander

+1

En fait, passons à un filtre LINQ-to-Objects sur une collection. – Liggi

Répondre

26

Il est compilé de la manière suivante:

  1. Tout d'abord, l'expression de requête LINQ est transformée en méthode appelle:

    public static void Main() 
    { 
        var query = db.Cars.Select<Car, Car>(c => c); 
        foreach (Car aCar in query) 
        { 
         Console.WriteLine(aCar.Name); 
        } 
    } 
    
  2. Si db.Cars est de type IEnumerable<Car> (ce qui est pour LINQ-to-Objects), l'expression lambda est transformée en une méthode séparée:

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

    En réalité, la méthode n'est pas appelée lambda0 mais quelque chose comme <Main>b__0 (où Main est le nom de la méthode contenant). De même, le délégué mis en cache est en réalité appelé CS$<>9__CachedAnonymousMethodDelegate1. Si vous utilisez LINQ-to-SQL, db.Cars sera de type IQueryable<Car> et cette étape est très différente. Il serait plutôt tourner l'expression lambda dans un arbre d'expression:

    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. La boucle foreach se transforme en un bloc try/finally (ce qui est la même pour les deux):

    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. Enfin, c'est compilé en IL de la manière attendue. Ce qui suit est pour 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 
    

    Le code compilé pour la version IQueryable<Car> est également prévu. Voici la partie importante qui est différent de ce qui précède (les variables locales auront des décalages et des noms maintenant, mais nous allons pas tenir compte de cela):

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

très bonne réponse! Je pense que l'étape 2 est la plus importante ici. Pour être complet, pourriez-vous ajouter des informations sur l'endroit où le linq n'est pas en cours (comme linq2sql peut-être) s'il vous plaît? –

+0

@Preet: Je ne sais pas ce que vous voulez dire, mais j'ai ajouté une note à propos de LINQ to SQL. – Timwi

+0

@Timwi - merci juste ce que je veux dire. –

0

Vous devez le compiler et exécuter ildasm par rapport à l'exécutable résultant pour le savoir.

+0

Je l'ai fait, mais j'espérais une explication plus complète et plus détaillée. :) – Liggi

+3

ou utiliser un réflecteur si vous voulez quelque chose de plus convivial. –