LINQ序列 - 它们如何在IL中链接?

Ale*_*. A 4 c# linq

无论我使用表达式查询语法还是方法语法,IL看起来几乎都是一样的.

将LINQ查询作为"渐进式语法"执行:

IEnumerable<Employee> query1 = _employees.Where(e => e.Location.Equals("California"));
IEnumerable<Employee> query2 = query1.OrderByDescending(e => e.Name);
IEnumerable<string> query3 = query2.Select(e => e.Name);
Run Code Online (Sandbox Code Playgroud)

产生:

IL_0001: ldarg.0      // this
IL_0002: ldfld        class [mscorlib]System.Collections.Generic.IList`1<class Fundamentals.LINQ.Employee> Fundamentals.LINQ.Syntax::_employees
IL_0007: ldsfld       class [mscorlib]System.Func`2<class Fundamentals.LINQ.Employee, bool> Fundamentals.LINQ.Syntax/'<>c'::'<>9__3_0'
IL_000c: dup          
IL_000d: brtrue.s     IL_0026
IL_000f: pop          
IL_0010: ldsfld       class Fundamentals.LINQ.Syntax/'<>c' Fundamentals.LINQ.Syntax/'<>c'::'<>9'
IL_0015: ldftn        instance bool Fundamentals.LINQ.Syntax/'<>c'::'<TestMethodSyntaxProgressive>b__3_0'(class Fundamentals.LINQ.Employee)
IL_001b: newobj       instance void class [mscorlib]System.Func`2<class Fundamentals.LINQ.Employee, bool>::.ctor(object, native int)
IL_0020: dup          
IL_0021: stsfld       class [mscorlib]System.Func`2<class Fundamentals.LINQ.Employee, bool> Fundamentals.LINQ.Syntax/'<>c'::'<>9__3_0'
IL_0026: call         class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0/*class Fundamentals.LINQ.Employee*/> [System.Core]System.Linq.Enumerable::Where<class Fundamentals.LINQ.Employee>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0/*class Fundamentals.LINQ.Employee*/>, class [mscorlib]System.Func`2<!!0/*class Fundamentals.LINQ.Employee*/, bool>)
IL_002b: stloc.0      // query1

// [100 13 - 100 82]
IL_002c: ldloc.0      // query1
IL_002d: ldsfld       class [mscorlib]System.Func`2<class Fundamentals.LINQ.Employee, string> Fundamentals.LINQ.Syntax/'<>c'::'<>9__3_1'
IL_0032: dup          
IL_0033: brtrue.s     IL_004c
IL_0035: pop          
IL_0036: ldsfld       class Fundamentals.LINQ.Syntax/'<>c' Fundamentals.LINQ.Syntax/'<>c'::'<>9'
IL_003b: ldftn        instance string Fundamentals.LINQ.Syntax/'<>c'::'<TestMethodSyntaxProgressive>b__3_1'(class Fundamentals.LINQ.Employee)
IL_0041: newobj       instance void class [mscorlib]System.Func`2<class Fundamentals.LINQ.Employee, string>::.ctor(object, native int)
IL_0046: dup          
IL_0047: stsfld       class [mscorlib]System.Func`2<class Fundamentals.LINQ.Employee, string> Fundamentals.LINQ.Syntax/'<>c'::'<>9__3_1'
IL_004c: call         class [System.Core]System.Linq.IOrderedEnumerable`1<!!0/*class Fundamentals.LINQ.Employee*/> [System.Core]System.Linq.Enumerable::OrderByDescending<class Fundamentals.LINQ.Employee, string>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0/*class Fundamentals.LINQ.Employee*/>, class [mscorlib]System.Func`2<!!0/*class Fundamentals.LINQ.Employee*/, !!1/*string*/>)
IL_0051: stloc.1      // query2

