2010-09-30 4 views
5

После прочтения «Odd query expressions» от Jon Skeet, я попробовал код ниже. Я ожидал, что запрос LINQ в конце перевести на int query = proxy.Where(x => x).Select(x => x);, который не компилируется, потому что Where возвращает int. Код скомпилирован и печатает «Where (x => x)» на экране, а запрос - на 2. Select никогда не вызывается, но он должен быть там, где код должен компилироваться. Что происходит?Почему этот запрос LINQ компилируется?

using System; 
using System.Linq.Expressions; 

public class LinqProxy 
{ 
    public Func<Expression<Func<string,string>>,int> Select { get; set; } 
    public Func<Expression<Func<string,string>>,int> Where { get; set; } 
} 

class Test 
{ 
    static void Main() 
    { 
     LinqProxy proxy = new LinqProxy(); 

     proxy.Select = exp => 
     { 
      Console.WriteLine("Select({0})", exp); 
      return 1; 
     }; 
     proxy.Where = exp => 
     { 
      Console.WriteLine("Where({0})", exp); 
      return 2; 
     }; 

     int query = from x in proxy 
        where x 
        select x; 
    } 
} 
+0

См. Также http://blogs.msdn.com/b/ericlippert/archive/2008/05/12/trivial-projections-are-usually-optimized-away.aspx для нескольких дополнительных мыслей об этой функции. –

ответ

11

Это потому, что ваш «выбрать х» не эффективно не оп - компилятор не мешает поставить Select(x => x) вызов в конце. Это будет, если вы удалили пункт where. Ваш текущий запрос известен как выражение выраженного выражения . Подробности см. В разделе 7.16.2.3 спецификации C# 4. В частности:

Вырожденное выражение запроса - это то, которое тривиально выбирает элементы источника. Более поздняя фаза перевода удаляет вырожденные запросы, введенные другими шагами перевода, заменяя их их источником. Однако важно убедиться, что результат выражения запроса никогда не является исходным объектом, так как это выявит тип и идентификатор источника для клиента запроса. Поэтому этот шаг защищает вырожденные запросы, написанные непосредственно в исходном коде, путем явного вызова Select на источнике. Именно тогда разработчики Select и других операторов запроса гарантируют, что эти методы никогда не возвратят исходный объект.

Таким образом, три перевода (независимо от источника данных)

// Query       // Translation 
from x in proxy     proxy.Where(x => x) 
where x 
select x 


from x in proxy     proxy.Select(x => x) 
select x    


from x in proxy     proxy.Where(x => x) 
where x        .Select(x => x * 2) 
select x * 2 
+0

Прохладный, не знал этого. –

+0

Спасибо. Я подумал, что это что-то вроде этого, но я точно не знал, почему его игнорируют. – mcrumley

7

Она компилирует, поскольку синтаксис запроса LINQ является лексического замещения. Компилятор превращает

int query = from x in proxy 
      where x 
      select x; 

в

int query = proxy.Where(x => x);  // note it optimises the select away 

и только тогда это проверить, действительно ли существуют методы Where и Select от типа proxy. Соответственно, в конкретном примере, который вы указали, Select на самом деле не существует для компиляции.

Если у вас что-то вроде этого:

int query = from x in proxy 
       select x.ToString(); 

тогда было бы переодеться в:

int query = proxy.Select(x => x.ToString()); 

и метод Select будет называться.

+0

Ну, дело в том, что он вообще не проверяет выбор. Подозреваю, это была дегенеративная часть запроса, которая сбивала с толку ОП. –

+0

@Jon: расширенный. – Timwi