For example:
var query = from c in db.Cars select c;
foreach(Car aCar in query)
{
Console.WriteLine(aCar.Name);
}
How would this translate once it is compiled? What happens behind the scenes?
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.
Method 1
It is compiled in the following way:
-
First, the LINQ query expression is transformed into method calls:
public static void Main() { var query = db.Cars.Select<Car, Car>(c => c); foreach (Car aCar in query) { Console.WriteLine(aCar.Name); } } -
If
db.Carsis of typeIEnumerable<Car>(which it is for LINQ-to-Objects), then the lambda expression is turned into a separate method: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 reality the method is not called
lambda0but something like<Main>b__0(whereMainis the name of the containing method). Similarly, the cached delegate is actually calledCS$<>9__CachedAnonymousMethodDelegate1.If you are using LINQ-to-SQL, then
db.Carswill be of typeIQueryable<Car>and this step is very different. It would instead turn the lambda expression into an expression tree: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 // ... } -
The
foreachloop is transformed into atry/finallyblock (this is the same for both):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(); } -
Finally, this is compiled into IL the expected way. The following is for
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
The compiled code for the
IQueryable<Car>version is also as expected. Here is the important part that is different from the above (the local variables will have different offsets and names now, but let’s disregard that):// 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
Method 2
You should compile it and run ildasm against the resulting executable to find out.
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0