// [101 13 - 101 69]
IL_0052: ldloc.1      // query2
IL_0053: ldsfld       class [mscorlib]System.Func`2<class Fundamentals.LINQ.Employee, string> Fundamentals.LINQ.Syntax/'<>c'::'<>9__3_2'
IL_0058: dup          
IL_0059: brtrue.s     IL_0072
IL_005b: pop          
IL_005c: ldsfld       class Fundamentals.LINQ.Syntax/'<>c' Fundamentals.LINQ.Syntax/'<>c'::'<>9'
IL_0061: ldftn        instance string Fundamentals.LINQ.Syntax/'<>c'::'<TestMethodSyntaxProgressive>b__3_2'(class Fundamentals.LINQ.Employee)
IL_0067: newobj       instance void class [mscorlib]System.Func`2<class Fundamentals.LINQ.Employee, string>::.ctor(object, native int)
IL_006c: dup          
IL_006d: stsfld       class [mscorlib]System.Func`2<class Fundamentals.LINQ.Employee, string> Fundamentals.LINQ.Syntax/'<>c'::'<>9__3_2'
IL_0072: call         class [mscorlib]System.Collections.Generic.IEnumerable`1<!!1/*string*/> [System.Core]System.Linq.Enumerable::Select<class Fundamentals.LINQ.Employee, string>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0/*class Fundamentals.LINQ.Employee*/>, class [mscorlib]System.Func`2<!!0/*class Fundamentals.LINQ.Employee*/, !!1/*string*/>)
IL_0077: stloc.2      // query3
Run Code Online (Sandbox Code Playgroud)

根据LINQ的工作方式,Where()操作的输出序列应该成为OrderByDescending()的输入序列,第二个输出序列应该流入Select().我只是无法理解哪些IL代码与之对应.

这是神奇的部分吗?

IL_002b: stloc.0      // query1

// [100 13 - 100 82]
IL_002c: ldloc.0      // query1
Run Code Online (Sandbox Code Playgroud)

如果我将LINQ查询作为'流利':

IEnumerable<string> names = _employees.Where(e => e.Location.Equals("California")).OrderByDescending(e => e.Name).Select(e => e.Name);
Run Code Online (Sandbox Code Playgroud)

我没有得到stloc.0,ldloc.0指令.这是"渐进式语法"和"流利语法"之间IL发出的唯一差异.JIT编译器中是否有第二步生成额外的IL指令来执行序列输入?

Sel*_*enç 5

两个版本之间的唯一区别是本地人.在第一个版本中,每个调用的返回值都保存到本地,然后在该本地调用下一个方法,依此类推.

我假设你问的是当没有当地人时它是如何运作的?方法的返回值存储在哪里?

它存储在评估堆栈中.然后使用评估堆栈中的值调用下一个方法.在链中调用最后一个方法后,结果将设置回局部变量names.

看一下Call指令,看看它是如何逐步工作的:

  1. 方法参数arg1到argN被压入堆栈.

  2. 方法参数arg1到argN从堆栈中弹出; 使用这些参数执行方法调用,并将控制转移到方法描述符引用的方法.完成后,callee方法生成一个返回值并发送给调用者.

  3. 返回值被压入堆栈.

因此,在调用方法之前,它的参数被压入堆栈.在你的第一个代码中它是这样的:

  1. 推_employees入堆叠,推Func<Employee, bool>入堆栈
  2. 呼叫 Where
  3. 存储结果 local 0
  4. 加载local 0(将其推入堆栈),推Func<Employee, string>入堆栈
  5. 呼叫 OrderByDescending
  6. 存储结果 local 1
  7. 加载local 1,推Func<Employee, string>入堆栈
  8. 呼叫 Select
  9. 存储结果 local2

第二个版本中没有加载本地内容,因为方法调用的返回值已存储在堆栈中.当一个方法被调用时,你的返回值将成为下一个调用的第一个参数,只有作为第二个参数的委托被压入堆栈并调用下一个方法.

我还要提一下,这根本不是LINQ特有的.方法链这样工作,LINQ并不特别.

注意:我已经简化了推送委托步骤,额外的代码用于缓存.在创建新的委托实例编译器之前,委托实例将缓存在编译器生成的类的字段中,以检查它是否为了效率而创建.