Продолжается подписка на наши издания! Вы не забыли подписаться?

Проект LINQ

Авторы: Don Box
Microsoft Corporation
Anders Hejlsberg
Microsoft Corporation
Опубликовано: 03.07.2006

.NET Language Integrated Query

За двадцать лет индустрия достигла точки стабильности в эволюции технологий объектно-ориентированного программирования. Программисты теперь считают очевидными такие вещи, как классы, объекты и методы. При рассмотрении текущего и следующего поколений технологий становится очевидным, что следующий большой шаг вперед в технологиях программирования должен уменьшить сложность доступа и интеграции информации, исходно определенной без использования ОО-технологий. Два самых распространенных источника не-OO-информации – реляционные базы данных и XML.

В проекте LINQ мы, вместо введения в языки программирования и runtime реляционных или XML-ориентированных возможностей, использовали более общий подход и встроили в .NET Framework универсальные поисковые средства, применимые не только к реляционным или XML-данным, но и к любым источникам информации. Эти средства получили название .NET Language Integrated Query (LINQ).

Мы используем термин language integrated query чтобы указать, что запрос – это интегрированная возможность основных языков программирования, применяемых разработчиком (например, C# или Visual Basic). Интегрированные в язык запросы позволяют выражениям запросов (query expressions) использовать развитые метаданные, проверку синтаксиса при компиляции, статическую типизацию и IntelliSense, ранее доступные только императивному коду. Интегрированные в язык запросы позволяют также применять одни и те же универсальные средства декларативных запросов к любой хранящейся в памяти информации, а не только к информации из внешних источников.

.NET Language Integrated Query определяет набор универсальных стандартных операторов запросов, позволяющих использовать в любом из .NET-языков декларативные операции просмотра, фильтрации и выборки значений. Стандартные операторы запросов позволяют обращаться с запросами к любому источнику информации, основанному на интерфейсе IEnumerable<T>. LINQ позволяет сторонним разработчикам пополнять набор стандартных операторов новыми операторами, соответствующими целевой предметной области или технологии. Еще важнее то, что сторонние разработчики могут также заменять стандартные операторы запросов собственными реализациями, предоставляющими такие дополнительные сервисы, как удаленные вычисления, трансляция запросов, оптимизация и т.д. При соблюдении соглашений паттернов LINQ такие реализации получат тот же уровень интеграции в язык и поддержки среды, что и стандартные операторы.

Расширяемость архитектуры запросов используется в самом проекте LINQ для создания реализаций, работающих как с XML-, так и с SQL-данными. Операторы запросов к XML (XLinq) используют эффективные и простые в использовании средства работы с XML в памяти для обеспечения функциональности XPath/XQuery в основном (таком, как C#) языке программирования. Операторы запросов к реляционным данным (DLinq) строятся на интеграции определений SQL-схемы в систему типов CLR. Такая интеграция позволяет строгую типизацию поверх реляционных данных, сохраняя выразительность реляционной модели и производительность исполнения запросов непосредственно в нижележащем хранилище данных.

Стандартные операторы запросов

Чтобы увидеть LINQ в деле, начнем с простой программы на C# 3.0, использующей стандартные операторы для обработки содержимого массива:

using System;
using System.Query;
using System.Collections.Generic;

class app 
{
  static void Main() 
  {
    string[] names = 
    {
      "Burke", "Connor", "Frank", 
      "Everett", "Albert", "George", 
      "Harris", "David" 
    };

    IEnumerable<string> expr = 
      from s in names 
        where s.Length == 5
        orderby s
        select s.ToUpper();

    foreach (string item in expr)
      Console.WriteLine(item);
  }
}

Если скомпилировать и запустить эту программу, на консоль будет выведено следующее:

BURKE DAVID FRANK

Чтобы понять, как работает LINQ, нужно разобрать первое выражение программы:

    IEnumerable<string> expr = 
      from s in names 
        where s.Length == 5
        orderby s
        select s.ToUpper();

Локальная переменная expr инициализируется выражением запроса. Выражение запроса работает с одним или несколькими источниками информации, применяя один или несколько операторов запроса – либо стандартных, либо операторов предметной области. Данное выражение использует три стандартных оператора: Where, OrderBy и Select.

Visual Basic 9.0 также поддерживает LINQ. Вот предыдущее выражение, написанное на этом языке:

Dim expr As IEnumerable(Of String) = _
                   Select s.ToUpper() _
                   From s in names _
                   Where s.Length = 5 _
                   Order By s

Как C#-, так и Visual Basic-выражения, показанные выше, используют синтаксис запросов. Как и выражение foreach, синтаксис запроса – это удобная декларативная короткая запись кода, который можно было бы написать вручную. Приведенные выше выражения семантически идентичны следующему C#-синтаксису:

IEnumerable<string> expr = names 
                           .Where(s => s.Length == 5) 
                           .OrderBy(s => s)
                           .Select(s => s.ToUpper());

Аргументы операторов Where, OrderBy и Select называются лямбда-выражениями, и являются фрагментами кода, похожими на делегаты (скорее не на делегаты, а на анонимные методы, введенные в C# 2.0 – прим.ред.). Их использование позволяет определять стандартные операторы запросов как индивидуальные методы, и применять их с использованием нотации с точкой (вызывая один метод для результата другого). Вместе эти методы формируют основу для расширяемого языка запросов.

Возможности языка, лежащие в основе LINQ

LINQ полностью построен на универсальных возможностях языков, некоторые из которых являются новинками С# 3.0 и Visual Basic 9.0. Каждая из этих возможностей имеет собственную ценность, а вместе они предоставляют расширяемый способ определения запросов и API для создания запросов. В этом разделе мы рассмотрим эти возможности языков и их вклад в намного более прямой и декларативный стиль запросов.

Лямбда-выражения и деревья выражений

Многие операторы запросов дают пользователю возможность указать функцию, выполняющую фильтрацию, выборку элементов данных или сортировку по ключу. Подсистемы запросов основываются на понятии лямбда-выражений, предоставляющих разработчикам удобный способ создания функций, которые можно передавать как параметры для последующих вычислений. Лямбда-выражения похожи на делегаты CLR и должны придерживаться сигнатуры метода, определенной типом делегата.

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

Чтобы проиллюстрировать это, можно развернуть приведенное выше выражение в эквивалентную, но более явную форму, используя тип делегата Func:

Func<string, bool>   filter  = s => s.Length == 5;
Func<string, string> extract = s => s;
Func<string, string> project = s => s.ToUpper();

IEnumerable<string> expr = names.Where(filter) 
                                .OrderBy(extract)
                                .Select(project);

Лямбда-выражения – это естественное развитие анонимных методов C# 2.0. Так, предыдущий пример можно переписать, используя анонимные методы:

Func<string, bool>   filter  = delegate (string s) 
{
  return s.Length == 5; 
};

Func<string, string> extract = delegate (string s) 
{ 
  return s; 
};

Func<string, string> project = delegate (string s) 
{
  return s.ToUpper(); 
};

IEnumerable<string> expr = 
  names.Where(filter) 
       .OrderBy(extract)
       .Select(project);

В общем, разработчик волен использовать с операторами запросов именованные методы, анонимные методы или лямбда-выражения. Достоинство лямбда-выражений в том, что для их создания используется самый простой и компактный синтаксис. Еще более важно то, что лямбда-выражения могут быть скомпилированы и как код, и как данные, что позволяет обработку лямбда-выражений оптимизаторами, трансляторами и вычислителями во время исполнения.

LINQ определяет отдельный тип Expression<T> (в пространстве имен System.Expressions), указывающий, что некоторое лямбда-выражение преобразуется не в традиционный IL-код, а в дерево выражений (expression tree). Деревья выражений – это эффективное представление данных лямбда-выражений в памяти, делающее структуру выражений прозрачной и явной.

Выдаст ли компилятор исполняемый IL или дерево выражения, определяется тем, как используется лямбда-выражение. Когда лямбда-выражение присваивается переменной, полю или параметру, тип которого – делегат, компилятор генерирует IL, идентичный анонимному методу. Когда лямбда-выражение присваивается переменной, полю или параметру, тип которого – Expression <T>, компилятор создает дерево выражения.

Рассмотрим, например, два объявления переменных:

Func<int, bool>             f = n => n < 5;
Expression<Func<int, bool>> e = n => n < 5;

Переменная f – это ссылка на делегат, ссылающийся на скомпилированный метод.

bool isSmall = f(2); // isSmall будет true

Переменная е – это ссылка на дерево выражений, не исполняемое напрямую.

bool isSmall = e(2); // ошибка компиляции, expressions == data

В отличие от делегатов, являющихся непрозрачным кодом, с деревом выражений можно взаимодействовать как с любой другой структурой данных. Например, эта программа:

Expression<Func<int, bool>> filter = n => n < 5;

BinaryExpression    body  = (BinaryExpression)filter.Body;
ParameterExpression left  = (ParameterExpression)body.Left;
ConstantExpression  right = (ConstantExpression)body.Right;

Console.WriteLine("{0} {1} {2}", 
                  left.Name, body.NodeType, right.Value);

разбирает дерево выражений во время исполнения и выводит строку:

n LT 5

Возможность рассматривать во время исполнения выражения как данные критична для создания экосистемы сторонних библиотек, усиливающих основные абстракции запросов, являющиеся частью платформы. Реализация доступа к данным в DLinq использует это средство для трансляции деревьев выражений в T-SQL-выражения, пригодные для вычислений на хранилище данных.

...

Заключение

.NET Language Integrated Query вводит возможность создания запросов в CLR и языки, рассчитанные на работу с ним. Средства запросов, основанные на лямбда-выражениях и деревьях выражений, позволяют использовать предикаты, выборку значений и сортировку как исполняемый код или как прозрачные данные в памяти, пригодные для дальнейшей обработки и трансляции. Стандартные операторы запросов, определенные в проекте LINQ, работают с любым источником данных, совместимым с IEnumerable<T>, и интегрированы с ADO.NET (DLinq) и System.Xml (XLinq), позволяя применять интегрированные в язык запросы при работе как с SQL, так и с XML.

........................
С полным содержанием данной статьи можно ознакомиться в печатной версии журнала

Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав.

Copyright © 1994-2016 ООО "К-Пресс"