无论我使用表达式查询语法还是方法语法,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);
生成:
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
根据LINQ的工作方式,Where()操作的输出序列应成为OrderByDescending()的输入序列,而第二个输出序列应流入Select()。我只是不明白哪些IL代码与之相对应。
这是神奇的部分吗?
IL_002b: stloc.0 // query1
// [100 13 - 100 82]
IL_002c: ldloc.0 // query1
如果我以“流利”的方式执行LINQ查询:
IEnumerable<string> names = _employees.Where(e => e.Location.Equals("California")).OrderByDescending(e => e.Name).Select(e => e.Name);
我没有得到stloc.0,ldloc.0指令。这是“渐进语法”和“流利语法”之间发出的IL的唯一区别。 JIT编译器中是否还有第二步生成额外的IL指令来执行序列输入?
答案 0 :(得分:5)
两个版本之间的唯一区别是本地语言。在第一个版本中,每个调用的返回值都保存在本地中,然后在该本地上调用下一个方法,依此类推。
我假设您要问的是在没有本地人的情况下如何工作?方法的返回值存储在哪里?
它存储在评估堆栈中。然后使用评估堆栈中的值调用下一个方法。在链中调用了最后一个方法之后,结果将重新设置为局部变量names
。
看看Call指令以逐步了解它的工作原理:
方法参数arg1到argN被压入堆栈。
方法参数arg1到argN从堆栈中弹出;的 使用这些参数执行方法调用,并且控制为 转移到方法描述符所引用的方法。什么时候 完成后,被调用方方法会生成并返回一个返回值 给呼叫者。
返回值被压入堆栈。
因此,在调用方法之前,会将其参数压入堆栈。在您的第一个代码中,它是这样的:
_employees
推入堆栈,将Func<Employee, bool>
推入堆栈Where
local 0
local 0
(将其推入堆栈),将Func<Employee, string>
推入堆栈OrderByDescending
local 1
local 1
,将Func<Employee, string>
推入堆栈Select
local2
第二个版本中没有加载本地内容,因为方法调用的返回值已存储在堆栈中。调用方法时,您将拥有返回值,该返回值将成为下一个调用的第一个参数,只有作为第二个参数的委托才被压入堆栈,然后调用下一个方法。
我还应该提到这并非完全针对LINQ。方法链接以这种方式工作,LINQ并不特殊。
注意:我简化了委托步骤,这里有用于缓存的额外代码。在创建新的委托实例编译器之前,将委托实例缓存在编译器生成的类的字段中,以检查效率是否在以前创建。