Ответы на экзамен (987689), страница 16
Текст из файла (страница 16)
68.Две разновидности синтаксиса на LINQ
Простейший запрос состоит из одной входной последовательности и одного оператора. Например, мы можем применить оператор where к строковому массиву и извлечь те его элементы, длина которых не меньше четырех символов. Поскольку стандартные операторы запроса реализованы в виде методов расширения, мы можем вызвать Where непосредственно ДЛЯ массива names так, словно это метод экземпляра:
IEnumerable<string> filteredNames = names."Where (n => n.Length >= 4);
Большинство операторов запроса принимает лямбда-выражение в качестве аргумента. Лямбда-выражение помогает направить и сформировать запрос. В нашем примере лямбда-выражение выглядит так:
n => п.Length >= 4
Входной аргумент соответствует входному элементу. В этом случае входной аргумент n представляет имя в массиве и имеет тип string. Оператор where требует, чтобы лямбда-выражение возвращало значение типа bool. Когда оно истинно, элемент должен быть включен в выходную последовательность.
Будем называть такие запросы лямбда-запросами
Чтобы строить более сложные запросы, вы добавляете новые операторы, образуя цепочку. Например, в следующем запросе из массива извлекаются все строки с буквой "а", после чего они сортируются по длине и переводятся в верхний регистр:
string[] names = {"Tom","Dick","Harry","Mary","Jay" }; IEnumerable<string> query = names.Where (n => n.Contains ("a")) .OrderBy (n => n.Length) .Select (n => n.ToUpper());
foreach (string name in query)
Console.Write (name + "|");
//Результат: JAY|MARY|HARRY|
Вот как выглядит тот же запрос в соответствии с синтаксисом, облегчающим его восприятие:
string[] names = {
"Tom","Dick","Harry","Mary","Jay" };
IEnumerable<string> query =
from n in names
where n.Contains ("a") // Фильтровать элементы
orderby n.Length // Сортировать элементы
select n.ToUpper(); // Проецировать каждый элемент
foreach (string name in query)
Console.Write (name + "/");
// Результат: JAY/MARY/HARRY/
Запрос с синтаксисом, облегчающим восприятие, всегда начинается с конструкции from и заканчивается конструкцией либо select, либо group. Конструкция from объявляет переменную итерации (в данном случае n). Вы можете считать, что она используется для перебора элементов входной коллекции, аналогично оператору foreach.
Компилятор обрабатывает запросы с синтаксисом, облегчающим восприятие, переводя их в лямбда-синтаксис. Фактически это означает, что все, написанное вами в соответствии с синтаксисом, облегчающим восприятие, могло быть написано и с соблюдением лямбда-синтаксиса. Запрос из нашего примера транслируется в следующий код:
IEnumerable<string> query = names .Where (n => n.Contains ("a")).OrderBy (n => n.Length).Select (n => n.ToUpper());
Затем операторы Where, OrderBy и Select будут откомпилированы по тем же правилам, что и запросы, изначально написанные с соблюдением лямбда-синтаксиса.
Если оператор запроса не поддерживается синтаксисом, облегчающим восприятие, вы можете комбинировать этот синтаксис с лямбда-синтаксисом. Единственное требование, которое при этом выдвигается, — каждая составляющая "понятного" синтаксиса должна быть полной (то есть начинаться с конструкции from и заканчиваться конструкцией select ИЛИ group).
Например:
int count =
(
from name in names
where n.Contains ("a")
select name
).Count();
Бывают ситуации, в которых запросы со смешанным синтаксисом оказываются самыми эффективными в терминах функциональности и простоты. Избегайте оказывать предпочтение какому-то одному из двух вариантов синтаксиса. В противном случае вы не сможете уверенно и безошибочно писать запросы в смешанном синтаксисе!
69.Отложенное выполнение операторов на LINQ
Важной особенностью большинства операторов запроса является тот факт, что они выполняются не тогда, когда сконструированы, а при переборе элементов (то есть когда для соответствующего перечислителя вызывается метод MoveNext):
var numbers = new List<int>(); numbers.Add (1) ; // Построить запрос
IEnumerable<int> query = numbers.Select (n => n * 10);
numbers.Add (2); // "Незаметно" добавить еще один элемент
foreach (int n in query)
Console.Write (n + "|");
// 10|20|
Дополнительный элемент, который мы "тайком" добавили после конструирования запроса, попадает в результат, потому что ни фильтрация, ни сортировка не происходят, пока не начнет выполняться оператор foreach. Это называется отложенным или "ленивым" выполнением. Отложенное выполнение характерно для всех стандартных операторов запроса, кроме
∙••••• операторов, возвращающих один элемент или скалярное значение, таких как First или Count;
∙••••• операторов преобразования типа: ToArray, ToList, ToDictionary, ToLookup
Эти операторы приводят к немедленному выполнению запроса, потому что у возвращаемых ими результатов нет механизма, который обеспечивал бы отложенное выполнение. Например, метод Count возвращает целое число, которое затем никак не "перебирается". Следующий запрос выполняется немедленно:
int matches = numbers.Where (n => n < 2).Count(); // 1
Отложенное выполнение играет важную роль, потому что оно отделяет конструирование запроса от его выполнения. Это позволяет вам конструировать запрос за несколько шагов, а также делать LINQ-запросы к SQL.
70.Операторы Select, Where, Take, Skip, TakeWhile, SkipWhile
Проецирование
Оператор Select
Аргумент | Тип |
Исходная последовательность | IEnumerable<TSource> |
Селектор результата | TSource => TResult |
Синтаксис,облегчающий восприятие
select выражение_проецирования
Описание
От оператора select вы всегда получаете то количество элементов, которое было на входе. Однако каждый элемент может быть как угодно преобразован лямбда-функцией.
В следующем коде выбираются названия всех шрифтов, установленных на компьютере (из пространства имен System. Drawing):
IEnumerable<string> query =
from f in FontFamily.Families
select f.Name;
foreach (string name in query)
Console.WriteLine (name);
В этом примере предложение select преобразует объект FontFamily в его имя. Вот лямбда-эквивалент этого кода:
IEnumerable<string> query = FontFamily.Families.Select (f => f.Name);
Фильтрация
Метод | Описание |
Where | Возвращает подмножество элементов, удовлетворяющих данному условию |
Take | Возвращает первые count элементов и игнорирует остальные |
Skip | Игнорирует первые count элементов и возвращает остальные |
TakeWhile | Возвращает элементы из входной последовательности, пока предикат равен true. |
SkipWhile | Игнорирует элементы из входной последовательности, пока предикат равен true, а затем возвращает остальные |
Distinct | Возвращает коллекцию, из которой исключены повторяющиеся элементы |
Каким бы методом фильтрации вы ни пользовались, вы всегда получите либо то же самое, либо меньшее количество элементов по сравнению с оригинальной последовательностью. Вы никогда не получите больше! Кроме того, элементы на выходе идентичны оригинальным; они никак не преобразуются.
Оператор Where
Аргумент | Тип |
Исходная последовательность | IEnumerable<TSource> |
Предикат | TSource => bool |
Синтаксис,облегчающий восприятие
where булево_выражение
Оператор where возвращает элементы входной последовательности, удовлетворяющие заданному предикату.
Например:
string[] names = {"Tom","Dick","Harry","Mary", "Jay" }; IEnumerable<string> query = names.Where (name => name.EndsWith ("y"));
// Результат: { "Harry", "Mary", "Jay" }
В синтаксисе, облегчающем восприятие, этот запрос выглядит так:
IEnumerable<string> query =
from n in names
where n.EndsWith("y")
select n;
Конструкция where может появиться в запросе более одного раза, причем она может чередоваться с конструкцией let:
from n in names
where n.Length > 3
let u = n.ToUpper()
where u.EndsWith ("Y")
select u;
// Результат: { "HARRY", "MARY"}
Для таких операторов действуют стандартные правила относительно области видимости, принятые в С#. Иными словами, вы не можете ссылаться на переменную до ее появления с помощью переменной итерации или конструкции let.
Операторы Take и Skip
Аргумент | Тип |
Исходная последовательность | IEnumerable<TSource> |
Количество элементов, которые необходимо возвратить или пропустить | int |
Оператор Take возвращает первые n элементов и игнорирует остальные, а оператор skip игнорирует первые n элементов и возвращает остальные. Эти два метода полезны в сочетании, когда вы реализуете веб-страницу, позволяющую пользователю просматривать большое количество записей, соответствующих его запросу. Предположим, например, пользователь ищет в базе данных книги, в названиях которых встречается слово "mercury" (ртуть), и таковых оказывается 100 штук.
Следующий запрос возвращает первые 20 названий:
IQueryable<Book> query = dataContext.Books.Where (b => b.Title.Contains("mercury")).OrderBy(b => b.Title).Take (20);
А этот запрос возвращает названия с 21 по 40:
IQueryable<Book> query - dataContext.Books.Where (b => b.Title.Contains("mercury")).OrderBy(b => b.Title).Skip(20).Take(20);
Операторы TakeWhile и SkipWhile
Аргумент | Тип |
Исходная последовательность | IEnumerable<TSource> |
Предикат | TSource => bool |
Оператор TakeWhile перебирает элементы входной последовательности и возвращает каждый из них, пока заданный предикат имеет значение true. После этого все остальные элементы игнорируются:
int[] numbers = { 3, 5, 2, 234, 4, 1 };
var takeWhileSmall = numbers.TakeWhile (n => n < 100);