Полное руководство. С# 4.0
Шрифт:
Для выполнения запроса в данном примере программы организуется следующий цикл. 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("Положительные значения из массива nums\n" + "после изменений в нем: "); // Выполнить запрос второй раз. 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<int> posNums = from n in nums where n > 0 select n;
Следует иметь в виду, что тип элемента, выбираемого оператором 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); } }