Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Конспект лекций (C#).pdf
Скачиваний:
39
Добавлен:
25.03.2016
Размер:
2.43 Mб
Скачать

9 LINQ

Аббревиатура LINQ означает Language-Integrated Query, т.е. язык интегрированных запросов. Это понятие охватывает ряд средств, позволяющих извлекать информацию из источника данных. Извлечение данных составляет важную часть многих программ. Например, программа может получать информацию из списка заказчиков, искать информацию в каталоге продукции или получать доступ к учётному документу, заведённому на работника. Как правило, такая информация хранится в базе данных, существующей отдельно от приложения. Так, каталог продукции может храниться в реляционной базе данных. В прошлом для взаимодействия с такой базой данных приходилось формировать запросы на языке структурированных запросов (SQL). А для доступа к другим источникам данных, например в формате XML, требовался отдельный подход. До версии 3.0 поддержка подобных запросов в С# отсутствовала. Но это положение изменилось после внедрения LINQ.

В основу LINQ положено понятие запроса, в котором определяется информация, получаемая из источника данных. Как только запрос будет сформирован, его можно выполнить. Это делается, в частности, в цикле foreach. В результате выполнения запроса выводятся его результаты. Поэтому использование запроса может быть разделено на две главные стадии. На первой стадии запрос формируется, а на второй – выполняется. Таким образом, при формировании запроса определяется, что именно следует извлечь из источника данных. А при выполнении запроса выводятся конкретные результаты.

Для обращения к источнику данных по запросу, сформированному средствами LINQ, в этом источнике должен быть реализован интерфейс IEnumerable. Он имеет две формы: обобщённую и необобщённую. Как правило, работать с источником данных легче, если в нем реализуется обобщённая форма IEnumerable<T>, где Т обозначает обобщённый тип перечисляемых данных.1

Для применения средств LINQ в исходный текст программы следует включить пространство имён System.Linq.

Перед изучением LINQ рассмотрим понятие методов расширения, анонимные типы и интерфейс IEnumerable<T>, т.к. они широко используются в LINQ.

9.1 Программные конструкции и типы, используемые LINQ

9.1.1 Методы расширения

Методы расширения позволяют «добавлять» методы в существующие типы без создания нового производного типа, перекомпиляции или иного изменения исходного типа.

1 Здесь и далее предполагается, что в источнике данных реализуется форма интерфейса IEnumerable<T>. Основные принципы работы LINQ будут рассматриваться на примере массивов.

136

Методы расширения определяются как статические методы, но вызываются с помощью синтаксиса обращения к методу экземпляра. Их первый параметр определяет, с каким типом оперирует метод, и перед параметром идёт модификатор this. При этом, при вызове метода первый параметр не указывается.

Пример: создать метод расширения, реализующий подсчёт количества слов в строке.

public static class MyClass

{

public static int WordCount(this String str)

{

return str.Split(new char[] { ' ', '.', ',' }, StringSplitOptions.RemoveEmptyEntries).Length;

}

}

 

 

string s =

 

 

" Это текст, имеющий лишние

пробелы и знаки препинания. ";

int n = s.WordCount();

// n = 8

 

Методы, описанные в типе, имеют приоритет перед методами расширения. Т.е., метод расширения никогда не будет вызван, если он имеет ту же самую сигнатуру, что и метод, определённый в типе.

Кроме того, методы расширения не могут обращаться к закрытым переменным типа, который они расширяют.

9.1.2 Анонимные типы

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

Анонимный тип в общем случае описывается с использованием следующей схемы:

new {<имя1> = <значение1> [,<имя2> = <значение2> ...]}

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

var a = new { Name = "Иванов", Year = 1987 };

то свойство Name будет типа string, а свойство Year – типа int.

1 Анонимные типы могут быть созданы и не в запросе, но там они не имеют особого смысла.

137

Свойства анонимных типов реализованы «только для чтения», поэтому команда

string Name = a.Name;

будет допустима, а команда

a.Name = "Петров";

нет.

При использовании анонимных типов имена свойств могут быть автоматически сгенерированы на основе имен объектов, из которых берутся значения, например:

string Name = "Иванов"; int Year = 1987;

var a = new { Name, Year}; string Name2 = a.Name;

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

var a = new { Name = "Иванов", Year = 1987 }; var b = new { Name = "Петров", Year = 1999 }; var c = new { Year = 1991, Name = "Сидоров" }; var d = new { Name = "Антонов" };

a = b; // Одниаковые типы, присовение возможно a = c; // Разные типы, присвоение не возможно a = d; // Разные типы, присвоение не возможно

9.1.3 Интерфейс IEnumerable<T>

Предоставляет перечислитель, который поддерживает простой перебор элементов в указанной коллекции.

Объявлен в пространстве имён System.Collections.Generic.

Форма интерфейса IEnumerable<T> поддерживается всеми массивами в С#, поэтому в примерах будут использоваться массивы.

Для выполнения большинства методов интерфейс IEnumerable<T> использует делегат System.Func, одна из перегрузок которого имеет следующее описание:

public delegate TResult Func<in T, out TResult>(T arg)

Данный делегат определяет метод с одним параметром типа T (перегрузки позволяют определять до 16 входных параметров) и возвращаемым значением типа

TResult.

138

Некоторые методы интерфейса IEnumerable<T> приведены в таблице 9.11.

Таблица 9.1 – Некоторые методы интерфейса IEnumerable<T>

Наименование

Описание

Select

Проецирует каждый элемент последовательности типа

<TSource, TResult>

TSource в новую форму типа TResult с использованием

(Func<TSource,

функции selector. Например:

TResult> selector)

 

 

private int F(int Value) { return Value * 2; }

 

int[] Source = { 3, 2, 1, 5, 4 };

 

IEnumerable<int> Dest = Source.Select(F);

 

// Dest = {6, 4, 2, 10, 8}

 

или с использованием лямбда-выражений:

 

int[] Source = { 3, 2, 1, 5, 4 };

 

IEnumerable<int> Dest =

 

Source.Select(Value => Value * 2);

ToArray<TSource>()

Создаёт массив из объекта IEnumerable<T>. Например:

 

int[] Source = { 3, 2, 1, 5, 4 };

 

int[] Dest =

 

Source.Select(Value => Value * 2).ToArray();

 

// Dest = {6, 4, 2, 10, 8}

 

 

Count<TSource>()

Подсчитывает общее (или удовлетворяющее условию,

Count<TSource>

проверяемому функцией predicate) количество элемен-

тов последовательности. Например:

(Func<TSource,

 

bool> predicate)

int[] Source = { 3, 2, 1, 5, 4 };

 

int Count1 = Source.Count();

 

// Count1 = 5

 

int Count2 = Source.Count(Value => Value > 3);

 

// Count2 = 2

 

 

1 Большинство методов имеет перегрузки. В таблице приводятся наиболее простые реализации методов. Большинство методов являются методами расширения, поэтому имеют первый параметр, ссылающийся на

данные, с которыми проводится обработка. Данный параметр не указывается в таблице в заголовках методов для уменьшения объёма заголовка.

139

Продолжение таблицы 9.1

Наименование

 

 

Описание

 

Min<TSource>()

Возвращает

элемент

последовательности

с минималь-

Max<TSource>()

ным/максимальным значением. Если TSource является

пользовательским типом, то для правильной работы ме-

 

 

тодов

требуется

реализация

интерфейса

 

IComparable<T>. Например:

 

int[] Source = { 3, 2, 1, 5, 4 }; int Min = Source.Min(); // Min = 1

public class People : IComparable<People>

{

public string Name; public int Age;

int IComparable<People>.CompareTo(People other)

{

if (this.Age < other.Age) return -1;

else

if (this.Age == other.Age) return 0;

else

return 1;

}

}

People[] Source = {

new People() {Name="Иванов", Age = 25}, new People() {Name="Петров", Age = 18}, new People() {Name="Сидоров", Age = 34} };

People Max = Source.Max();

// Max = “Сидоров”, 34

140

Продолжение таблицы 9.1

Наименование

Описание

Sum<TSource>

Находит сумму целых чисел, получаемых из каждого

(Func<TSource,

элемента последовательности с использованием функции

int1> selector)

selector. Например:

 

 

public class People

 

{

 

public string Name;

 

public int Weight;

 

}

 

People[] Source = {

 

new People() {Name="Иванов", Weight = 85},

 

new People() {Name="Петров", Weight = 78},

 

new People() {Name="Сидоров", Weight = 94} };

 

int SumWeight =

 

Source.Sum(people => people.Weight);

 

// SumWeight = 257

Average<TSource>

Находит среднее из целых чисел, получаемых из каждого

(Func<TSource,

элемента последовательности с использованием функции

int2> selector)

selector. Например:

 

 

string[] Mas =

 

{ "это", "текст", "для", "примера" };

 

double Avg = Mas.Average(Value => Value.Length);

 

// Avg = 4,5

 

 

Where<TSource>

Возвращает последовательность, состоящую из элемен-

(Func<TSource,

тов, удовлетворяющих условию функции predicate.

bool> predicate)

Например:

 

 

int[] Mas1 = { 6, 4, 8, -5, 9 };

 

int[] Mas2 =

 

Mas1.Where(Value => Value % 3 != 0).ToArray();

 

// Mas2 = { 4, 8, -5 }

1Также имеются методы суммирования для других числовых типов.

2Также имеются методы нахождения среднего для других числовых типов.

141

Продолжение таблицы 9.1

Наименование

Описание

Aggregate<TSource>

Применяет агрегатную функцию func к каждому элемен-

(Func<TSource,

ту последовательности. Особенностями работы метода

TSource, TSource>

являются:

func)

первый элемент последовательности не обрабатывает-

 

 

ся, а является начальным значением результата;

 

функция func имеет два параметра, первым из которых

 

является результат, полученный на предыдущем этапе

 

обработки последовательности;

 

результатом работы метода является результат работы

 

функции с последним элементом последовательности.

 

Например:

 

private string F(string str, string word)

 

{

 

return word + " " + str;

 

}

 

string[] Mas = { "один", "два", "три" };

 

string s = Mas.Aggregate(F);

 

// s = “три два один”

 

или с использованием лямбда выражения:

 

string[] Mas = { "один", "два", "три" };

 

string s =

 

Mas.Aggregate((str, word) => word + " " +

 

str);

 

// s = “три два один”

All<TSource>

Определяет, все ли элементы последовательности удовле-

(Func<TSource,

творяют условию, заданному с использованием функции

bool> predicate)

predicate. Например:

 

 

int[] Mas1 = { 6, 4, 8, -5, 9 };

 

int[] Mas2 = { 6, 4, 8, 5, 9 };

 

bool b1 = Mas1.All(Value => Value > 0);

 

// b1 = False

 

bool b2 = Mas2.All(Value => Value > 0);

 

// b2 = True

142

Продолжение таблицы 9.1

Наименование

Описание

Any<TSource>()

Определяет, есть ли в последовательности какие либо

 

элементы (или элементы, удовлетворяют условию, задан-

Any<TSource>

ному с использованием функции predicate). Например:

(Func<TSource,

 

bool> predicate)

int[] Mas1 = { 6, 4, 8, -5, 9 };

 

bool b1 = Mas1.Any();

 

// b1 = True

 

bool b2 = Mas1.Any(Value => Value == 0);

 

// b2 = False

 

 

Distinct<TSource>()

Возвращает различные элементы последовательности.

 

Например:

 

int[] Mas1 = { 6, 4, 6, 1, 4 };

 

int[] Mas2 = Mas1.Distinct().ToArray();

 

// Mas2 = {6, 4, 1}

 

 

ElementAt<TSource>

Возвращает элемент последовательности с номером

(int index)

index (нумерация с нуля). Если элемента с указанным

 

номером нет, то возникает ошибка. Например:

 

int[] Mas = { 6, 4, 8, -5, 9 };

 

int Value = Mas.ElementAt(2);

 

// Value = 8

 

 

First<TSource>()

Возвращает первый/последний элемент последовательно-

Last<TSource>()

сти (или первый/последний элемент, удовлетворяющий

First<TSource>(

условию, указанному в функции predicate). Например:

 

Func<TSource, bool>

int[] Mas = { 6, 4, -8, -2, 9 };

predicate)

int i1 = Mas.First();

Last<TSource>(

// i1 = 6

Func<TSource, bool>

int i2 = Mas.Last();

predicate)

// i2 = 9

 

 

int i3 = Mas.First(Value => Value < 0);

 

// i3 = -8

 

int i4 = Mas.Last(Value => Value % 2 == 0);

 

// i4 = -2

 

 

Except<TSource>

Находит разность между двумя последовательностями.

(IEnumerable

Например:

<TSource> second)

 

 

int[] Mas1 = { 6, 4, 8, -5, 9 };

 

int[] Mas2 = { 6, -4, -8, -5, 9 };

 

int[] Mas3 = Mas1.Except(Mas2).ToArray();

 

// Mas3 = { 4, 8 }

143