How is LINQ compiled into the CIL?

前端 未结 2 437
一整个雨季
一整个雨季 2020-12-29 07:27

For example:

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

How would this trans

2条回答
  •  抹茶落季
    2020-12-29 07:51

    It is compiled in the following way:

    1. First, the LINQ query expression is transformed into method calls:

      public static void Main()
      {
          var query = db.Cars.Select(c => c);
          foreach (Car aCar in query)
          {
               Console.WriteLine(aCar.Name);
          }
      }
      
    2. If db.Cars is of type IEnumerable (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 CachedAnonymousMethodDelegate1;
      public static void Main()
      {
          if (CachedAnonymousMethodDelegate1 == null)
              CachedAnonymousMethodDelegate1 = new Func(lambda0);
          var query = db.Cars.Select(CachedAnonymousMethodDelegate1);
          foreach // ...
      }
      

      In reality the method is not called lambda0 but something like

      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 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>(parameter, new ParameterExpression[] { parameter }));
          var query = db.Cars.Select(lambda);
          foreach // ...
      }
      
    3. The foreach loop is transformed into a try/finally block (this is the same for both):

      IEnumerator 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. Finally, this is compiled into IL the expected way. The following is for IEnumerable:

      // 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 Program::CachedAnonymousMethodDelegate1
      L_0021: brtrue.s L_0034
      L_0023: ldnull 
      L_0024: ldftn Car Program::lambda0(Car)
      L_002a: newobj instance void Func::.ctor(object, native int)
      L_002f: stsfld Func Program::CachedAnonymousMethodDelegate1
      
      
      // Put the delegate for “c => c” on the stack
      L_0034: ldsfld Func Program::CachedAnonymousMethodDelegate1
      
      
      // Call to Enumerable.Select()
      L_0039: call IEnumerable Enumerable::Select(IEnumerable, Func)
      L_003e: stloc.1
      
      
      // “try” block starts here
      L_003f: ldloc.1 
      L_0040: callvirt instance IEnumerator IEnumerable::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::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 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 Expression::Lambda>(Expression, ParameterExpression[])
      
      
      // var query = Queryable.Select(...);
      L_004b: call IQueryable Queryable::Select(IQueryable, Expression>)
      L_0050: stloc.1 
      

提交回复
热议问题