LINQ实际编译为什么

本文关键字:为什么 编译 LINQ | 更新日期: 2024-09-19 04:05:01

背景

其背景是,我最近在评论中与另一位知识渊博的用户进行了一次关于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很复杂(而且功能强大),但我主要想了解CLR或与LINQ语句等效的函数。有很多很棒的网站可以帮助理解如何创建LINQ语句,但其中很少有网站能指导如何实际编译或运行这些语句。

旁注-我绝对会通读John Skeet关于linq to object的系列文章。

旁注2-我不应该把它标记为LINQ to SQL。我了解ORM和micro ORM的工作原理。这真的超出了问题的重点。

LINQ实际编译为什么

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

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

成为:

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

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

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

编译器将处理此问题的lambda表达式转换为方法。其中的lambda被转换为一个可以设置为Func<Product, Boolean>的方法,select被转换为Func<Product, String>

有关详细的解释,请参阅Jon Skeet的博客系列:重新实现LINQ到对象。他介绍了这是如何工作的整个过程,包括编译器转换(从查询语法到方法调用),方法是如何实现的,等等

请注意,LINQ到Sql和IQueryable<T>的实现是不同的。lambda生成的Expression<T>被传递到查询提供程序,查询提供程序又以某种方式"转换"为调用(这取决于提供程序如何实现),在ORM的情况下,通常在服务器上运行。


对于这种方法,例如:

    private static IEnumerable<string> ProductNames(IEnumerable<Product> 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<string> ProductNames(class [mscorlib]System.Collections.Generic.IEnumerable`1<class ConsoleApplication3.Product> products) cil managed
{
    .maxstack 3
    .locals init (
        [0] class [mscorlib]System.Collections.Generic.IEnumerable`1<string> enumerable,
        [1] class [mscorlib]System.Collections.Generic.IEnumerable`1<string> enumerable2)
    L_0000: nop 
    L_0001: ldarg.0 
    L_0002: ldsfld class [mscorlib]System.Func`2<class ConsoleApplication3.Product, bool> 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::<ProductNames>b__2(class ConsoleApplication3.Product)
    L_0012: newobj instance void [mscorlib]System.Func`2<class ConsoleApplication3.Product, bool>::.ctor(object, native int)
    L_0017: dup 
    L_0018: stsfld class [mscorlib]System.Func`2<class ConsoleApplication3.Product, bool> ConsoleApplication3.Program::CS$<>9__CachedAnonymousMethodDelegate3
    L_001d: call class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0> [System.Core]System.Linq.Enumerable::Where<class ConsoleApplication3.Product>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>, class [mscorlib]System.Func`2<!!0, bool>)
    L_0022: ldsfld class [mscorlib]System.Func`2<class ConsoleApplication3.Product, string> 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::<ProductNames>b__4(class ConsoleApplication3.Product)
    L_0032: newobj instance void [mscorlib]System.Func`2<class ConsoleApplication3.Product, string>::.ctor(object, native int)
    L_0037: dup 
    L_0038: stsfld class [mscorlib]System.Func`2<class ConsoleApplication3.Product, string> ConsoleApplication3.Program::CS$<>9__CachedAnonymousMethodDelegate5
    L_003d: call class [mscorlib]System.Collections.Generic.IEnumerable`1<!!1> [System.Core]System.Linq.Enumerable::Select<class ConsoleApplication3.Product, string>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>, class [mscorlib]System.Func`2<!!0, !!1>)
    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指令。Lambda被转换为其他方法,例如:

[CompilerGenerated]
private static bool <ProductNames>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查询)等。