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);
}
}
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 // ...
}
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();
}
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
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
En fait, passons à un filtre LINQ-to-Objects sur une collection. – Liggi