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?
What a Compiled query in LINQ is. A compiled query is a cached version of the actual query, that we use to get the results. We write the query. For the first time it is parsed or verified for any kind of syntax error in LINQ, then converted into SQL version and is added into cache.
The way providers such as LINQ to SQL work is generally via the Queryable class. At their core, they translate expression trees into other query formats, and then construct appropriate objects with the results of executing those out-of-process queries.
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.Cars
is of type IEnumerable<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 lambda0
but something like <Main>b__0
(where Main
is the name of the containing method). Similarly, the cached delegate is actually called CS$<>9__CachedAnonymousMethodDelegate1
.
If you are using LINQ-to-SQL, then db.Cars
will be of type IQueryable<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 foreach
loop is transformed into a try/finally
block (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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With