Преобразования запросов являются синтаксическими

Как вы, вероятно, знаете, в C# существует два варианта написания LINQ-запросов. Лично я предпочитаю использовать «выражение запроса» (query comprehension):

from customer in customerList
where customer.City == "London"
select customer.Name

Или вы можете получить аналогичное поведение с помощью синтаксиса «на основе метода»:

customerList
.Where(customer=>customer.City == "London")
.Select(customer=>customer.Name)

Эти два варианта гарантированно эквивалентны, поскольку компилятор просто преобразовывает выражение с первым синтаксисом во второй перед компиляцией. Интересный аспект этого преобразования заключается в том, что оно является (почти) полностью синтаксическим. («Невидимые переменные», сгенерированные для некоторых запросов требуют небольшого количества семантического анализа для определения соответствующих анонимных типов, но в основном, все, что нужно сделать компилятору, так это достать сырые куски кода для каждого выражения и преобразовать программу в форму вызова методов.) Как только код преобразован в форму, понятную другим частям компилятора, семантический анализатор продолжает свою работу как обычно.

Все это означает, что совершенно законно делать всякие глупости. Например, предположим, что под выражением “where” мы подразумеваем не «фильтровать» (filter), а «умножать» (multiply), а под выражением “select” мы на самом деле подразумеваем «прибавить» (add), а не получить проекцию (project). Без проблем!

static class ExtInt
{
    public static int Where(this int c, Func<int, int> f)
    {
        return f(0) * c;
    }
    public static int Select(this int c, Func<int, int> f)
    {
        return f(0) + c;
    }
}

int ten = 10;
int twenty = 20;
int thirty = 30;
int result = from c in ten where twenty select thirty;
Console.WriteLine(result);

И, конечно же, десять где/умножить (where/times) двадцать получить/прибавить (select/plus) тридцать в результате даст 230. Это весьма необычный способ записи математических выражений, но абсолютно законный.

Предполагается следующая семантика метода Where: «взять коллекцию элементов типа T и с помощью предиката соответствия объекта типа T булевому значения, вернуть отфильтрованную коллекцию элементов типа T, соответствующих этому предикату». Этот метод не принимает коллекцию или предикат, он не возвращает коллекцию, и у него уж точно нет семантики фильтрации, но компилятор не знает об этом, да и ему все это абсолютно безразлично. Все, что компилятор делает, так это синтаксически превращает эту строку кода в следующую:

int result = ten.Where(c=>twenty).Select(c=>thirty);

и компилирует ее. Эта строка прекрасно компилируется, поэтому с точки зрения компилятора никаких проблем нет.

В спецификации C# есть раздел, в котором описана реализация шаблона поведения, которую мы ожидаем от поставщика запроса (query provider): функция Where принимает предикат и т.д. Но мы никак не проверяем, что поставщик запроса реализовал поведение, которое отвечает форме или семантике рекомендованного нами шаблона. Если вы сделаете какую-то глупость, например, поведение оператора “where” сделаете отличным от предиката и будете получать безумные результаты, тогда чем я смогу вам помочь? Если вам больно, когда вы что-то делаете, просто не делайте этого!

Оригинал статьи