什么是LINQ实际编译到?

背景

这样做的背景是我最近在评论中与另一位知识渊博的用户讨论了如何编译LINQ。 我首先“总结”并说LINQ被编译为for循环。 虽然这是不正确的,但是我对其他堆栈的理解是,LINQ查询被编译为lambda,其中包含一个循环。 然后在第一次枚举变量时调用它(之后存储结果)。 另一个用户表示LINQ需要额外的优化,例如散列。 我找不到支持或反对的任何支持文档。

我知道这似乎是一个非常模糊的观点,但我一直觉得,如果我不理解某些东西是如何完全运作的,那么我很难理解为什么我没有正确使用它。

问题

所以,让我们采取以下非常简单的例子:

var productNames = from p in products where p.Id > 100 and p.Id < 5000 select p.ProductName; 

在CLR中实际编译的这个语句什么? LINQ接管了什么优化只是编写一个手动解析结果的函数? 这只是语义还是还有更多呢?

澄清

很明显我问这个问题是因为我不明白LINQ“黑匣子”的内部是什么样的。 尽管我理解LINQ很复杂(而且function强大),但我主要是在寻找对CLR或与LINQ语句等效的function的基本理解。 有很多很棒的站点可以帮助理解如何创建LINQ语句,但是很少有这些站点可以提供有关如何实际编译或运行这些语句的任何指导。

旁注 – 我绝对会阅读关于linq的John Skeet系列文章。

附注2 – 我不应该将其标记为LINQ to SQL。 我理解ORM和微观ORM是如何工作的。 这真的是问题的重点。

对于LINQ to Objects,这被编译为一组静态方法调用:

 var productNames = from p in products where p.Id > 100 and p.Id < 5000 select p.ProductName; 

变为:

 IEnumerable productNames = products .Where(p => p.Id > 100 and p.Id < 5000) .Select(p => p.ProductName); 

这使用了Enumerable类型中定义的扩展方法,因此实际编译为:

 IEnumerable productNames = Enumerable.Select( Enumerable.Where(products, p => p.Id > 100 and p.Id < 5000), p => p.ProductName ); 

处理此问题的lambda表达式由编译器转换为方法。 将where中的lambda转换为一个方法,该方法可以设置为Func ,并选择为Func

有关详细说明,请参阅Jon Skeet的博客系列:重新实现LINQ to Objects 。 他将介绍其工作原理的整个过程,包括编译器转换(从查询语法到方法调用),方法的实现方式等。

请注意,LINQ to Sql和IQueryable实现是不同的。 由lambda生成的Expression被传递到查询提供程序,查询提供程序又以某种方式(由提供程序如何执行此操作)“转换”为调用,通常在服务器上运行一个ORM。


对于此方法,例如:

  private static IEnumerable ProductNames(IEnumerable products) { var productNames = from p in products where p.Id > 100 && p.Id < 5000 select p.ProductName; return productNames; } 

获取编译为以下IL:

  .method private hidebysig static class [mscorlib]System.Collections.Generic.IEnumerable`1 ProductNames(class [mscorlib]System.Collections.Generic.IEnumerable`1 products) cil managed { .maxstack 3 .locals init ( [0] class [mscorlib]System.Collections.Generic.IEnumerable`1 enumerable, [1] class [mscorlib]System.Collections.Generic.IEnumerable`1 enumerable2) L_0000: nop L_0001: ldarg.0 L_0002: ldsfld class [mscorlib]System.Func`2 ConsoleApplication3.Program::CS$<>9__CachedAnonymousMethodDelegate3 L_0007: dup L_0008: brtrue.s L_001d L_000a: pop L_000b: ldnull L_000c: ldftn bool ConsoleApplication3.Program::b__2(class ConsoleApplication3.Product) L_0012: newobj instance void [mscorlib]System.Func`2::.ctor(object, native int) L_0017: dup L_0018: stsfld class [mscorlib]System.Func`2 ConsoleApplication3.Program::CS$<>9__CachedAnonymousMethodDelegate3 L_001d: call class [mscorlib]System.Collections.Generic.IEnumerable`1 [System.Core]System.Linq.Enumerable::Where(class [mscorlib]System.Collections.Generic.IEnumerable`1, class [mscorlib]System.Func`2) L_0022: ldsfld class [mscorlib]System.Func`2 ConsoleApplication3.Program::CS$<>9__CachedAnonymousMethodDelegate5 L_0027: dup L_0028: brtrue.s L_003d L_002a: pop L_002b: ldnull L_002c: ldftn string ConsoleApplication3.Program::b__4(class ConsoleApplication3.Product) L_0032: newobj instance void [mscorlib]System.Func`2::.ctor(object, native int) L_0037: dup L_0038: stsfld class [mscorlib]System.Func`2 ConsoleApplication3.Program::CS$<>9__CachedAnonymousMethodDelegate5 L_003d: call class [mscorlib]System.Collections.Generic.IEnumerable`1 [System.Core]System.Linq.Enumerable::Select(class [mscorlib]System.Collections.Generic.IEnumerable`1, class [mscorlib]System.Func`2) L_0042: stloc.0 L_0043: ldloc.0 L_0044: stloc.1 L_0045: br.s L_0047 L_0047: ldloc.1 L_0048: ret } 

请注意,这些是方法调用的正常call指令。 lambdas转换为其他方法,例如:

 [CompilerGenerated] private static bool b__2(Product p) { return ((p.Id > 100) && (p.Id < 0x1388)); } 

查询语法只是方法语法的语法糖,它有效地编译为:

 var productNames = Products().Where(p => p.Id > 100 && p.Id < 5000).Select(p => productName); 

现在这些函数实际上做了什么取决于你正在使用的LINQ的样式,例如Linq to Objects(将内存处理程序链接在一起)或Linq to SQL(将它转换为SQL查询)等。