Вы находитесь на странице: 1из 41

33Лабораторная работа 3.

Язык LINQ и PLINQ


Задание
1. Выполнение запросов LINQ
1.1. Выполнение запроса над числовым массивом (варианты 1-5)
1.2. Выполнение запроса над строковым массивом (варианты 1-5)
1.3. Выполнение запроса над коллекцией (массивом) объектов
(варианты 1-5)
1.4. Выполнение двух запрос (использование операции над
множествами и операции соединения) над двумя коллекциями
(списками) объектов (варианты 1-)
Индивидуальные варианты
1.1.
1. Найти максимальный элемент
2. Найти сумму элементов, меньших 10
3. Найти произведение четных элементов
4. Найти квадраты нечетных элементов
5. Найти сумму отрицательных элементов

1.2.
1. Подсчитать количество строк, содержащих заданную подстроку
2. Найти строки, начинающиеся с заданного символа
3. Найти в каждой строке число запятых
4.Найти строки, являющиеся изображениями чисел
5. Подсчитать сумму чисел, содержащихся в каждой строке

1.3.
1. лекарства (название, фирма-производитель, цена за упаковку,
количество упаковок).
2. издания (индекс, название, месячная стоимость, количество
месяцев подписки).
3. детали (название, фирма-изготовитель, количество модификаций),
4. студенты (шифр группы, ФИО, номер зачетки, зарплата отца,
зарплата матери, и количество членов семьи).
5. детали (название, материал, название операции, время
выполнения).

1.4.
1. Студенты (ФИО, номер зачетной книжки) и предметы (название,
ФИО преподавателя, название кафедры).
2. сотрудники предприятия (табельный номер, ФИО, паспортные
данные, адрес, дата рождения, пол, должность и количество детей),
и отделы (наименование, количество закрепленных помещений).

