2013-12-13 4 views
6

фонЧто LINQ на самом деле скомпилировано?

Предпосылкой этого является то, что у меня был недавний разговор в комментариях с другим хорошо осведомленного пользователя о том, как LINQ компилируется. Сначала я обобщил и сказал, что LINQ был скомпилирован в цикл for. Хотя это неверно, мое понимание из других стеков, таких как this one, заключается в том, что запрос LINQ скомпилирован в лямбда с петлей внутри него. Затем это вызывается, когда переменная перечисляется в первый раз (после чего сохраняются результаты). Другой пользователь сказал, что 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 для объектов.

Сторона Примечание 2 - Я не должен был отмечать это как LINQ to SQL. Я понимаю, как работают ORM и micro-ORM. На самом деле это тоже вопрос.

+4

LINQ to _what_? – SLaks

+0

'запрос LINQ компилируется в лямбда с петлей внутри него. Затем это вызывается, когда переменная перечисляется в первый раз (после чего сохраняются результаты). Другой пользователь сказал, что LINQ выполняет дополнительные оптимизации, такие как хеширование. Большая часть ошибок неверна. – SLaks

+3

[Reimplementing LINQ to Objects - Jon Skeet] (http://msmvps.com/blogs/jon_skeet/archive/2010/09/03/reimplementing-linq-to-objects-part-1-introduction.aspx) – Habib

ответ

11

Для LINQ к объектам, это компилируется в набор статических вызовов метода:

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 
    ); 

Лямбда-выражения для обработки этого метода превращаются в метод s компилятором. Лямбда в том, где превращается в метод, который может быть установлен на Func<Product, Boolean>, и выбрать в Func<Product, String>.

Для подробного объяснения см. Jon Skeet's blog series: Reimplementing LINQ to Objects.Он просматривает весь процесс, как это работает, включая преобразования компилятора (от синтаксиса запроса до вызовов метода), способы реализации и т. Д.

Обратите внимание, что реализации LINQ to Sql и IQueryable<T> отличаются друг от друга. 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 инструкции для вызовов методов. В лямбды преобразуются в другие методы, такие как:

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

Но чем скомпилированы команды .Where() и .Select()? Внутренне CIL не имеет .Where(). См .: http://en.wikipedia.org/wiki/List_of_CIL_instructions Я пытаюсь понять, что это решает на низком уровне. Это реальная цель этого вопроса! –

+0

@drew_w Я ответил, что - он трансформируется в вызовы Enumerable.Where и Enumerable.Select. –

+0

Этот ответ не относится к основному вопросу OP. Что это за оператор, скомпилированный в CLR? – Shiva

-1

Синтаксис запросов является просто синтаксический сахар для синтаксиса метода, он эффективно компилируется к этому:

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

Теперь то, что эти функции на самом деле зависит от того, какой вкус LINQ вы используете, например Linq to Objects (который объединяет обработчики в памяти) или Linq to SQL (который преобразует его в SQL-запрос) и т. Д.

+0

1) Вы действительно не отвечаете на свой вопрос. Добавленные пояснения дают достаточно четкое представление о том, что вы * не * заинтересованы в том, как синтаксис запросов сопоставляется синтаксису метода, а скорее реализации фактических методов 'Where',' Select' и т. Д. 2) Все, что вы сказали после фрагмента кода, не записывается. Каждый поставщик linq-to-anythingButObjects основан на интерфейсе 'IQueryable', а методы' Where', 'Select' и т. Д. - это все' Queryable.Where' и т. Д. И не являются разными методами для каждого поставщика запросов , Это то, что выполняет поставщик запросов с генерируемыми выражениями. – Servy

Смежные вопросы