Текст книги "Полное руководство. С# 4.0"
Автор книги: Герберт Шилдт
Жанр:
Программирование
сообщить о нарушении
Текущая страница: 39 (всего у книги 58 страниц)
ГЛАВА 19. LINQ
Без сомнения, LINQ относится к одним из самых инте ресных средств языка С#. Эти средства были внедрены в версии C# 3.0 и явились едва ли не самым главным его дополнением, которое состояло не только во внесении совершенно нового элемента в синтаксис С#, добавлении нескольких ключевых слов и предоставлении больших воз можностей, но и в значительном расширении рамок дан ного языка программирования и круга задач, которые он позволяет решать. Проще говоря, внедрение LINQ стало поворотным моментом в истории развития С#.
Аббревиатура LINQ означает Language-Integrated Query, т.е. язык интегрированных запросов. Это понятие охватывает ряд средств, позволяющих извлекать информацию из ис точника данных. Как вам должно быть известно, извлечение данных составляет важную часть многих программ. Напри мер, программа может получать информацию из списка заказчиков, искать информацию в каталоге продукции или получать доступ к учетному документу, заведенному на ра ботника. Как правило, такая информация хранится в базе данных, существующей отдельно от приложения. Так, ката лог продукции может храниться в реляционной базе дан ных. В прошлом для взаимодействия с такой базой данных приходилось формировать запросы на языке структуриро ванных запросов (SQL). А для доступа к другим источникам данных, например в формате XML, требовался отдельный подход. Следовательно, до версии 3.0 поддержка подобных запросов в C# отсутствовала. Но это положение изменилось после внедрения LINQ.
LINQ дополняет C# средствами, позволяющими форми ровать запросы для любого LINQ-совместимого источника данных. При этом синтаксис, используемый для формирования запросов, остается не изменным, независимо от типа источника данных. Это, в частности, означает, что син таксис, требующийся для формирования запроса к реляционной базе данных, прак тически ничем не отличается от синтаксиса запроса данных, хранящихся в массиве. Для этой цели теперь не нужно прибегать к средствам SQL или другого внешнего по отношению к C# механизма извлечения данных из источника. Возможности формиро вать запросы отныне полностью интегрированы в язык С#.
Помимо SQL, LINQ можно использовать вместе с XML-файлами и наборами дан ных ADO.NET Dataset. Не менее важным является применение LINQ вместе с масси вами и коллекциями в C# (подробнее рассматриваемыми в главе 25). Таким образом, средства LINQ предоставляют, в целом, единообразный доступ к данным. И хотя такой принцип уже сам по себе является весьма эффективным и новаторским, преимуще ства LINQ этим не ограничиваются. LINQ предлагает осмыслить иначе и подойти по– другому к решению многих видов задач программирования, помимо традиционной организации доступа к базам данных. И в конечном итоге многие решения могут быть выработаны на основе LINQ.
LINQ поддерживается целым рядом взаимосвязанных средств, включая внедренный в C# синтаксис запросов, лямбда-выражения, анонимные типы и методы расширения. О лямбда-выражениях речь уже шла в главе 15, а остальные средства рассматриваются в этой главе.
ПРИМЕЧАНИЕ LINQ в C# – это, по сути, язык в языке. Поэтому предмет рассмотрения LINQ довольно обширен и включает в себя многие средства, возможности и альтернативы. Несмотря на то что в этой главе дается подробное описание средств LINQ, рассмотреть здесь все их воз можности, особенности и области применения просто невозможно. Для этого потребовалась бы отдельная книга. В связи с этим в настоящей главе основное внимание уделяется глав ным элементам LINQ, применение которых демонстрируется на многочисленных примерах. А в долгосрочной перспективе LINQ представляет собой подсистему, которую придется изу чать самостоятельно и достаточно подробно. Основы LINQ
В основу LINQ положено понятие запроса, в котором определяется информация, получаемая из источника данных. Например, запрос списка рассылки почтовых со общений заказчикам может потребовать предоставления адресов всех заказчиков, проживающих в конкретном городе; запрос базы данных товарных запасов – список товаров, запасы которых исчерпались на складе; а запрос журнала, регистрирующего интенсивность использования Интерента, – список наиболее часто посещаемых веб сайтов. И хотя все эти запросы отличаются в деталях, их можно выразить, используя одни и те же синтаксические элементы LINQ.
Как только запрос будет сформирован, его можно выполнить. Это делается, в част ности, в цикле foreach. В результате выполнения запроса выводятся его результаты. Поэтому использование запроса может быть разделено на две главные стадии. На пер вой стадии запрос формируется, а на второй – выполняется. Таким образом, при фор мировании запроса определяется, что именно следует извлечь из источника данных. А при выполнении запроса выводятся конкретные результаты.
Для обращения к источнику данных по запросу, сформированному средствами LINQ, в этом источнике должен быть реализован интерфейс IEnumerable. Он име ет две формы: обобщенную и необобщенную. Как правило, работать с источником данных легче, если в нем реализуется обобщенная форма IEnumerable, где Т обо значает обобщенный тип перечисляемых данных. Здесь и далее предполагается, что в источнике данных реализуется форма интерфейса IEnumerable. Этот интерфейс объявляется в пространстве имен System.Collections.Generic. Класс, в котором реализуется форма интерфейса IEnumerable, поддерживает перечисление, а это означает, что его содержимое может быть получено по очереди или в определенном порядке. Форма интерфейса IEnumerable поддерживается всеми массивами в С#. Поэтому на примере массивов можно наглядно продемонстрировать основные прин ципы работы LINQ. Следует, однако, иметь в виду, что применение LINQ не ограни чивается одними массивами. Простой запрос
А теперь самое время обратиться к простому примеру использования LINQ. В при веденной ниже программе используется запрос для получения положительных значе ний, содержащихся в массиве целых значений. // Сформировать простой запрос LINQ. using System; using System.Linq; class SimpQuery { static void Main() { int[] nums = { 1, -2, 3, 0, -4, 5 }; // Сформировать простой запрос на получение только положительных значений. var posNums = from n in nums where n > 0 select n; Console.Write("Положительные значения из массива nums: "); // Выполнить запрос и отобразить его результаты. foreach(int i in posNums) Console.Write(i + " "); Console.WriteLine(); } }
Эта программа дает следующий результат. Положительные значения из массива nums: 1 3 5
Как видите, в конечном итоге отображаются только положительные значения, хра нящиеся в массиве nums. Несмотря на всю свою простоту, этот пример наглядно де монстрирует основные возможности LINQ. Поэтому рассмотрим его более подробно.
Прежде всего обратите внимание на применение в данном примере программы следующего оператора. using System.Linq;
Для применения средств LINQ в исходный текст программы следует включить про странство имен System.Linq.
Затем в программе объявляется массив nums типа int. Все массивы в C# неявным образом преобразуются в форму интерфейса IEnumerable. Благодаря этому лю бой массив в C# может служить в качестве источника данных, извлекаемых по запросу LINQ.
Далее объявляется запрос, по которому из массива nums извлекаются элементы только с положительными значениями. var posNums = from n in nums where n > 0 select n;
Переменная posNums называется переменной запроса. В ней хранится ссылка на ряд правил, определяемых в запросе. Обратите внимание на применение ключевого слова var для объявления переменной posNums неявным образом. Как вам должно быть уже известно, благодаря этому переменная posNums становится неявно типизирован ной. Такими переменными удобно пользоваться в запросах, хотя их тип можно объя вить и явным образом (это должна быть одна из форм интерфейса IEnumerable). Объявляемой переменной posNums в итоге присваивается выражение запроса.
Все запросы начинаются с оператора from, определяющего два элемента. Первым из них является переменная диапазона, принимающая элементы из источника данных. В рассматриваемом здесь примере эту роль выполняет переменная n. Вторым элемен том является источник данных (в данном случае – массив nums). Тип переменной диа пазона выводится из источника данных. Поэтому переменная n относится к типу int. Ниже приведена общая форма оператора from. from переменная_диапазона in источник_данных
Далее следует оператор where, обозначающий условие, которому должен удо влетворять элемент в источнике данных, чтобы его можно было получить по запросу. Ниже приведена общая форма синтаксиса оператора where. where булево_выражение
В этой форме булево_выражение должно давать результат типа bool. Такое вы ражение иначе называется предикатом. В запросе можно указывать несколько операто ров where. В данном примере программы используется следующий оператор where. where n > 0
Этот оператор будет давать истинный результат только для тех элементов массива, значения которых оказываются больше нуля. Выражение n > 0 будет вычисляться для каждого из n элементов массива n при выполнении запроса. В итоге будут по лучены только те значения, которые удовлетворяют этому условию. Иными словами, оператор where выполняет роль своеобразного фильтра, отбирая лишь определенные элементы.
Все запросы оканчиваются оператором select или group. В данном примере используется оператор select, точно определяющий, что именно должно быть по лучено по запросу. В таких простых примерах запросов, как рассматриваемый здесь, выбирается конкретное значение диапазона. Поэтому по данному запросу возвраща ются только те целые значения, которые удовлетворяют условию, указанному в опера торе where. В более сложных запросах можно дополнительно уточнять, что именно следует выбирать. Например, по запросу списка рассылки может быть получена лишь фамилия адресата вместо его полного адреса. Обратите внимание на то, что оператор select завершается точкой с запятой, поскольку это последний оператор в запросе. А другие его операторы не оканчиваются точкой с запятой.
Итак, переменная запроса posNums создана, но результаты запроса пока еще не получены. Дело в том, что сам запрос определяет лишь ряд конкретных правил, а ре зультаты будут только после выполнения запроса. Кроме того, один и тот же запрос может быть выполнен два раза или больше, причем с разными результатами, если в промежутке между последовательно производимыми попытками выполнить один и тот же запрос изменяется базовый источник данных. Поэтому одного лишь объяв ления переменной запроса posNums совершенно недостаточно для того, чтобы она со держала результаты запроса.
Для выполнения запроса в данном примере программы организуется следующий цикл. foreach(int i in posNums) Console.WriteLine(i + " ");
В этом цикле переменная posNums указывается в качестве коллекции, к которой происходит обращение на каждом шаге цикла. В цикле foreach соблюдаются прави ла, определенные в запросе и доступные по ссылке из переменной posNums. На каж дом шаге цикла возвращается очередной элемент, полученный из массива. Этот про цесс завершается, когда запрашиваемых элементов в массиве больше не обнаружено. В данном примере тип int переменной шага цикла i указывается явно, поскольку по запросу извлекаются элементы именно этого типа. Явное указание типа переменной шага цикла вполне допустимо в тех случаях, когда заранее известен тип значения, вы бираемого по запросу. Но в более сложных случаях оказывается проще, а иногда даже нужно, указывать тип переменной шага цикла неявным образом с помощью ключево го слова var. Неоднократное выполнение запросов
Итак, в запросе определяются правила, по которым извлекаются данные, но этого явно недостаточно для получения результатов, поскольку запрос должен быть выпол нен, причем это может быть сделано несколько раз. Если же в промежутке между по следовательно производимыми попытками выполнить один и тот же запрос источник данных изменяется, то получаемые результаты могут отличаться. Поэтому как только запрос определен, его выполнение будет всегда давать только самые последние резуль таты. Обратимся к конкретному примеру. Ниже приведен другой вариант рассматри ваемой здесь программы, где содержимое массива nums изменяется в промежутке между двумя последовательно производимыми попытками выполнить один и тот же запрос, хранящийся в переменной posNums. // Сформировать простой запрос. using System; using System.Linq; using System.Collections.Generic; class SimpQuery { static void Main() { int[] nums = { 1, -2, 3, 0, -4, 5 }; // Сформировать простой запрос на получение только положительных значений. var posNums = from n in nums where n > 0 select n; Console.Write("Положительные значения из массива nums: "); // Выполнить запрос и отобразить его результаты. foreach(int i in posNums) Console.Write(i + " "); Console.WriteLine(); // Внести изменения в массив nums. Console.WriteLine("nЗадать значение 99 для элемента массива nums[1]."); nums[1] = 99; Consofe.Write("Положительные значения из массива numsn" + "после изменений в нем: "); // Выполнить запрос второй раз. foreach(int i in posNums) Console.Write(i + " "); Console.WriteLine(); } }
Вот к какому результату приводит выполнение этой программы. Положительные значения из массива nums: 1 3 5 Задать значение 99 для элемента массива nums[l]. Положительные значения из массива nums после изменений в нем: 1 99 3 5
Как следует из результата выполнения приведенной выше программы, значение элемента массива nums[1] изменилось с -2 на 99, что и отражают результаты повтор ного выполнения запроса. Этот важный момент следует подчеркнуть особо. Каждая попытка выполнить запрос приносит свои результаты, получаемые при перечислении текущего содержимого источника данных. Поэтому если источник данных претерпева ет изменения, то могут измениться и результаты выполнения запроса. Преимущества такого подхода к обработке запросов весьма значительны. Так, если по запросу полу чается список необработанных заказов в Интернет-магазине, то при каждой попытке выполнить запрос желательно получить сведения обо всех заказах, включая и только что введенные. Связь между типами данных в запросе
Как показывает предыдущий пример, запрос включает в себя переменные, типы которых связаны друг с другом. К их числу относятся переменная запроса, переменная диапазона и источник данных. Соблюсти соответствие этих типов данных очень важно, но в то же время нелегко – по крайней мере, так кажется на первый взгляд, поэтому данный вопрос заслуживает более пристального внимания.
Тип переменной диапазона должен соответствовать типу элементов, хранящих ся в источнике данных. Следовательно, тип переменной диапазона зависит от типа источника данных. Как правило, тип переменной диапазона может быть выведен средствами С#. Но выводимость типов может быть осуществлена при условии, что в источнике данных реализована форма интерфейса IEnumerable, где Т обозна чает тип элементов в источнике данных. (Как упоминалось выше, форма интерфейса IEnumerable реализуется во всех массивах, как, впрочем, и во многих других ис точниках данных.) Но если в источнике данных реализован необобщенный вариант интерфейса IEnumerable, то тип переменной диапазона придется указывать явно. И это делается в операторе from. Ниже приведен пример явного объявления типа int переменной диапазона n. var posNums = from int n in nums // ...
Очевидно, что явное указание типа здесь не требуется, поскольку все массивы неяв но преобразуются в форму интерфейса IEnumerable, которая позволяет вывести тип переменной диапазона.
Тип объекта, возвращаемого по запросу, представляет собой экземпляр интерфей са IEnumerable, где Т – тип получаемых элементов. Следовательно, тип пере менной запроса должен быть экземпляром интерфейса IEnumerable, а значение Т должно определяться типом значения, указываемым в операторе select. В преды дущих примерах значению Т соответствовал тип int, поскольку переменная п имела тип int. (Как пояснялось выше, переменная n относится к типу int, потому что эле менты именно этого типа хранятся в массиве nums.) С учетом явного указания типа IEnumerable упомянутый выше запрос можно было бы составить следующим образом. IEnumerable
Следует иметь в виду, что тип элемента, выбираемого оператором select, должен соответствовать типу аргумента, передаваемого форме интерфейса IEnumerable, используемой для объявления переменной запроса. Зачастую при объявлении пере менных запроса используется ключевое слово var вместо явного указания ее типа, поскольку это дает компилятору возможность самому вывести соответствующий тип данной переменной из оператора select. Как будет показано далее в этой главе, та кой подход оказывается особенно удобным в тех случаях, когда оператор select воз вращает из источника данных нечто более существенное, чем отдельный элемент. Когда запрос выполняется в цикле foreach, тип переменной шага цикла должен быть таким же, как и тип переменной диапазона. В предыдущих примерах тип этой переменной указывался явно как int. Но имеется и другая возможность: предоставить компилятору самому вывести тип данной переменной, и для этого достаточно указать ее тип как var. Как будет показано далее в этой главе, ключевое слово var приходится использовать и в тех случаях, когда тип данных просто неизвестен. Общая форма запроса
У всех запросов имеется общая форма, основывающаяся на ряде приведенных ниже контекстно-зависимых ключевых слов. Ascending by descending equals from group in into join let on orderby select where
Среди них лишь приведенные ниже ключевые слова используются в начале опера торов запроса. from group join let orderby select where
Запрос должен начинаться с ключевого слова from и оканчиваться ключевым сло вом select или group. Оператор select определяет тип значения, перечисляемого по запросу, а оператор group возвращает данные группами, причем каждая группа может перечисляться по отдельности. Как следует из приведенных выше примеров, в операторе where указываются критерии, которым должен удовлетворять искомый элемент, чтобы быть полученным по запросу. А остальные операторы позволяют уточ нить запрос. Все они рассматриваются далее по порядку. Отбор запрашиваемых значений с помощью оператора where
Как пояснялось выше, оператор where служит для отбора данных, возвращаемых по запросу. В предыдущих примерах этот оператор был продемонстрирован в своей простейшей форме, в которой для отбора данных используется единственное усло вие. Однако для более тщательного отбора данных можно задать несколько условий и, в частности, в нескольких операторах where. В качестве примера рассмотрим сле дующую программу, в которой из массива выводятся только те значения, которые по ложительны и меньше 10. // Использовать несколько операторов where. using System; using System.Linq; class TwoWheres { static void Main() { int[] nums = { 1, -2, 3, -3, 0, -8, 12, 19, 6, 9, 10 }; // Сформировать запрос на получение положительных значений меньше 10. var posNums = from n in nums where n > 0 where n < 10 select n; Console.Write("Положительные значения меньше 10: "); // Выполнить запрос и вывести его результаты. foreach(int i in posNums) Console.Write (i + " "); Console.WriteLine(); } }
Эта программа дает следующий результат. Положительные значения меньше 10: 1 3 6 9
Как видите, по данному запросу извлекаются только положительные значения меньше. 10. Этот результат достигается благодаря двум следующим операторам where. where n > 0 where n < 10
Условие в первом операторе where требует, чтобы элемент массива был больше нуля. А условие во втором операторе where требует, чтобы элемент массива был мень ше 10. Следовательно, запрашиваемый элемент массива должен находиться в пределах от 1 до 9 (включительно), чтобы удовлетворять обоим условиям.
В таком применении двух операторов where для отбора данных нет ничего дурно го, но аналогичного эффекта можно добиться с помощью более компактно составлен ного условия в единственном операторе where. Ниже приведен тот же самый запрос, переформированный по этому принципу. var posNums = from n in nums where n > 0 ts n < 10 select n;
Как правило, в условии оператора where разрешается использовать любое допу стимое в C# выражение, дающее булев результат. Например, в приведенной ниже про грамме определяется массив символьных строк. В ряде этих строк содержатся адреса Интернета. По запросу в переменой netAddrs извлекаются только те строки, которые содержат более четырех символов и оканчиваются на ".net". Следовательно, по дан ному запросу обнаруживаются строки, содержащие адреса Интернета с именем .net домена самого верхнего уровня. // Продемонстрировать применение еще одного оператора where. using System; using System.Linq; class WhereDemo2 { static void Main() { string[] strs = { ".com", ".net", "hsNameA.com", "hsNameB.net", "test", ".network", "hsNameC.net", "hsNameD.com" }; // Сформировать запрос на получение адресов // Интернета, оканчивающихся на .net. var netAddrs = from addr in strs where addr.Length > 4 && addr.EndsWithC.net", StringComparison.Ordinal) select addr; // Выполнить запрос и вывести его результаты. foreach(var str in netAddrs) Console.WriteLine(str); } }
Вот к какому результату приводит выполнение этой программы. hsNameB.net hsNameC.net
Обратите внимание на то, что в операторе where данной программы используется один из методов обработки символьных строк под названием EndsWith(). Он возвра щает логическое значение true, если вызывающая его строка оканчивается последова тельностью символов, указываемой в качестве аргумента этого метода. Сортировка результатов запроса с помощью оператора orderby
Зачастую результаты запроса требуют сортировки. Допустим, что требуется полу чить список просроченных счетов по порядку остатка на счету: от самого большого до самого малого или же список имен заказчиков в алфавитном порядке. Независимо от преследуемой цели, результаты запроса можно очень легко отсортировать, используя такое средство LINQ, как оператор orderby.
Оператор orderby можно использовать для сортировки результатов запроса по одному или нескольким критериям. Рассмотрим для начала самый простой случай сортировки по одному элементу. Ниже приведена общая форма оператора orderby для сортировки результатов запроса по одному критерию: orderby элемент порядок
где элемент обозначает конкретный элемент, по которому проводится сортировка. Это может быть весь элемент, хранящийся в источнике данных, или только часть одно го поля в данном элементе. А порядок обозначает порядок сортировки по нарастаю щей или убывающей с обязательным добавлением ключевого слова ascending или descending соответственно. По умолчанию сортировка проводится по нарастающей, и поэтому ключевое слово ascending, как правило, не указывается.
Ниже приведен пример программы, в которой оператор orderby используется для извлечения значений из массива типа int по нарастающей. // Продемонстрировать применение оператора orderby. using System; using System.Linq; class OrderbyDemo { static void Main() { int[] nums = { 10, -19, 4, 7, 2, -5, 0 }; // Сформировать запрос на получение значений в отсортированном порядке. var posNums = from n in nums orderby n select n; Console.Write("Значения по нарастающей: "); // Выполнить запрос и вывести его результаты. foreach(int i in posNums) Console.Write(i + " "); Console.WriteLine(); } }
При выполнении этой программы получается следующий результат. Значения по нарастающей: -19 -5 0 2 4 7 10
Для того чтобы изменить порядок сортировки по нарастающей на сортировку по убывающей, достаточно указать ключевое слово descending, как показано ниже. var posNums = from n in nums orderby n descending select n;
Попробовав выполнить этот запрос, вы получите значения в обратном порядке. Зачастую сортировка результатов запроса проводится по единственному критерию. Тем не менее для сортировки по нескольким критериям служит приведенная ниже форма оператора orderby. orderby элемент_А направление, элемент_В направление, элемент_С направление, ...
В данной форме элементА обозначает конкретный элемент, по которому прово дится основная сортировка; элементВ – элемент, по которому производится сорти ровка каждой группы эквивалентных элементов; элемент_С – элемент, по которому производится сортировка всех этих групп, и т.д. Таким образом, каждый последующий элемент обозначает дополнительный критерий сортировки. Во всех этих критериях указывать направление сортировки необязательно, но по умолчанию сортировка про водится по нарастающей. Ниже приведен пример программы, в которой сортировка информации о банковских счетах осуществляется по трем критериям: фамилии, име ни и остатку на счете. // Сортировать результаты запроса по нескольким // критериям, используя оператор orderby. using System; using System.Linq; class Account { public string FirstName { get; private set; } public string LastName { get; private set; } public double Balance { get; private set; } public string AccountNumber { get; private set; } public Account(string fn, string ln, string accnum, double b) { FirstName = fn; LastName = ln; AccountNumber = accnum; Balance = b; } } class OrderbyDemo { static void Main() { // Сформировать исходные данные. Account[] accounts = { new Account(«Том», «Смит», «132CK», 100.23), new Account(«Том», «Смит», «132CD», 10000.00), new Account(«Ральф», «Джонс», «436CD», 1923.85), new Account(«Ральф», «Джонс», «454MM», 987.132), new Account(«Тед», «Краммер», «897CD», 3223.19), new Account(«Ральф», «Джонс», «434CK», -123.32), new Account(«Capa», «Смит», «543MM», 5017.40), new Account(«Capa», «Смит», «547CD», 34955.79), new Account(«Capa», «Смит», «843CK», 345.00), new Account(«Альберт», «Смит», «445CK», -213.67), new Account(«Бетти», «Краммер»,"968MM",5146.67), new Account(«Карл», «Смит», «078CD», 15345.99), new Account(«Дженни», «Джонс», «108CK», 10.98) }; // Сформировать запрос на получение сведений о // банковских счетах в отсортированном порядке. // Отсортировать эти сведения сначала по имени, затем // по фамилии и, наконец, по остатку на счете, var accInfo = from асе in accounts orderby acc.LastName, acc.FirstName, acc.Balance select acc; Console.WriteLine("Счета в отсортированном порядке: "); string str = ""; // Выполнить запрос и вывести его результаты. foreach(Account acc in accInfo) { if(str != acc.FirstName) { Console.WriteLine(); str = acc.FirstName; } Console.WriteLine(«{0}, {1}tHoмep счета: {2}, {3,10:C}», acc.LastName, acc.FirstName, acc. AccountNumber, acc.Balance); } Console.WriteLine(); } }
Ниже приведен результат выполнения этой программы. Счета в отсортированном порядке: Джонс, Дженни Номер счета: 108СК, $10.98 Джонс, Ральф Номер счета: 434СК, ($123.32) Джонс, Ральф Номер счета: 454ММ, $987.13 Джонс, Ральф Номер счета: 436CD, $1,923.85 Краммер, Бетти Номер счета: 968ММ, $5,146.67 Краммер, Тед Номер счета: 897CD, $3,223.19 Смит, Альберт Номер счета: 445СК, ($213.67) Смит, Карл Номер счета: 078CD, $15,345.99 Смит, Сара Номер счета: 843СК, $345.00 Смит, Сара Номер счета: 543ММ, $5,017.40 Смит, Сара Номер счета: 547CD, $34,955.79 Смит, Том Номер счета: 132СК, $100.23 Смит, Том Номер счета: 132CD, $10,000.00
Внимательно проанализируем оператор orderby в следующем запросе из приве денной выше программы. var accInfo = from acc in accounts orderby acc.LastName, acc.FirstName, acc.Balance select acc;
Сортировка результатов этого запроса осуществляется следующим образом. Снача ла результаты сортируются по фамилии, затем элементы с одинаковыми фамилиями сортируются по имени. И наконец, группы элементов с одинаковыми фамилиями и именами сортируются по остатку на счете. Именно поэтому список счетов вкладчиков по фамилии Джонс выглядит так. Джонс, Дженни Номер счета: 108СК, $10.98 Джонс, Ральф Номер счета: 434СК, ($123.32) Джонс, Ральф Номер счета: 454ММ, $987.13 Джонс, Ральф Номер счета: 436CD, $1,923.85
Как показывает результат выполнения данного запроса, список счетов отсортиро ван сначала по фамилии, затем по имени и, наконец, по остатку на счете. Используя несколько критериев, можно изменить на обратный порядок любой со ртировки с помощью ключевого слова descending. Например, результаты следующе го запроса будут выведены по убывающей остатков на счетах. var accInfo = from acc in accounts orderby x.LastName, x.FirstName, x.Balance descending select acc;
В этом случае список счетов вкладчиков по фамилии Джонс будет выглядеть так, как показано ниже. Джонс, Дженни Номер счета: 108СК, $10.98 Джонс, Ральф Номер счета: 436CD, $1,923.85 Джонс, Ральф Номер счета: 454ММ, $987.13 Джонс, Ральф Номер счета: 434СК, ($123.32)
Как видите, теперь счета вкладчика по фамилии Ральф Джонс выводятся по убы вающей: от наибольшей до наименьшей суммы остатка на счете. Подробное рассмотрение оператора select
Оператор select определяет конкретный тип элементов, получаемых по запросу. Ниже приведена его общая форма. select выражение
В предыдущих примерах оператор select использовался для возврата перемен ной диапазона. Поэтому выражение в нем просто обозначало имя переменной диа пазона. Но применение оператора select не ограничивается только этой простой функцией. Он может также возвращать отдельную часть значения переменной диа пазона, результат выполнения некоторой операции или преобразования переменной диапазона и даже новый тип объекта, конструируемого из отдельных фрагментов ин формации, извлекаемой из переменной диапазона. Такое преобразование исходных данных называется проецированием.
Начнем рассмотрение других возможностей оператора select с приведенной ниже программы. В этой программе выводятся квадратные корни положительных значений, содержащихся в массиве типа double. // Использовать оператор select для возврата квадратных корней всех // положительных значений, содержащихся в массиве типа double. using System; using System.Linq; class SelectDemo { static void Main() { double[] nums = { -10.0, 16.4, 12.125, 100.85, -2.2, 25.25, -3.5 }; // Сформировать запрос на получение квадратных корней всех // положительных значений, содержащихся в массиве nums. var sqrRoots = from n in nums where n > 0 select Math.Sqrt(n); Console.WriteLine("Квадратные корни положительных значений,n" + "округленные до двух десятичных цифр:"); // Выполнить запрос и вывести его результаты. foreach (double r in sqrRoots) Console.WriteLine("{0:#.##}", r); } }