1
3. товары (наименование, цена единицы) и склады (номер, ФИО
кладовщика).
4.изделия (наименование, фирма-изготовитель) детали
(наименование, фирма-изготовитель, (наименование материала, цена
за грамм).
5. книги (регистрационный номер, количество страниц, год издания,
раздел - учебник, художественная общественно-политическая и т.д. )
и читатели (ФИО, домашний адрес, паспортные данные).

2. Выполнение запросов PLINQ

Язык интегрированных запросов LINQ (Language- Integrated Query)


— новое расширение языка С#, добавленное в версию С# 3.0. Язык
LINQ решает проблему работы с большими коллекциями объектов,
когда обычно приходится выбирать подмножество коллекции для
решения определенной задачи.
В прошлом такого рода работа требовала написания объемного
циклического кода и дополнительной обработки, связанной с сортировкой
или группированием найденных объектов, которая порождала еще больший
код. Язык LINQ избавляет от необходимости 333писать дополнительный
циклический код для фильтрации и сортировки. Он позволяет
сосредоточиться на объектах, которыми оперирует программа, предоставляя
удобные средства формулирования запросов.
Цикл foreach
Цикл foreach используется для перебора элементов коллекции. Коллекция —
это группа объектов. С# определяет несколько типов коллекций, и одним из
них является массив.
Формат записи цикла foreach имеет такой вид:
foreach(тип имя_переменной in коллекция) операторы; Здесь элементы тип и
имя_переменной задают тип и имя итерационной переменной, которая при
функционировании цикла fоreach будет получать значения элементов из
коллекции. Элемент коллекция служит для указания просматриваемой кол-
лекции (в данном случае в качестве коллекции мы рассматриваем массив).
Таким образом, элемент тип должен совпадать (или быть совместимым) с
базовым типом массива. Здесь важно запомнить, что итерационную
переменную применительно к массиву можно использовать только для
чтения. Следовательно, невозможно изменить содержимое массива, присвоив
итерационной переменной новое значение.
Рассмотрим простой пример использования цикла foreach. Приведенная ниже
программа создает массив для хранения целых чисел и присваивает его
элементам начальные значения. Затем она отображает элементы массива,
попутно вычисляя их сумму. // Использование цикла foreach.
using System;
2
class ForeachDemo {
public static void MainO { int sum = 0; int[] nums = new int[10];
// Присваиваем элементам массива nums значения,
for(int i = 0; i < 10; i++) nums[i] = i ;
// Используем цикл foreach для отображения значений
// элементов массива и их суммирования, foreach(int x in nums) {
Console.WriteLine("Значение элемента равно: " + х) ;
sum += х;

Console.WriteLine("Сумма равна: " + sum);


Как видно из приведенных выше результатов, цикл foreach последовательно
просматривает элементы массива в направлении от наименьшего индекса к
наибольшему.
Несмотря на то что цикл foreach работает до тех пор, пока не будут
просмотрены все элементы массива, существует возможность досрочного его
останова с помощью оператора break. Например, следующая программа
суммирует только пять первых элементов массива nums.
class ForeachDemo {
public static void MainO
{ int sum = 0; int[] nums = new int [10];
// Присваиваем элементам массива nums значения,
for(int i = 0; i < 10; i++)
nums[i] = i;// Используем цикл foreach для отображения значений
// элементов массива и их суммирования,
foreach(int x in nums)
{Console.WriteLine("Значение элемента равно: " + х) ;
sum += х;if(x == 4) break; }
// Останов цикла, когда х равен 4. >
Console.WriteLine("Сумма первых 5 элементов: " + sum);
Поскольку цикл fоreach может просматривать массив последовательно (от
начала к концу), может сложиться впечатление, что его использование носит
весьма ограниченный характер. Однако это не так. Для функционирования
широкого круга алгоритмов требуется именно такой механизм. Одним из них
является алгоритм поиска.
Например, следующая программа использует цикл foreach для поиска в
массиве заданного значения.
Когда значение найдено, цикл останавливается.
// Поиск значения в массиве с помощью цикла foreach.
using System;
class Search (public static void MainO {
int[] nums = new int[10];
int val; bool found = false;// Присваиваем элементам массива nums значения,
for(int i - 0; i < 10; I++)
nums [ i ] - i;
3
val = 5;// Используем цикл foreach для поиска в массиве nums
// заданного значения,
foreach(int x in nums) { if(x == val) ( found = true; break; }
if(found)
Console.WriteLine("Значение найдено!");
Цикл foreach также используется для вычисления среднего значения,
определения минимального или максимально числа в наборе чисел, поиска
дубликатов и т.д.
Пример foreach с массивом строк
string[] sotr = { "Иванов", "Петров", "Сидоров", "Сергеев", "Миронов",
"Сидоров" };
int k = 0;
foreach (string s in sotr)
{ Console.WriteLine(s);
if (s == "Сидоров")
k++;
}
Console.WriteLine("Имеется {0} Сидоровых", k);
Console.ReadKey();
Классы коллекций
Объекты, то есть экземпляры классов, полезно рассматривать в их
совокупности. Это позволяет выполнять некоторые действия по
суммированию значений полей, подсчету количества экземпляров,
удовлетворяющих определенным условиям, и выполнению самых
разнообразных запросов. Возникает вопрос, как хранить такую совокупность
объектов?
Простым способом хранения совокупности элементов является массив.
Он позволяет добавлять к нему элементы, удалять элементы, просматривать
его для выполнения различных запросов. В один массив, объявленный с
типом базового класса, можно помещать объекты базового класса и
дочерних классов. Например,
Global.A1 = new stud[20];
Пусть имеется два класса: предок stud и потомок grad_stud и на форме
есть компонента groupBox, содержащая две компоненты radioButton для
выбора типа добавляемого объекта, тогда добавление объекта в массив
происходит в соответствие с кодом:
Global.st1.name=textBox2.Text;
Global.st1.age = Convert.ToInt32(maskedTextBox1.Text);
Global.st1.ball = Convert.ToInt32(maskedTextBox2.Text);
Global.st1.group = Convert.ToInt32(maskedTextBox3.Text);
if (radioButton1.Checked)
Global.A1[Global.cnt] = new stud(Global.st1.name, Global.st1.age,
Global.st1.ball,Global.st1.group);
else
4
{ Global.st1.place = textBox1.Text;
Global.A1[Global.cnt] = new grad_stud(Global.st1.name, Global.st1.age,
Global.st1.ball,Global.st1.group,Global.st1.place); }
Global.cnt++;
Для просмотра элементов массива следует организовать цикл перебора
элементов массива и добавления их в ListBox:
listBox1.Items.Clear();
for (i=0;i<Global.cnt;i++)
{listBox1.Items.Add(Global.A1[i].info()); }
Для выполнения метода класса-потомка по отношению к элементу
массива нужно выполнять приведение типа предка к типу объекта-потомка.
Для того же примера определим количество выпускников, имеющих
определенное место распределения:
int i,cnt=0;
string[] cl = new string[2]; string cc, cd;
char[] separator = new char[] { '.' };
grad_stud A2;
for (i = 0; i < Global.cnt; i++) {
cc = Convert.ToString(Global.A1[i].GetType());
cl = cc.Split(separator);
if (cl[1] == "grad_stud") {
A2 = (grad_stud)Global.A1[i];
if (A2.get_place() == textBox1.Text) cnt++; } };
MessageBox.Show("Число выпускников, распределенных в "
+textBox1.Text+ " равно " +Convert.ToString(cnt));
Надо иметь в виду, что метод GetType() возвращает полное имя
объекта в виде имя приложения.имя класса. Поэтому, перводя его в строку и
выделяя имя объекта, можно проверить, что данный объект принадлежит
классу - потомку и привести объект из массива к типу объекта-потомка.
С массивами, однако, связаны свои ограничения. Наибольшее из них
состоит в том, что после создания массивы имеют фиксированный размер,
так что нельзя добавлять новые элементы в конец существующего массива
без создания нового.
Коллекции также позволяют обслуживать группы объектов. Коллекции
могут предоставлять более совершенную функциональность, вроде
возможности управлять доступом к содержащимся в них объектам,
возможности выполнять по ним поиск и т.д.
Цикл foreach позволяет обращаться к каждому элементу в коллекции с
помощью такого простого синтаксиса:
foreach ( <базовый_тип> <имя> in <коллекция> )
{ // можно использовать <имя> для каждого элемента }
Этот цикл будет осуществлять проход по всем элементам и помещать
каждый из них по очереди в переменную <имя> безо всякого риска
получения доступа к недопустимым элементам. Такой подход позволяет не
5
беспокоиться о том, сколько элементов содержится в коллекции, и быть
уверенным, что каждый из них будет использован в цикле.
Аналогично массивам необходимо выполнять приведение типов для
использования методов класса-потомка.
При использовании коллекций в форму надо вставлять директиву
using System.Collections;

Класс List и его методы


Класс List – это коллекция, элементами которой является экземпляры
класса. Поддерживает методы для поиска по списку, выполнения сортировки
и других операций со списками.
Объявление списка имеет вид:
List<тип входящего объекта> имя = new List< тип входящего объекта>();
У класса имеются свойство
Count - Число элементов, которое фактически содержится в коллекции.
Полезные методы класса List перечислены в табл. 12.
Таблица 12
Некоторые методы класса List
Метод Описание
Add(объект) Добавляет объект в конец коллекции
Clear() Удаляет все элементы из коллекции
bool Contains(элемент) Определяет, входит ли элемент в
состав коллекции
bool Exists(метода) Определяет, содержит ли коллекция
элементы, удовлетворяющие
условиям указанного метода,
который в качестве параметра должен
иметь объект того класса, который
помещен в коллекцию
Тип объекта Find(метод) Выполняет поиск элемента,
удовлетворяющего условиям метода,
и возвращает первое найденное
вхождение в пределах всего списка
Insert(индекс с 0,элемент) Добавляет элемент в список в
позиции с указанным индексом
bool Remove(элемент) Удаляет первое вхождение
указанного объекта из коллекции
Sort(метод сравнения) Сортирует элементы во всем списке с
использованием указанного метода
Один из примеров иллюстриует применение метода Exists
List<string> dinosaurs = new List<string>();
dinosaurs.Add("Compsognathus");
dinosaurs.Add("Amargasaurus");
dinosaurs.Add("Oviraptor");
6
dinosaurs.Add("Velociraptor");
dinosaurs.Add("Deinonychus");
dinosaurs.Add("Dilophosaurus");
dinosaurs.Add("Gallimimus");
dinosaurs.Add("Triceratops");
Console.WriteLine("\nExists(EndsWithSaurus): {0}",
dinosaurs.Exists(EndsWithSaurus));
// Метод поиска возвращает true, если строка заканчивается "saurus"
private static bool EndsWithSaurus(String s)
{ if ((s.Length > 5) && (s.Substring(s.Length - 6).ToLower() == "saurus"))
{ return true; }
else
{return false; } }
Второй пример иллюстрирует применение метода Sort:
List<string> dinosaurs = new List<string>();
dinosaurs.Add("Pachycephalosaurus");
dinosaurs.Add("Amargasaurus");
dinosaurs.Add("");
dinosaurs.Add(null);
dinosaurs.Add("Mamenchisaurus");
dinosaurs.Add("Deinonychus");
Display(dinosaurs);
dinosaurs.Sort(CompareDinosByLength);
Display(dinosaurs);
private static int CompareDinosByLength(string x, string y)
{// сранение длин двух строк
int retval = x.Length.CompareTo(y.Length);
if (retval != 0)
// Если строки неравной длины, то более длинная строка больше
return retval;
else
{// Если строки равной длины, они сортируются обычным образом
return x.CompareTo(y); }
}
private static void Display(List<string> list)
{ Console.WriteLine();
foreach( string s in list )
{if (s == null)
Console.WriteLine("(null)");
else
Console.WriteLine("\"{0}\"", s); } }
LINQ для массивов

Первый запрос LINQ


7
В следующем примере создадим запрос для поиска некоторых данных
в простом, находящемся в памяти, массиве объектов с использованием LINQ
и выведем результат на форму.
В число директив надо добавить
using System.Linq;
Форма имеет вид (рис. )

Рис. Форма приложения для поиска

В нижней части формы расположена компонента label для вывода


результатов поиска.
Фактически можно выбрать один из двух запросов –
 поиск данных, содержащих заданную строку и
 поиск данных, равных заданной строке.
Далее создадим некоторые данные, что осуществляется
объявлением и инициализацией массива names:
string[] names = { "Alonso", "Zheng", "Smith", "Jones", "Smythe", "Small",
"Ruiz", "Hsieh", "Jorgenson", "Ilyich", "Singh", "Samba", "Fatimah" };
При нажатии кнопки выбирается и выполняется один из двух
вариантов запроса:
if (radioButton1.Checked)
{ // поиск подстроки
var gueryResults1 =
from n in names
where n.Contains(aName)
select n;
label1.Text = "";
foreach (string st in gueryResults1)
label1.Text = label1.Text + st + '\n'; }
else
{ // равенство
var gueryResults2=
from n in names
where n.Equals(aName)
8
select n;
label1.Text = "";
foreach (string st in gueryResults2)
label1.Text = label1.Text + st + '\n'; }
Оператор запроса LINQ состоит из четырех частей: начинающееся с var
объявление переменной gueryResults1, которой выполняется присваивание с
использованием синтаксиса запроса, состоящего из конструкций from, where
и select.
Ключевое слово var заставляет компилятор С# вывести тип результата
на основе самого запроса. Таким образом, не нужно заранее объявлять тип
объектов, возвращаемых запросом LINQ— компилятор позаботится об этом
сам. Если запрос может вернуть множество элементов, то этот тип должен
вести себя как коллекция объектов из источника данных запроса;
Конструкция from специфицирует источник запрашиваемых данных:
from n in names
Источником данных в рассматриваемом случае является names —
массив строк, объявленный ранее. Переменная n — просто описание
индивидуального элемента в источнике данных - массиве.
В следующей части запроса определяетя условие запроса с
использованием конструкции where: where n.Equals(aName).В конструкции
where может быть определено любое булевское выражение, которое
применимо к элементам в источнике данных. Конструкция where называется
ограничивающей операцией в LINQ, потому что она ограничивает результат
запроса.
В примере определено, что строка равна заданной.
Конструкция select определяет, какие элементы появятся в
результирующем наборе. В примере она выглядит так: select n
Конструкция select необходима для определения того, какие элементы
запрос поместит в результирующий набор.
Формирование результата происходит с помощью цикла foreach:
foreach (string st in gueryResults2)
label1.Text = label1.Text + st + '\n';
В LINQ существует много способов решения одной и той же задачи,
как это часто бывает в программировании. Предыдущий пример написан с
использованием синтаксиса запросов LINQ. В следующем примере напишем
ту же программу с применением синтаксиса методов LINQ.
Если написать в программе names и поставить точку после names, то
можно увидеть в списке все доступные методы объекта names, как показано
на рис.42.

9
Рис. 42.
Список методов объекта names

Метод Where<> и большинство прочих доступных методов являются


расширениями LINQ. Выражение запроса for. . .where. . .select,
использованное в предыдущем примере, транслируется компилятором С# в
серию вызовов этих методов. При использовании синтаксиса методов LINQ
эти методы вызываются напрямую. Синтаксис запросов является
предпочтительным способом программирования запросов в LINQ,
поскольку его намного легче читать и проще использовать в большинстве
часто применяемых запросов. Однако важно иметь базовое понятие о
синтаксисе методов, поскольку некоторые возможности LINQ либо
недоступны в синтаксисе запросов, либо просто их легче применять в
синтаксисе методов.
Большинство методов LINQ, использующих синтаксис методов,
требует
передачи метода или функции для вычисления выражения запроса. используя
специальную конструкцию С# 3.0, которая называется лямбда-выражением.
Это короткий, согласованный способ записи метода, применяемый для
вычисления выражений запроса. Ниже показано простое лямбда-выражение:
п => п < 1000
Операция => называется лямбда - операцией.
Лямбда-выражение n => n < 1000 определяет функцию, которая
принимает параметр по имени n и возвращает true, если п меньше 1000, и
false— в противном случае.
Лямбда-выражение для запроса в примере программы может быть
записано, как
n =>n.Equals(aName);
что следует читать, как "n такое, что оно равно aName”.
Изменение в примере:
var gueryResults1 =
names.Where(n=>n.Contains(aName));
label1.Text = "";
foreach (string st in gueryResults1)
label1.Text = label1.Text + st + '\n';

10
В конструкции where (или вызове метода Where ()), LINQ позволяет
сортировать результирующие данные. Для этого существует ключевое слово
orderby:
var gueryResults2=from n in names where n.Equals(aName) orderby n select n;
Это сортировка по возрастанию. Для сортировки по убыванию
добавляется слово descending:
where n.Equals(aName) orderby n descending
Для отбора данных, удовлетворяюших учловию, можно применять
операции сравнения. Например:
int[] numbers = generateLotsOfNumbers(12345678);
var queryResults = from n in numbers where n < 1000 select n ;
foreach (int item in queryResults)
{ ListBox1.Items.Add(item.ConvertTo.String
Console.WriteLine(item); }
generateLotsOfNumbers - метод для генерации списка случайных чисел:
private static int[] generateLotsOfNumbers(int count)
{ Random generator = new Random();
int[] result = new int[count];
for (int i = 0; i < count; i++)
{ result[i] = generator.Next (); }
return result; }
LINQ предоставляет набор агрегатных операций, позволяющих
анализировать результаты запроса, не выполняя цикл по всем результатам.
Перечисленные ниже агрегатные операции чаще всего используются для
набора числовых результатов, таких как результаты предыдущего запроса.
Count () Подсчитывает количество результатов
Min () Минимальное значение результатов
Mах () Максимальное значение результатов
Average () Среднее значение числовых результатов
Sum () Сумма всех числовых результатов
Приведенный ниже код выводит в список различные числовые
характеристики подмножества запроса:
int[] numbers = generateLotsOfNumbers(2345678);
listBox1.Items.Add("Числовые агрегаты");
var queryResults = from n in numbers where n > 1000 select n;
listBox1.Items.Add("Количество чисел > 1000");
listBox1.Items.Add(Convert.ToString(queryResults.Count()));
listBox1.Items.Add("Максимальное из чисел > 1000");
listBox1.Items.Add(Convert.ToString(queryResults.Max()));
listBox1.Items.Add("Минимальное из чисел > 1000");
listBox1.Items.Add(Convert.ToString(queryResults.Min()));
listBox1.Items.Add("Среднее из чисел > 1000");
listBox1.Items.Add(Convert.ToString(queryResults.Average()));
listBox1.Items.Add("Сумма чисел > 1000");
11
listBox1.Items.Add(Convert.ToString(queryResults.Sum(n => (long)n)));

Все операции запроса LINQ состоят из трех различных действий.

1. Получение источника данных.


2. Создание запроса.
3. Выполнение запроса.

В следующем примере показано выражение этих трех частей операции


запроса в исходном коде. В примере в качестве источника данных для
удобства используется массив целых чисел; тем не менее, те же принципы
применимы и к другим источникам данных.
class IntroToLINQ
{
static void Main()
{
// The Three Parts of a LINQ Query:
// 1. Data source.
int[] numbers = new int[7] { 0, 1, 2, 3, 4, 5, 6 };

// 2. Query creation.
// numQuery is an IEnumerable<int>
var numQuery =
from num in numbers
where (num % 2) == 0
select num;

// 3. Query execution.
foreach (int num in numQuery)
{
Console.Write("{0,1} ", num);
}
}
}

На следующем рисунке показана завершенная операция запроса. В LINQ выполнение


запроса отличается от самого запроса; другими словами, создание переменной запроса
само по себе не связано с получением данных.

12
Запрос указывает, какую информацию нужно извлечь из источника или
источников данных. При необходимости, запрос также указывает способ
сортировки, группировки и формирования этих сведений перед
возвращением. Запрос хранится в переменной запроса и инициализируется
выражением запроса. Чтобы упростить написание запросов, в C# появился
новый синтаксис запроса.

Запрос из первого примера возвращает все четные числа из массива целых


чисел. Выражение запроса содержит три предложения: from, where и select.
(Если вы знакомы с SQL, обратите внимание, что порядок предложений
противоположен порядку в SQL.) Предложение from указывает источник
данных, предложение where применяет фильтр, а предложение select
указывает тип возвращаемых элементов.

Важно, что в LINQ сама переменная запроса не предпринимает действий и не


возвращает никаких данных. Она просто хранит сведения, необходимые для
предоставления результатов при последующем выполнении запроса.

В этом разделе рассматриваются три способа создания запросов LINQ на


языке C#.
1. Использование синтаксиса запроса.
2. Использование синтаксиса метода.
13
3. Использование сочетания синтаксиса запроса и синтаксиса метода.
В следующем примере демонстрируются простые запросы LINQ при
использовании каждого из перечисленных выше синтаксисов. Общее
правило таково: следует использовать (1) всегда, когда это возможно; (2) и
(3) при необходимости.
Синтаксис запроса
Лучше всего для создания запросов использовать синтаксис запроса, создавая выражения
запросов. В следующем примере показано три выражения запроса. В первом выражении
демонстрируется фильтрация или ограничение результатов путем применения условий в
предложении where. Оно возвращает все элементы в исходной последовательности со
значениями больше 7 и меньше 3. Второе выражение демонстрирует сортировку
возвращаемых результатов. Третий запрос демонстрирует группировку результатов. Он
возвращает две группы на основе первой буквы слова.

// Query #1.
List<int> numbers = new List<int>() { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };

// The query variable can also be implicitly typed by using var


var filteringQuery =
from num in numbers
where num < 3 || num > 7
select num;

// Query #2.
var orderingQuery =
from num in numbers
where num < 3 || num > 7
orderby num ascending
select num;

// Query #3.
string[] groupingQuery = { "carrots", "cabbage", "broccoli", "beans",
"barley" };
var queryFoodGroups =
from item in groupingQuery
group item by item[0];

В каждом из приведенных выше примеров фактическое выполнение запроса


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

Синтаксис метода
Некоторые операции запросов должны быть выражены в виде вызова метода. Чаще всего
используются методы, возвращающие одноэлементные числовые значения, например
Sum, Max, Min, Average и т.д. Эти методы всегда должны быть вызваны последними в
запросе, поскольку они представляют только одно значение и не могут служить
источником дополнительных действий запроса. В следующем примере демонстрируется
вызов метода в выражении запроса.
List<int> numbers1 = new List<int>() { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
List<int> numbers2 = new List<int>() { 15, 14, 11, 13, 19, 18, 16, 17, 12, 10
};
// Query #4.
double average = numbers1.Average();

14
// Query #5.
var concatenationQuery = numbers1.Concat(numbers2);

Если у метода есть параметры, они представлены в виде лямбда-выражения, как показано
в следующем примере.
// Query #6.
var largeNumbersQuery = numbers2.Where(c => c > 15);

Смешанный синтаксис запроса и метода


В этом примере демонстрируется использование синтаксиса метода для результатов
предложения запроса. Нужно всего лишь заключить выражение запроса в скобки, а затем
применить оператор точки и вызвать метод. В следующем примере запрос #7 возвращает
количество чисел, значение которых лежит в диапазоне от 3 до 7. Однако в общем случае
лучше использовать вторую переменную для хранения результатов вызова метода. Таким
образом, будет меньше вероятность перепутать запрос с результатами запроса.

// Query #7.

// Using a query expression with method syntax


int numCount1 =
(from num in numbers1
where num < 3 || num > 7
select num).Count();

// Better: Create a new variable to store


// the method call result
var numbersQuery =
from num in numbers1
where num < 3 || num > 7
select num;

int numCount2 = numbersQuery.Count();

Запрос №7 возвращает одиночное значение, а не коллекцию, поэтому он выполняется


мгновенно.

Можно использовать синтаксис метода следующим образом.


var numCount = numbers.Where(n => n < 3 || n > 7).Count();
Можно использовать явную типизацию следующим образом.
int numCount = numbers.Where(n => n < 3 || n > 7).Count();

LINQ для коллекций классов

Создадим класс Customer и коллекцию типа List, содержащую


экземпляры Customer.
В классе Customer есть поля, конструктор, методы Get_Region(0 и
Get_ID() для возврата значений соответствующих полей и метод Info() для
отображения полей экземпляра класса:

class Customer
15
{public string ID ;
private string City ;
private string Country ;
private string Region ;
private decimal Sales ;
public string Get_Region() { return Region; }
public Customer(string ID, string City, string Country, string Region, decimal
Sales)
{this.ID = ID; this.City = City; this.Country = Country;
this.Region = Region; this.Sales = Sales;}
public string Info()
{ return "ID: " + ID + " Город: " + City + " Страна: " + Country +
"Регион: " + Region + " Продажи: " + Convert.ToString(Sales); } }

List<Customer> customers = new List<Customer>();


При загрузке формы добавим к коллекции несколько объектов:
customers.Add(new Customer("A", "New York", "USA","North America", 9999));
customers.Add(new Customer("C", "Karachi", "Pakistan", "Asia", 7777));
customers.Add(new Customer("D", "Delhi", "India", "Asia", 6666));
customers.Add(new Customer("E", "SantaPaulo", "Brazil", "South America",
5555));
customers.Add(new Customer("F", "Moscow", "Russia", "Europe", 4444));
customers.Add(new Customer("G", "Seoul", "Korea", "Asia", 3333));
customers.Add(new Customer("H", "Istanbul", "Turkey", "Asia", 2222));
customers.Add(new Customer("I", "Shanghai", "China", "Asia", 1111));
customers.Add(new Customer("J", "Lagos", "Nigeria", "Africa", 1000));
customers.Add(new Customer("K", "Mexico City", "Mexico", "North America",
1000));
customers.Add(new Customer("LM", "Jakarta", "Indonesia", "Asia", 3000));
customers.Add(new Customer("M", "Tokyo", "Japan", "Asia", 4000));
customers.Add(new Customer("N", "Los Angeles", "USA", "North America",
5000));
customers.Add(new Customer("O", "Cairo", "Egypt", "Africa", 6000));
customers.Add(new Customer("P", "Tehran", "Iran", "Asia", 7000));
customers.Add(new Customer("Q", "London", "UK", "Europe", 8000));
customers.Add(new Customer("R", "Beijing", "China", "Asia", 9000));
customers.Add(new Customer("S", "Bogota", "Colombia", "South America",
1001));
customers.Add(new Customer("T", "Lima", "Peru", "South America", 2002));
На форме поместим кнопку и список listBox для вывода объектов из
Азии при нажатии на кнопку (рис. 43):

16
Рис. 43. Форма для вывода части коллекции

Код события нажатия кнопки:


private void button1_Click(object sender, EventArgs e)
{var queryResults = from c in customers where c.Get_Region() == "Asia" select
c;
listBox1.Items.Clear();
foreach (Customer d in queryResults)
listBox1.Items.Add(d.Info()); }
Переменная типа var содержит результаты запроса для коллекции List с
условием отбора экземпляров из Азии. Цикл foreach добавляет в список
ListBox значение метода Info (рис. 44)

Рис. 44. Форма при выполнении приложения


Для применения нужного числа выражений фильтра в предложении
where можно использовать логические операторы C# && и ||.

17
В предложении where может содержаться один или несколько методов,
возвращающих логические значения.
Например,
var queryEvenNums =from num in numbers where IsEven(num)select num;
// Выполнение запроса
foreach (var s in queryEvenNums)
{Console.Write(s.ToString() + " "); } }
// Метод.
static bool IsEven(int i)
{ return i % 2 == 0; }

Включение в запрос дополнительных полей


В LINQ можно создать новый объект в конструкции select для хранения
результатов, которые нужны для запроса.
Модифицируем код нажатия кнопки:
var queryResults =
from c in customers select new { Value = c.Get_Region(), Id = c.Get_ID() };
foreach (var d in queryResults)
{ string v = d.Value;string id = d.Id;
listBox1.Items.Add(v+" ***"+id);}

Выбор уникальных значений


Еще один тип запросов — запрос select distinct, в котором выполняется поиск
уникальных значений в данных, то есть значений, которые не повторяются.
Потребность в этом возникает очень часто при работе с запросами.
Предположим, что нужно извлечь список регионов из данных заказчиков,
которые применялись в предыдущих примерах. В используемых данных нет
отдельного списка регионов, поэтому необходимо получить уникальный, не
повторяющийся список регионов из списка заказчиков. LINQ предоставляет
метод Distinct(), который облегчает выполнение задачи нахождения данных
подобного рода.
Например,
var queryResults =(from c in customers select c.Get_Region()).Distinct();

Запросы ANY и ALL


Еще один тип запросов состоит в определении того, что какие-то
данные удовлетворяют определенному условию, или в определении того, что
все данные удовлетворяют условию. Например, может потребоваться знать,
что продукта нет на складе (количество равно нулю) или что транзакция
произошла.
LINQ предоставляет булевские методы Any() и All(), которые могут
быстро сообщить, истинно ли некоторое условие для существующих
данных.

18
Например, поместим на форму кнопки для определения того, что есть
заказчики из США и из Азии.
Код, выполняемый при нажатии на ту и другую кнопки, следующий:
private void button2_Click(object sender, EventArgs e)
{ bool anyUSA = customers.Any(с => с.Get_Country() == "USA");
if (anyUSA)
label1.Text = "Некоторые заказчики находятся в США";
else
label1.Text = "Нет заказчиков из США"; }

private void button6_Click(object sender, EventArgs e)


{bool allAsia = customers.All(с => с.Get_Region() == "Asia");
if (allAsia)
label2.Text="Все заказчики находятся в Азии";
else
label2.Text="He все заказчики находятся в Азии"; }

Групповой запрос
Групповой запрос делит данные на группы и позволяет сортировать,
вычислять агрегатные значения и сравнивать группы.
В код события нажатия кнопки Сумма заказов по каждому региону поместим
два запроса и цикл обработки результатов:
var queryResults =
from с in customers group с by с.Get_Region() into eg
select new { TotalSales = eg.Sum(c => c.Get_Sales()), Region = eg.Key };
var orderedResults =
from eg in queryResults
orderby eg.TotalSales descending
select eg;
foreach (var item in orderedResults)
{listBox2.Items.Add(item.TotalSales + "\t: " + item.Region);
Данные в групповом запросе группируются по ключевому полю —
полю, для которого все члены каждой группы имеют одноименное значение
– сумма продаж. В этом примере ключевым полем является Region:
group с by с.Get_Region()
Необходимо вычислить сумму по каждой группе, поэтому выполняется
группирование в новый результирующий набор по имени eg:
group с by с.Get_Region() into eg
В конструкции select определяются новые поля — общая сумма продаж
(вычисляемая путем сылки на результирующий набору eg) и ключевое
значение группы, к которому обращение происходит по специальному полю
Key группы:
select new { TotalSales = eg.Sum(c => с. Get_Sales()), Region =
eg.Key }.это критерий создания каждой группы данных.
19
Желательно упорядочить результат в порядке убывания по полю
TotalSales. Для этого создается второй запрос для упорядочивания
результатов группового запроса. Это стандартный запрос select с
конструкцией orderby; он не использует никаких групповых средств LINQ, за
исключением того, что источник данных для него поступает от предыдущего
группового запроса.
Затем результаты второго запроса выводятся в список.

Вложенная группировка

В следующем примере показано, как создавать вложенные группы в выражении запроса


LINQ. Каждая группа, созданная в соответствии с годом или уровнем обучения, затем
подразделяется на группы на основе имен учащихся.

public void QueryNestedGroups()


{
var queryNestedGroups =
from student in students
group student by student.Year into newGroup1
from newGroup2 in
(from student in newGroup1
group student by student.LastName)
group newGroup2 by newGroup1.Key;

// Three nested foreach loops are required to iterate


// over all elements of a grouped group. Hover the mouse
// cursor over the iteration variables to see their actual type.
foreach (var outerGroup in queryNestedGroups)
{
Console.WriteLine("DataClass.Student Level = {0}", outerGroup.Key);
foreach (var innerGroup in outerGroup)
{
Console.WriteLine("\tNames that begin with: {0}",
innerGroup.Key);
foreach (var innerGroupElement in innerGroup)
{
Console.WriteLine("\t\t{0} {1}", innerGroupElement.LastName,
innerGroupElement.FirstName);
}
}
}
}
/*
Output:
DataClass.Student Level = SecondYear
Names that begin with: Adams
Adams Terry
Names that begin with: Garcia
Garcia Hugo
Names that begin with: Omelchenko
Omelchenko Svetlana
DataClass.Student Level = ThirdYear
Names that begin with: Fakhouri
Fakhouri Fadi
Names that begin with: Garcia
Garcia Debra
Names that begin with: Tucker
Tucker Lance

20
DataClass.Student Level = FirstYear
Names that begin with: Feng
Feng Hanying
Names that begin with: Mortensen
Mortensen Sven
Names that begin with: Tucker
Tucker Michael
DataClass.Student Level = FourthYear
Names that begin with: Garcia
Garcia Cesar
Names that begin with: O'Donnell
O'Donnell Claire
Names that begin with: Zabokritski
Zabokritski Eugene
*/

Обратите внимание на то, что для выполнения итерации по внутренним


элементам вложенной группы необходимы три вложенных цикла foreach.

Take и Skip
В ряде случаев нужно найти несколько первых данных из запроса. Их
количество заранее неизвестно, поэтому нельзя использовать условие where
для их поиска.
LINQ-операция такого запроса представлена методом Take (), который
учитывает первые n результатов при выводе запроса. При практическом
применении это может быть скомбинировано с orderby для получения первых
n результатов.
Противоположностью Take () является метод Skip (), который
пропускает первые n результатов, возвращая остальные.
Предположим, что добавлено две кнопки – выбор первого десятка
продаж с наибольшими суммами и выдача всех продаж, кроме первого
десятка.
Запрос:
var queryResults =
from c in customers
orderby c.Get_Sales() descending select new {Dan=c.Info()};
Опишем два цикла обработки результатов, один с использованием Take
(), а другой — с применением Skip ():
foreach (var item in queryResults.Take(10))
{ listBox2.Items.Add(item.Dan); }
foreach (var item in queryResults.Skip(10))
{ listBox2.Items.Add(item.Dan);}

First и FirstOrDefault
Предположим, что требуется найти заказчика из Африки в наборе
данных.
LINQ предоставляет эту возможность через метод First(), который
возвращает первый элемент результирующего набора, отвечающий заданным
критериям. Если нет ни одного заказчика, например, из Антарктиды, то LINQ
21
предоставляет метод для обработки такого случая без дополнительного кода
обработки ошибок: FirstOrDefault():
var queryResults = from c in customers
select new{ID=c.Get_ID(),Region=c.Get_Region()} ;
listBox1.Items.Add(queryResults.First(c => c.Region == "Africa"));
listBox1.Items.Add(queryResults.FirstOrDefault(c => c.Region ==
"Antarctica"));

Операции с множествами
В LINQ операциями с множествами называют операции запроса,
образующие результирующий набор, основанный на наличии или отсутствии
эквивалентных элементов внутри одной или разных коллекций (или
множеств).
В следующем разделе перечислены методы стандартных операторов
запросов, выполняющие операции с множествами.

Имя
Описание
метода

Distinct Удаляет повторяющиеся значения из коллекции.

Возвращает разность множеств — набор элементов одной коллекции, которые


Except
не содержатся во второй коллекции.

Возвращает пересечение множеств — набор элементов, содержащихся в


Intersect
каждой из коллекций.

Возвращает объединение множеств — набор уникальных элементов,


Union
содержащихся в любой из коллекций.

Сравнение операций над множествами

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

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

22
последовательности, которые не содержатся во второй входной
последовательности.

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

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

LINQ поддерживает стандартные операции множеств, такие как Union


() и Intersect () - объединентие и пересечение, оперирующие результатами
запроса.
В примере добавим список заказов и используем операции над
множествами для сопоставления заказов с существующими закшзчиками.
Создадим новый класс:
class Order
{ string ID; decimal Amount;
public string Get_ID() { return ID; }
public decimal Get_Amount() { return Amount; }
public Order(string ID, decimal Amount)
{ this.ID = ID; this.Amount = Amount;}
public string Info()
{ return "ID: " + ID + " Сумма: " + Convert.ToString(Amount); }
Создадим коллекцию объектов класса Order и поместим в нее экземпляры:
List <Order> orders = new List<Order>();

23
orders.Add(new Order("P", 100 ));
orders.Add(new Order ("Q", 200 ));
orders.Add(new Order ("R",300));
orders.Add(new Order ("S",400));
orders.Add(new Order ("T",500));
orders.Add(new Order ("MU", 600));
orders.Add(new Order ("V",700));
orders.Add(new Order ("MW",800));
orders.Add(new Order ("X",900));
orders.Add(new Order ("Y",1000));
orders.Add(new Order ("Z",1100));
Создадим запросы по каждой коллекции:
var customerlDs =
from с in customers
select с.Get_ID();
var orderlDs =
from o in orders
select o.Get_ID();
Далее создадим три запроса для выполнения соответственно операций
пересечения, исключения и объединения:
var customersWithOrders = customerlDs.Intersect(orderlDs);
listBox1.Items.Clear();
foreach (var item in customersWithOrders)
{listBox1.Items.Add(item); }

var ordersNoCustomers = orderlDs.Except(customerlDs);


listBox1.Items.Clear();
foreach (var item in ordersNoCustomers)
{ listBox1.Items.Add(item);}}

var allCustomerOrderlDs = customerlDs.Union(orderlDs);


listBox1.Items.Clear();
foreach (var item in allCustomerOrderlDs)
{listBox1.Items.Add(item);}}

Соединения
Наборы данных, подобные спискам customers и orders, которые были
только что созданы с общим ключевым полем (ID) позволяют использовать
запрос join, посредством которого можно запросить взаимосвязанные
данные из обоих списков в единственном запросе, объединяя их по значению
ключевого поля.
var queryResults =
from c in customers
join o in orders on c.Get_ID() equals o.Get_ID()
24
select new { ID=c.Get_ID(), City=c.Get_City(), SalesBefore = c.Get_Sales(),
NewOrder = o.Get_Amount(), SalesAfter = c.Get_Sales()+o.Get_Amount() };
listBox1.Items.Clear();
foreach (var item in queryResults)
{listBox1.Items.Add(item.ID+" "+item.City+"
"+Convert.ToString(item.SalesBefore)+" "
+Convert.ToString(item.NewOrder)+" "+Convert.ToString(item.SalesAfter));}
Операнды equals – это сравниваемые значения ключа в двух списках.
Вид формы при выполнении и нажатии кнопки Соединение (рис. 45):

Рис. 45. Выполнение запроса Соединение

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


вспомогательные классы и источники данных.
public class StudentClass
{
#region data
protected enum GradeLevel { FirstYear = 1, SecondYear, ThirdYear,
FourthYear };
protected class Student
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int ID { get; set; }
public GradeLevel Year;
public List<int> ExamScores;
}
25
protected static List<Student> students = new List<Student>
{
new Student {FirstName = "Terry", LastName = "Adams", ID = 120,
Year = GradeLevel.SecondYear,
ExamScores = new List<int>{ 99, 82, 81, 79}},
new Student {FirstName = "Fadi", LastName = "Fakhouri", ID = 116,
Year = GradeLevel.ThirdYear,
ExamScores = new List<int>{ 99, 86, 90, 94}},
new Student {FirstName = "Hanying", LastName = "Feng", ID = 117,
Year = GradeLevel.FirstYear,
ExamScores = new List<int>{ 93, 92, 80, 87}},
new Student {FirstName = "Cesar", LastName = "Garcia", ID = 114,
Year = GradeLevel.FourthYear,
ExamScores = new List<int>{ 97, 89, 85, 82}},
new Student {FirstName = "Debra", LastName = "Garcia", ID = 115,
Year = GradeLevel.ThirdYear,
ExamScores = new List<int>{ 35, 72, 91, 70}},
new Student {FirstName = "Hugo", LastName = "Garcia", ID = 118,
Year = GradeLevel.SecondYear,
ExamScores = new List<int>{ 92, 90, 83, 78}},
new Student {FirstName = "Sven", LastName = "Mortensen", ID = 113,
Year = GradeLevel.FirstYear,
ExamScores = new List<int>{ 88, 94, 65, 91}},
new Student {FirstName = "Claire", LastName = "O'Donnell", ID = 112,
Year = GradeLevel.FourthYear,
ExamScores = new List<int>{ 75, 84, 91, 39}},
new Student {FirstName = "Svetlana", LastName = "Omelchenko", ID = 111,
Year = GradeLevel.SecondYear,
ExamScores = new List<int>{ 97, 92, 81, 60}},
new Student {FirstName = "Lance", LastName = "Tucker", ID = 119,
Year = GradeLevel.ThirdYear,
ExamScores = new List<int>{ 68, 79, 88, 92}},
new Student {FirstName = "Michael", LastName = "Tucker", ID = 122,
Year = GradeLevel.FirstYear,
ExamScores = new List<int>{ 94, 92, 91, 91}},
new Student {FirstName = "Eugene", LastName = "Zabokritski", ID = 121,
Year = GradeLevel.FourthYear,
ExamScores = new List<int>{ 96, 85, 91, 60}}
};
#endregion

//Helper method, used in GroupByRange.


protected static int GetPercentile(Student s)
26
{
double avg = s.ExamScores.Average();
return avg > 0 ? (int)avg / 10 : 0;
}

public void QueryHighScores(int exam, int score)


{
var highScores = from student in students
where student.ExamScores[exam] > score
select new {Name = student.FirstName, Score =
student.ExamScores[exam]};

foreach (var item in highScores)


{
Console.WriteLine("{0,-15}{1}", item.Name, item.Score);
}
}
}

public class Program


{
public static void Main()
{
StudentClass sc = new StudentClass();
sc.QueryHighScores(1, 90);

// Keep the console window open in debug mode.


Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
}

В следующем примере показывается группировка элементов источника с


помощью использования отдельного свойства элемента в качестве ключа
группы. В данном случае в качестве ключа используется string, фамилия
учащегося. Для ключа можно также использовать подстроку. При операции
группирования используется компаратор проверки на равенство,
используемый по умолчанию для данного типа.

Вставьте приведенный ниже метод в класс StudentClass. Измените оператор


вызова в методе Main на sc.GroupBySingleProperty().
27
public void GroupBySingleProperty()
{
Console.WriteLine("Group by a single property in an object:");

// Variable queryLastNames is an IEnumerable<IGrouping<string,


// DataClass.Student>>.
var queryLastNames =
from student in students
group student by student.LastName into newGroup
orderby newGroup.Key
select newGroup;

foreach (var nameGroup in queryLastNames)


{
Console.WriteLine("Key: {0}", nameGroup.Key);
foreach (var student in nameGroup)
{
Console.WriteLine("\t{0}, {1}", student.LastName, student.FirstName);
}
}
}
/* Output:
Group by a single property in an object:
Key: Adams
Adams, Terry
Key: Fakhouri
Fakhouri, Fadi
Key: Feng
Feng, Hanying
Key: Garcia
Garcia, Cesar
Garcia, Debra
Garcia, Hugo
Key: Mortensen
Mortensen, Sven
Key: O'Donnell
O'Donnell, Claire
Key: Omelchenko
Omelchenko, Svetlana
Key: Tucker
Tucker, Lance
Tucker, Michael
Key: Zabokritski
Zabokritski, Eugene
28
*/

В следующем примере показывается группировка элементов источника,


когда в качестве ключа группы используется не свойство объекта. В данном
случае в качестве ключа используется первая буква фамилии учащегося.

Вставьте приведенный ниже метод в класс StudentClass. Измените оператор


вызова в методе Main на sc.GroupBySubstring().

public void GroupBySubstring()


{
Console.WriteLine("\r\nGroup by something other than a property of the
object:");

var queryFirstLetters =
from student in students
group student by student.LastName[0];

foreach (var studentGroup in queryFirstLetters)


{
Console.WriteLine("Key: {0}", studentGroup.Key);
// Nested foreach is required to access group items.
foreach (var student in studentGroup)
{
Console.WriteLine("\t{0}, {1}", student.LastName, student.FirstName);
}
}
}
/* Output:
Group by something other than a property of the object:
Key: A
Adams, Terry
Key: F
Fakhouri, Fadi
Feng, Hanying
Key: G
Garcia, Cesar
Garcia, Debra
Garcia, Hugo
Key: M
Mortensen, Sven
Key: O
O'Donnell, Claire
29
Omelchenko, Svetlana
Key: T
Tucker, Lance
Tucker, Michael
Key: Z
Zabokritski, Eugene
*/

В следующем примере показывается группировка элементов источника с


помощью использования числового диапазона в качестве ключа группы.
Затем запрос передает результаты в анонимный тип, содержащий только имя
и фамилию, а также диапазон процентилей, к которому принадлежит
студент. Использование анонимного типа объясняется тем, что для
отображения результатов не требуется использовать полный объект Student.
GetPercentile — это вспомогательная функция, которая вычисляет процент на
основе средних результатов учащихся. Метод возвращает целое число в
диапазоне от 0 до 10.

C#

//Helper method, used in GroupByRange.


protected static int GetPercentile(Student s)
{
double avg = s.ExamScores.Average();
return avg > 0 ? (int)avg / 10 : 0;
}

Вставьте приведенный ниже метод в класс StudentClass. Измените оператор


вызова в методе Main на sc.GroupByRange().

public void GroupByRange()


{
Console.WriteLine("\r\nGroup by numeric range and project into a new
anonymous type:");

var queryNumericRange =
from student in students
let percentile = GetPercentile(student)
group new { student.FirstName, student.LastName } by percentile into
percentGroup
30
orderby percentGroup.Key
select percentGroup;

// Nested foreach required to iterate over groups and group items.


foreach (var studentGroup in queryNumericRange)
{
Console.WriteLine("Key: {0}", (studentGroup.Key * 10));
foreach (var item in studentGroup)
{
Console.WriteLine("\t{0}, {1}", item.LastName, item.FirstName);
}
}
}
/* Output:
Group by numeric range and project into a new anonymous type:
Key: 60
Garcia, Debra
Key: 70
O'Donnell, Claire
Key: 80
Adams, Terry
Feng, Hanying
Garcia, Cesar
Garcia, Hugo
Mortensen, Sven
Omelchenko, Svetlana
Tucker, Lance
Zabokritski, Eugene
Key: 90
Fakhouri, Fadi
Tucker, Michael
*/

В следующем примере показывается группировка элементов источника с


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

31
Вставьте приведенный ниже метод в класс StudentClass. Измените оператор
вызова в методе Main на sc.GroupByBoolean().

public void GroupByBoolean()


{
Console.WriteLine("\r\nGroup by a Boolean into two groups with string keys");
Console.WriteLine("\"True\" and \"False\" and project into a new anonymous
type:");
var queryGroupByAverages = from student in students
group new { student.FirstName, student.LastName }
by student.ExamScores.Average() > 75 into studentGroup
select studentGroup;

foreach (var studentGroup in queryGroupByAverages)


{
Console.WriteLine("Key: {0}", studentGroup.Key);
foreach (var student in studentGroup)
Console.WriteLine("\t{0} {1}", student.FirstName, student.LastName);
}
}
/* Output:
Group by a Boolean into two groups with string keys
"True" and "False" and project into a new anonymous type:
Key: True
Terry Adams
Fadi Fakhouri
Hanying Feng
Cesar Garcia
Hugo Garcia
Sven Mortensen
Svetlana Omelchenko
Lance Tucker
Michael Tucker
Eugene Zabokritski
Key: False
Debra Garcia
Claire O'Donnell
*/

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


инкапсуляции ключа, содержащего несколько значений. В данном случае в
качестве первого значения ключа используется первая буква фамилии
32
учащегося. Второе значение ключа является логическим и указывает, набрал
ли учащийся более 85 баллов на первом экзамене. Группы можно
сортировать по любому из свойств в данном ключе.

Вставьте приведенный ниже метод в класс StudentClass. Измените оператор


вызова в методе Main на sc.GroupByCompositeKey().

C#

public void GroupByCompositeKey()


{

var queryHighScoreGroups =
from student in students
group student by new { FirstLetter = student.LastName[0],
Score = student.ExamScores[0] > 85 } into studentGroup
orderby studentGroup.Key.FirstLetter
select studentGroup;

Console.WriteLine("\r\nGroup and order by a compound key:");


foreach (var scoreGroup in queryHighScoreGroups)
{
string s = scoreGroup.Key.Score == true ? "more than" : "less than";
Console.WriteLine("Name starts with {0} who scored {1} 85",
scoreGroup.Key.FirstLetter, s);
foreach (var item in scoreGroup)
{
Console.WriteLine("\t{0} {1}", item.FirstName, item.LastName);
}
}
}
/* Output:
Group and order by a compound key:
Name starts with A who scored more than 85
Terry Adams
Name starts with F who scored more than 85
Fadi Fakhouri
Hanying Feng
Name starts with G who scored more than 85
Cesar Garcia
Hugo Garcia
Name starts with G who scored less than 85
Debra Garcia
Name starts with M who scored more than 85
Sven Mortensen
33
Name starts with O who scored less than 85
Claire O'Donnell
Name starts with O who scored more than 85
Svetlana Omelchenko
Name starts with T who scored less than 85
Lance Tucker
Name starts with T who scored more than 85
Michael Tucker
Name starts with Z who scored more than 85
Eugene Zabokritski
*/

В терминах реляционных баз данных внутреннее соединение возвращает результирующий


набор, в котором каждый элемент первой коллекции появляется один раз для каждого
соответствующего элемента второй коллекции. Если для элемента первой коллекции нет
соответствующего элемента, он не появляется в результирующем наборе. Метод Join,
вызываемый предложением join в C#, реализует внутреннее соединение.

В этом разделе демонстрируется выполнение следующих четырех типов внутреннего


соединения.

 Простое внутреннее соединение, которое сопоставляет элементы из двух


источников данных на основании простого ключа.
 Внутреннее соединение, которое сопоставляет элементы из двух источников
данных на основании составного ключа. Составной ключ (ключ, который содержит
несколько значений) позволяет сопоставлять элементы на основании нескольких
свойств.
 При выполнении множественного соединения последовательные операции
соединения применяются друг к другу.
 Внутреннее соединение, которое реализуется с помощью группового соединения.

Пример

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


В следующем примере создаются две коллекции, содержащие объекты двух
определяемых пользователем типов Person и Pet. В C# при запросе используется
предложение join для сопоставления объектов Person с объектами Pet, для которых Owner
является Person. Предложение select в языке C# определяет то, как будут выглядеть
результирующие объекты. В этом примере результирующие объекты — анонимные типы,
состоящие из имени владельца и клички питомца.

C#

Копировать

class Person
{
public string FirstName { get; set; }
34
public string LastName { get; set; }
}

class Pet
{
public string Name { get; set; }
public Person Owner { get; set; }
}

/// <summary>
/// Simple inner join.
/// </summary>
public static void InnerJoinExample()
{
Person magnus = new Person { FirstName = "Magnus", LastName =
"Hedlund" };
Person terry = new Person { FirstName = "Terry", LastName =
"Adams" };
Person charlotte = new Person { FirstName = "Charlotte", LastName
= "Weiss" };
Person arlene = new Person { FirstName = "Arlene", LastName =
"Huff" };
Person rui = new Person { FirstName = "Rui", LastName =
"Raposo" };

Pet barley = new Pet { Name = "Barley", Owner = terry };


Pet boots = new Pet { Name = "Boots", Owner = terry };
Pet whiskers = new Pet { Name = "Whiskers", Owner = charlotte };
Pet bluemoon = new Pet { Name = "Blue Moon", Owner = rui };
Pet daisy = new Pet { Name = "Daisy", Owner = magnus };

// Create two lists.


List<Person> people = new List<Person> { magnus, terry,
charlotte, arlene, rui };
List<Pet> pets = new List<Pet> { barley, boots, whiskers,
bluemoon, daisy };

// Create a collection of person-pet pairs. Each element in the


collection
// is an anonymous type containing both the person's name and
their pet's name.
var query = from person in people
join pet in pets on person equals pet.Owner
select new { OwnerName = person.FirstName, PetName =
pet.Name };

foreach (var ownerAndPet in query)


{
Console.WriteLine("\"{0}\" is owned by {1}",
ownerAndPet.PetName, ownerAndPet.OwnerName);
}
}

// This code produces the following output:


//
// "Daisy" is owned by Magnus
// "Barley" is owned by Terry
// "Boots" is owned by Terry
// "Whiskers" is owned by Charlotte
// "Blue Moon" is owned by Rui

35
Обратите внимание, что объект Person,свойство которого LastName имеет значение
"Huff", не отображается среди результатов, так как не существует объекта Pet, свойство
Pet.Owner которого было бы равно свойству Person.

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


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

В следующем примере списки объектов Employee и Student используются для


определения того, какие работники являются также студентами. Оба этих типа обладают
свойствами FirstName и LastName типа String. Функция, создающая ключи соединения из
элементов каждого списка, возвращает анонимный тип, состоящий из свойств FirstName и
LastName каждого элемента. Операция соединения сравнивает данные составные ключи
на признак равенства и возвращает пары объектов из обоих списков, у которых совпадают
имя и фамилия.

C#

Копировать

class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int EmployeeID { get; set; }
}

class Student
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int StudentID { get; set; }
}

/// <summary>
/// Performs a join operation using a composite key.
/// </summary>
public static void CompositeKeyJoinExample()
{
// Create a list of employees.
List<Employee> employees = new List<Employee> {
new Employee { FirstName = "Terry", LastName = "Adams",
EmployeeID = 522459 },
new Employee { FirstName = "Charlotte", LastName = "Weiss",
EmployeeID = 204467 },
new Employee { FirstName = "Magnus", LastName = "Hedland",
EmployeeID = 866200 },
new Employee { FirstName = "Vernette", LastName = "Price",
EmployeeID = 437139 } };

// Create a list of students.


List<Student> students = new List<Student> {
new Student { FirstName = "Vernette", LastName = "Price",
StudentID = 9562 },
36
new Student { FirstName = "Terry", LastName = "Earls",
StudentID = 9870 },
new Student { FirstName = "Terry", LastName = "Adams",
StudentID = 9913 } };

// Join the two data sources based on a composite key consisting


of first and last name,
// to determine which employees are also students.
IEnumerable<string> query = from employee in employees
join student in students
on new { employee.FirstName,
employee.LastName }
equals new { student.FirstName,
student.LastName }
select employee.FirstName + " " +
employee.LastName;

Console.WriteLine("The following people are both employees and


students:");
foreach (string name in query)
Console.WriteLine(name);
}

// This code produces the following output:


//
// The following people are both employees and students:
// Terry Adams
// Vernette Price

Пример множественного соединения


Операции соединения в любом количестве могут быть добавлены друг к другу для
выполнения группового соединения. Каждое предложение join в C# сопоставляет
указанный источник данных с результатами предыдущего соединения.

В следующем примере создаются три коллекции: список объектов Person, список


объектов Cat и список объектов Dog.

Первое предложение join в C# проверяет соответствие людей и кошек на основании


сопоставления объекта Person со свойством Cat.Owner. Возвращается последовательность
анонимных типов, содержащая объекты Person и свойства Cat.Name.

Второе предложение join в C# сопоставляет анонимные типы, возвращенные первым


соединением, с объектами Dog в предоставленном списке собак на основании составного
ключа, состоящего из свойства Owner типа Person, и первой буквы клички животного.
Возвращается последовательность анонимных типов, содержащая свойства Cat.Name и
Dog.Name каждой совпадающей пары. Так как это внутреннее соединение, возвращаются
только объекты из первого источника данных, для которых найдено соответствии во
втором источнике данных.

C#

Копировать

class Person
{
37
public string FirstName { get; set; }
public string LastName { get; set; }
}

class Pet
{
public string Name { get; set; }
public Person Owner { get; set; }
}

class Cat : Pet


{ }

class Dog : Pet


{ }

public static void MultipleJoinExample()


{
Person magnus = new Person { FirstName = "Magnus", LastName =
"Hedlund" };
Person terry = new Person { FirstName = "Terry", LastName =
"Adams" };
Person charlotte = new Person { FirstName = "Charlotte", LastName
= "Weiss" };
Person arlene = new Person { FirstName = "Arlene", LastName =
"Huff" };
Person rui = new Person { FirstName = "Rui", LastName =
"Raposo" };
Person phyllis = new Person { FirstName = "Phyllis", LastName =
"Harris" };

Cat barley = new Cat { Name = "Barley", Owner = terry };


Cat boots = new Cat { Name = "Boots", Owner = terry };
Cat whiskers = new Cat { Name = "Whiskers", Owner = charlotte };
Cat bluemoon = new Cat { Name = "Blue Moon", Owner = rui };
Cat daisy = new Cat { Name = "Daisy", Owner = magnus };

Dog fourwheeldrive = new Dog { Name = "Four Wheel Drive", Owner =


phyllis };
Dog duke = new Dog { Name = "Duke", Owner = magnus };
Dog denim = new Dog { Name = "Denim", Owner = terry };
Dog wiley = new Dog { Name = "Wiley", Owner = charlotte };
Dog snoopy = new Dog { Name = "Snoopy", Owner = rui };
Dog snickers = new Dog { Name = "Snickers", Owner = arlene };

// Create three lists.


List<Person> people =
new List<Person> { magnus, terry, charlotte, arlene, rui,
phyllis };
List<Cat> cats =
new List<Cat> { barley, boots, whiskers, bluemoon, daisy };
List<Dog> dogs =
new List<Dog> { fourwheeldrive, duke, denim, wiley, snoopy,
snickers };

// The first join matches Person and Cat.Owner from the list of
people and
// cats, based on a common Person. The second join matches dogs
whose names start
// with the same letter as the cats that have the same owner.
var query = from person in people
join cat in cats on person equals cat.Owner
join dog in dogs on

38
new { Owner = person, Letter = cat.Name.Substring(0,
1) }
equals new { dog.Owner, Letter =
dog.Name.Substring(0, 1) }
select new { CatName = cat.Name, DogName =
dog.Name };

foreach (var obj in query)


{
Console.WriteLine(
"The cat \"{0}\" shares a house, and the first letter of
their name, with \"{1}\".",
obj.CatName, obj.DogName);
}
}

// This code produces the following output:


//
// The cat "Daisy" shares a house, and the first letter of their
name, with "Duke".
// The cat "Whiskers" shares a house, and the first letter of their
name, with "Wiley".

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


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

В query1 выполняется групповое соединение списка объектов Person со списком объектов


Pet на основании сопоставления объекта Person и свойства Pet.Owner. Групповое
соединение создает коллекцию промежуточных групп, в которой каждая группа состоит
из объекта Person и последовательности совпадающих объектов Pet.

При добавлении в запрос второго предложения from данная последовательность из


последовательностей объединяется (или выравнивается) в одну более длинную
последовательность. Тип элементов конечной последовательности задается предложением
select. В этом примере тип является анонимным типом, состоящим из свойств
Person.FirstName и Pet.Name каждой соответствующей пары.

Результат query1 аналогичен результирующему набору, полученному при использовании


предложения join для выполнения внутреннего соединения без предложения into.
Переменная query2 демонстрирует этот эквивалентный запрос.

C#

Копировать

class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}

class Pet
{
public string Name { get; set; }

39
public Person Owner { get; set; }
}

/// <summary>
/// Performs an inner join by using GroupJoin().
/// </summary>
public static void InnerGroupJoinExample()
{
Person magnus = new Person { FirstName = "Magnus", LastName =
"Hedlund" };
Person terry = new Person { FirstName = "Terry", LastName =
"Adams" };
Person charlotte = new Person { FirstName = "Charlotte", LastName
= "Weiss" };
Person arlene = new Person { FirstName = "Arlene", LastName =
"Huff" };

Pet barley = new Pet { Name = "Barley", Owner = terry };


Pet boots = new Pet { Name = "Boots", Owner = terry };
Pet whiskers = new Pet { Name = "Whiskers", Owner = charlotte };
Pet bluemoon = new Pet { Name = "Blue Moon", Owner = terry };
Pet daisy = new Pet { Name = "Daisy", Owner = magnus };

// Create two lists.


List<Person> people = new List<Person> { magnus, terry,
charlotte, arlene };
List<Pet> pets = new List<Pet> { barley, boots, whiskers,
bluemoon, daisy };

var query1 = from person in people


join pet in pets on person equals pet.Owner into gj
from subpet in gj
select new { OwnerName = person.FirstName, PetName =
subpet.Name };

Console.WriteLine("Inner join using GroupJoin():");


foreach (var v in query1)
{
Console.WriteLine("{0} - {1}", v.OwnerName, v.PetName);
}

var query2 = from person in people


join pet in pets on person equals pet.Owner
select new { OwnerName = person.FirstName, PetName =
pet.Name };

Console.WriteLine("\nThe equivalent operation using Join():");


foreach (var v in query2)
Console.WriteLine("{0} - {1}", v.OwnerName, v.PetName);
}

// This code produces the following output:


//
// Inner join using GroupJoin():
// Magnus - Daisy
// Terry - Barley
// Terry - Boots
// Terry - Blue Moon
// Charlotte - Whiskers
//
// The equivalent operation using Join():
// Magnus - Daisy
// Terry - Barley

40
// Terry - Boots
// Terry - Blue Moon
// Charlotte - Whiskers

41

Вам также может понравиться