Листинг 1.1. "Hello LINQ"
using System; using System.Linq;
string[] greetings = {"hello world", "hello LINQ", "hello Apress" } ;
var items = from s in greetings where s.EndsWith("LINQ") select s; foreach (var item in items) Console.WriteLine(item) ;
Запуск приведенного выше кода по нажатию <Ctrl+F5> выдаст следующий вывод в окно консоли:
hello LINQ
Смещение парадигмы
Вы почувствовали только что, как ваш мир сдвинулся с места? Как разработчик .NET, вы должны были это почувствовать. То, что показано в тривиальном примере программы из листинга 1.1, который вы только что запустили, выглядит как запрос на языке структурированных запросов (Structured Query Language — SQL) к массиву строк1. Взгляните на конструкцию where. Она выглядит так. будто я использовал метод EndsWith объекта string, потому что так оно и есть. Вы можете спросить— а как насчет типа переменной var? Выполняет ли по-прежнему С# контроль типов? Ответ — да, он проверяет статически типы во время компиляции. Какое средство или средства С# позволяют все это? Ответ: Microsoft Language Integrated Query (язык интегрированных запросов Microsoft), иначе именуемый как LINQ.
Запрос к XML
В то время как пример из листинга 1.1 достаточно тривиален, пример листинга 1.2 уже может очертить потенциальную мощь, которую вручает LINQ в руки разработчика .NET. Он демонстрирует легкость, с которой можно взаимодействовать и опрашивать данные в расширяемом языке разметки (Extensible Markup Language — XML) с помощью программного интерфейса LINQ to XML. Вы должны уделить особое внимание тому, как я конструирую из данных XML объект по имени books, с которым потом можно взаимодействовать программно.
Листинг 1.2. Простой запрос XML с использованием LINQ to XML
using System; using System.Linq; using System.Xmi.Linq; XElement books = XElement.Parse( @"<books> <book>
<title>Pro LINQ: Language Integrated Query in C# 2008</title> <author>Joe Rattz</author> </book> <book>
<title>Pro WF: Windows Workflow in .NET 3.0</title> <author>Bruce Bukovics</author> </book> <book>
<title>Pro C# 2005 and the .NET 2.0 Platform, Third Edition</title> <author>Andrew Troelsen</author> </book> </books>"); var titles = from book in books. Elements ("book")
where (string) book.Element("author") == "Joe Rattz" select book.Element("title") ; foreach(var title in titles) Console.WriteLine(title.Value);
Запуск предыдущего кода нажатием <Ctrl+F5> выведет следующие данные в окно консоли:
Pro LINQ: Language Integrated Query in C# 2008
Вы обратили внимание, что я разобрал данные XML для помещения их в объект XElement? Я нигде не создавал XmlDocument. Среди преимуществ LINQ to XML— расширения, которые он привносит в XML API. Теперь вместо того, чтобы сосредотачивать все вокруг XmlDocument, как того требует W3C Document Object Model (DOM) XML API, LINQ to XML позволяет разработчику взаимодействовать на уровне элемента, используя класс XElement.
Опять-таки, обратите внимание, что я использовал некоторый SQL-подобный синтаксис для опроса данных XML, как если бы это была база данных.
Запрос к базе данных SQL Server
Следующий пример демонстрирует применение LINQ to SQL для опроса таблиц базы данных2, В листинге 1.3 выполняется запрос к стандартной базе данных примеров Microsoft North wind.
Листинг 1.3. Простой запрос XML с использованием LINQ to SQL
using System.Linq; using System.Data.Linq; using nwind;
Northwind db = new Northwind(@"Data Source=.\SQLEXPRESS;Initial Catalog=Northwind") ; var ousts =
from с in db.Customers where с.City == "Rio de Janeiro" select c; foreach (var cust in custs) Console.WriteLine("{0}", cust.CompanyName);
На заметку! Код в листинге 1.3 требует добавления сборки System. Data. Linq. dll к списку ссылок проекта, если это не сделано ранее. Также обратите внимание, что я добавил директиву using для пространства имен System. Data. Linq.
Вы можете видеть здесь, что я добавил директиву using для пространства имен nwind. Для этого примера вы должны использовать утилиту командной строки SQLMetal или Object Relational Designer, чтобы сгенерировать сущностные классы для целевой базы данных, которой в данном случае является база данных примеров Microsoft Northwind. Как это делается с SQLMetal — читайте в главе 12. Сгенерированные сущностные классы создаются в пространстве имен nwind, которое специфицировал при их генерации. Затем я добавил сгенерированный SQLMetal исходный модуль в проект, а также директиву using для пространства имен nwind.
На заметку! Вам может понадобиться изменить строку соединения, переданную конструктору Northwind в листинге 1.3, чтобы правильно установить соединение. В разделе "DataContext () и [Your] DataContext {)" главы 16 описаны разных способах подключения к базе данных.
Запуск предыдущего кода нажатием <Ctrl+F5> выведет следующие данные в окно консоли:
Hanari Carnes Que Delicia Ricardo Adocicados
Этот пример демонстрирует опрос таблицы Customers из базы данных Northwind на предмет списка заказчиков из Рио-де-Жанейро (Rio de Janeiro). Хотя может показаться, что здесь не происходит ничего нового или особенного, чего нельзя было бы получить существующими средствами, все же на самом деле есть серьезные отличия. Важнее всего то. что этот запрос интегрирован в язык, а это значит, что я получаю поддержку уровня языка, включающую проверку синтаксис и IntelliSense. Ушли в прошлое те дни, когда запрос SQL записывался в строку, и невозможно было обнаружить ошибку до тех пор. пока код не будет выполнен. Вы хотите сделать вашу конструкцию where зависящей от поля таблицы Customers, но не помните имени этого поля? IntelliSense покажет вам поля таблицы. Как только вы введете с. в предыдущем примере, IntelliSense отобразит все поля таблицы Customers.
Все предыдущие запросы используют синтаксис выражений запросов. Из главы 12 вы узнаете, что существует два синтаксиса запросов LINQ, один из которых — синтаксис выражешпЧ запросов. Конечно, вы можете всегда использовать синтаксис стандартной точечной нотации, который вы привыкли видеть в С#. Этот синтаксис предлагает нормальный шаблон вызовов объект .метод (), который вы обычно применяете.
Введение
По мере взросления платформы Microsoft .NET и поддерживаемых ею языков С# и VB. стало ясно, что одной из наиболее проблемных областей для разработчиков остается доступ к данным из разных источников. В частности, доступ к базе данных и манипуляции XML часто в лучшем случае запутаны, а в худшем — проблематичны.
Проблемы баз данных многочисленны. Во-первых, сложность представляет то, что вы не можете программно взаимодействовать с базой данных на уровне естественного языка. Это приводит к синтаксическим ошибкам, которые не обнаруживаются вплоть до момента запуска. Неправильные ссылки на поля базы данных также не обнаруживаются. Это может пагубно отразиться на программе, особенно если произойдет во время выполнения кода обработки ошибок. Нет ничего хуже, чем крах механизма обработки ошибок из-за синтаксически неверного кода, который никогда не тестировался. Иногда это неизбежно из-за непредсказуемого поведения ошибки. Наличие кода базы данных, который не проверяется во время компиляции, определенно может привести к этой проблеме.
Вторая проблема связана с неудобством, которое вызвано различными типами данных, используемыми определенным доменом данных, таким как разница между типами базы данных или типами данных XML и естественным языком, на котором написана программа. В частности, серьезно докучать могут времена и даты.
Анализ, итерация и манипуляция XML могут быть достаточно утомительными. Часто фрагмент XML— это все, что вам нужно, но из-за W3C DOM XML API объект XmlDocument должен быть создан только для того, чтобы выполнять различные операции над фрагментом XML.
Вместо того чтобы просто добавить больше классов и методов для постепенного восполнения этих недостатков, команда разработчиков Microsoft решила пойти на один шаг дальше в абстрагировании основ запросов данных из этих конкретных доменов данных. В результате появился LINQ. LINQ — это технология Microsoft, предназначенная для обеспечения механизма поддержки уровня языка для опроса данных всех типов. Эти типы включают массивы и коллекции в памяти, базы данных, документы XML и многое другое.
LINQ для запросов данных
Большей частью LINQ ориентирован на запросы — будь то запросы, возвращающие набор подходящих объектов, единственный объект или подмножество полей из объекта или множества объектов. В LINQ этот возвращенный набор объектов называется последовательностью (sequence). Большинство последовательностей LINQ имеют тип IEnurnerable<T>, где Т— тип данных объектов, находящихся в последовательности. Например, если у вас есть последовательность целых чисел, они должны храниться в переменной типа IEnumerable<int>. Вы увидите, что IEnumerable<T> буквально господствует в LINQ. Очень многие методы LINQ возвращают IEnumerable<T>.
Компоненты
Поскольку LINQ настолько мощный, вы можете ожидать появления множества систем и продуктов, совместимых с LINQ. Почти любое хранилище данных может быть хорошим кандидатом на поддержку запросов LINQ. Сюда относятся базы данных, система Microsoft Active Directory, системный реестр, файловая система, файл Excel и т.д.
Microsoft уже идентифицировала компоненты, представленные в настоящем разделе, как необходимые для LINQ. Нет никаких сомнений в том, что их количество будет расти.
LINQ to Objects
LINQ to Objects— название, данное IEnumerable<T> API для стандартных операций запросов (Standard Query Operators). Именно LINQ to Objects позволяет вам выполнять запросы к массивам и находящимся в памяти коллекциям данных. Стандартные операции запросов— это статические методы класса System.Linq.Enumerable, которые вы используете для создания запросов LINQ to Objects.
LINQ to XML
LINQ to XML— название, присвоенное интерфейсу LINQ API. предназначенному для работы с XML. Этот интерфейс был ранее известен как XLinq в предварительных выпусках LINQ. Microsoft не только добавила необходимые библиотеки XML для работы с LINQ, но также восполнила недостатки стандартной модели XML DOM, тем самым существенно облегчив работу с XML. Прошли времена, когда нужно было создавать XmlDocument только для того, чтобы поработать с маленьким кусочком XML. Чтобы воспользоваться преимуществами LINQ to XML, вы должны иметь в своем проекте ссылку на сборку System.Xml. Linq. dll и директиву using следующего вида:
using System.Xml.Linq;
LINQ to DataSet
LINQ to DataSet— наименование, присвоенное программному интерфейсу LINQ API для DataSet. У многих разработчиков есть масса кода, полагающегося на DataSet. Те, кто не хотят отставать от новых веяний, но и не готовы переписывать свой код, благодаря этому интерфейсу могут воспользоваться всей мощью LINQ.
LINQ to SQL
LINQ to SQL—наименование, присвоенное программному интерфейсу IQueryable<T>, позволяющему запросам LINQ работать с базой данных Microsoft SQL Server. Этот интерфейс в ранних выпусках LINQ назывался DLlnq.
Чтобы воспользоваться преимуществами LINQ to SQL, вы должны иметь в своем проекте ссылку на сборку System. Data. Linq .dll, а также следующую директиву using в исходном коде:
using System.Data.Linq;
LINQ to Entities
LINQ to Entities— альтернативный интерфейс UNQ API, используемый для обращения к базе данных. Он отделяет сущностную объектную модель от физической базы данных, вводя логическое отображение между ними двумя. С таким отделением возрастает мощь и гибкость, но также растет и сложность. Поскольку UNQ to Entitles стоит в стороне от ядра каркаса LINQ, мы не описываем его в настоящей книге. Однако если
вы считаете, что вам нужна более высокая гибкость, чем обеспечиваемая LINQ to SQL, может быть, вам стоит рассмотрев эту альтернативу. В частности, если вам нужно ослабить связь между вашей сметной объектной моделью и базой данных, если ваши сущностные объекты констР^РУ10™ из нескольких таблиц или вам нужна боль- шая гибкость в моделировании в***™* сущностных объектов, то в этом случае LINQ to Entitles может стать оптимальны выбром.
Как получить LINQ
Технически отдельного продукта LINQ, который нужно было бы получать отдельно, не существует. LINQ_ это просто кодовое имя проекта средства запросов, добавленного
в С# 3.0 и .NET Framework 3.5, которое впервые было представлено в очередной версии
Visual Studio — Visual Studio 2008.
LINQ - не только для запросов
Может показаться, что LINQ — это нечто, связанное только с запросами, поскольку расшифровывается, как язык интегрированных запросов (Language Integrated Query). Однако не думайте о нем лишь в этом контексте. Лично я предпочитаю воспринимать LINQ как механизм итерации данных (data iteration engine), но возможно в Microsoft не захотели называть технологию DIE ("умереть").
Приходилось ли вам когда-нибудь вызывать метод, который возвращает обратно данные в некоторой структуре, которую вам затем приходилось конвертировать в еще одну структуру данных, прежде чем передать другому методу? Скажем, для примера, что вы вызываете метод А, и этот метод возвращает массив типа string, содержащий числовые значений в виде строк. Затем вам нужно вызвать метод В, но метод В требует массива целых чисел. Обычно вам приходится организовывать цикл для прохода по массиву строк и наполнения вновь сконструированного массива целых чисел. Какая досада! Позвольте мне указать на мощь Microsoft LINQ.
Предположим, что имеется массив строк, которые я принял от метода А, как показано в листинге 1.4.
Листинг 1.4. Преобразования массива строк в массив целых
string[] numbers = { "0042", "010", "9", "27" } ;
Для этого примера объявлен статический массив строк. Теперь перед вызовом метода В нужно преобразовать массив строк в массив целых чисел: ! '
int[) nums = numbers.Select(s => Int32.Parse(s)).ToArray();
Вот и все. Что может быть проще? Даже если вы скажете "абракадабра", это сэкономит вам всего 48 символов.
А вот код, необходимый для отображения результирующего массива целых чисел:
foreach(int num in nums) Console.WriteLine(num);
Вывод будет выглядеть так:
42
10
9
27
Я знаю, о чем вы подумали: может, я просто отсек ведущие пробелы? Но если и отсортирую результат, убедит ли вас это? Если бы это были по-прежнему строки, то 9 окажется в конце, а 10— в начале. Листинг 1.5 содержит некоторый код. который выполняет преобразование и сортирует вывод.
Листинг 1.5. Преобразования массива строк в массив целых с последующей сортировкой
string[] numbers = { "0042", "010", "9", "27" };
int[ ] nurns = numbers.Select(s => Int32.Parse(s)} .OrderBy(s => s) .ToArray(); foreach(int num in nums) Console.WriteLine(num);
И вот результат:
[1]
[1] 27 42
He правда ли— гладко? Хорошо, скажете вы, все это прекрасно, но ведь это всего лишь простой пример. Давайте рассмотрим более сложный.
Скажем, у вас есть некоторый код, содержащий класс Employee. В этом классе Employee есть метод, возвращающий всех сотрудников. Также предположим, что у вас есть другой код. включающий класс Contact, и в этом классе— метод, публикующий контакты. Предположим, что вам нужно опубликовать всех сотрудников, как контакты.
Задача кажется достаточно простой, но здесь таится ловушка. Общий метод Employee, который извлекает всех сотрудников, возвращает их в списке ArrayList, хранящем объекты Employee, а метод Contact, публикующий контакты, требует массива объектов типа Contact. Ниже показан обычный для такого случая код.
namespace LINQDev.HR {
public class Employee f
public int id;
public string firstName;
public string lastName;
public static ArrayList GetEmployees () {
// Конечно, реальный код должен был
//бы выполнить запрос к базе данных.
ArrayList al = new ArrayList ();
//He правда ли, новое средство инициализации
// объектов С# превращает это в пару пустяков?
al.Add(new Employee { id - 1, firstName = "Joe", lastName = "Rattz"} ): al.Add(new Employee { id = 2, firstName = "William", lastName = "Gates"} ); al. Add (new Employee { id = 3, firstName = "Anders", lastName = "Hejlsberg") ) ; return(al);
}
}
}
namespace LINQDev.Common {
publie class Contact I
public int Id; public string Name;
public static void PublishContocLs (Contact [ ] contacts) <
// Этот метод публикации просто пишет их в окно консоли, foreach(Contact с in contacts)
Console.WriteLine("Contact Id: { 0 } Contact: {1}", c.Id, c.Name);
}
}
}
Как видите, класс Employee и метод GetEmployees находятся в одном пространстве имен— LINQDev.HR, и метод GetEmployees возвращает ArrayList. Метод PublishContacts находится в другом пространстве имен — LINQDev. Common, и требует передачи ему массива объектов Contact.
Ранее это всегда требовало итерации по ArrayList, который возвращен методом GetEmployees, и создания нового массива типа Contact для передачи методу PublishContacts. LINQ значительно облегчает задачу, как показано в листинге 1.6.
Листинг 1.6. Вызов обычного кода
ArrayList alEmployees = LINQDev.HR.Employee.GetEmployees(); LINQDev. Common.Contact[] contacts = alEmployees . CastCLINQDev. HR. Employee> () . Select (e => new LINQDev. Common. Contact { Id = e.id,
Name = string.Format("{0} {1}", e. firstName, e.lastName) ))
. ToArray<LINQDev. Common. Contact> () ;
LINQDev. Common.Contact.PublishContacts(contacts);
Чтобы преобразовать ArrayList объектов Employee в массив объектов Contact, я сначала привожу ArrayList объектов Employee к последовательности IEnumerable<Employee>. используя стандартную операцию запросов Cast. Это необходимо, потому что использован унаследованный класс коллекций ArrayList. Синтаксически говоря, в коллекции ArrayList хранятся объекты класса System. Object, а не объекты типа класса Employee. Поэтому я должен привести их к объектам Employee. Если бы метод GetEmployees возвращал обобщенную коллекцию List, необходимости в этом не было бы. Однако этот тип коллекции был недоступным, когда был написан унаследованный код.
Затем я вызываю операцию Select на возвращенной последовательности объектов Employee, и в лямбда-выражении— коде, переданном внутри вызова метода Select, — создаю и инициализирую экземпляр объекта Contact, используя для этого новые средства инициализации объектов С# 3.0, чтобы присвоить значения входного элемента Employee вновь сконструированному выходному элементу Contact. Лямбда-выражение (lambda expression)— новое средство С# 3.0, представляющее собой новое сокращение для специфицирования анонимных методов, которое будет объяснено в главе 2. И, наконец, я преобразую последовательность вновь сконструированных объектов Contact в массив объектов Contact, применяя для этого операцию ТоАггау, потому что этого требует метод PublishContacts. Разве не изящно? А вот результат:
Contact Id: 1 Contact: Joe Rattz
Contact Id: 2 Contact: William Gates
Contact Id: 3 Contact: Anders Hejlsberg
Как видите, LINQ может делать многое помимо запросов данных. По мере чтения глав нашей книги задумайтесь о дополнительных применениях средств, представленных LINQ.
Советы начинающим
Несмотря на то что доступно много очень полезных ресурсов для разработчиков, которые желают изучать применение LINQ во всей его потенциальной мощи, все же я хочу привести несколько небольших советов, чтобы помочь вам начать. В некоторых отношениях эти советы кажутся более уместными в конце книги. В конце концов, я еще не объяснил некоторые упоминающиеся в них концепции. Но было бы, мягко говоря, некорректно заставлять вас сначала прочесть всю книгу лишь только для того, чтобы добраться до полезных советов в ее конце. С учетом всего сказанного, в данном разделе содержатся некоторые советы, которые, как я полагаю, вы сочтете полезными, даже не полностью понимая их или их контекст.
Когда запутались, используйте ключевое слово var
В то время как необходимо использовать ключевое слово var при захвате последовательности от анонимных классов в переменную, иногда это также удобный путь заставить код компилироваться, когда вы запутались со сложными обобщенными типами. Хотя я отдаю предпочтение разработчикам, которые точно знают, какого типа данные содержатся в последовательности— в том смысле, что для IEnumerable<T> вы должны знать, какой тип представляет Т — иногда, особенно начиная работать с LINQ, это может вводить в заблуждение. Если вы обнаруживаете, что код не компилируется из-за определенного рода несоответствия типов данных, попробуйте заменить явно установленные типы переменных на указанные с применением ключевым словом var.
Например, предположим, что есть такой код:
// Этот код не компилируется.
Northwind db = new Northwind(@"Data Source=. \SQLEXPRESS; Initial Catalog=Northwind") ;
IEnumerable<?> orders = db.Customers
.Where (с => c.Country == "USA" && c.Region == "WA") .SelectMany(с => с.Orders) ;
Может быть неясно, каков тип данных у последовательности IEnumerable. Вы знаете. что это IEnumerable некоторого типа Т, но что есть Т? Удобный трюк состоит в присвоении результата запроса переменной, чей тип специфицирован ключевым словом var, нежели указывать тип переменной с необходимостью точного знания, что за типом является Т. В листинге 1.7 показано как должен выглядеть такой код.
Листинг 1.7. Пример кода, использующего ключевое слово var
Northwind db = new Northwind(@"Data Source=.\SQLEXPRESS; Initial Catalog=Northwind">;
var orders = db.Customers
.Where (с => c.Country == "USA" && c.Region == "WA") .SelectMany(с => с.Orders) ;
Console.WriteLine(orders.GetType());
В этом примере обратите внимание на то. что теперь тип переменной orders указан с использованием ключевого слова var. Запуск этого кода даст следующий вывод:
System.Data.Linq.DataQuery41[nwind.Order]
Здесь используется загадочный жаргон компилятора, но для нас интерес представляет часть nwind.Order. Теперь вы знаете, что тип данных полученной последовательности — nwind. Order.
и примера в отладчике и просмотр переменной Если пас смущает такой вывод, запуск г
. . Г вам такой тип данных orders:
orders в окне Locals (Локальные) покажет вам
System.Linq.IQueryable<nwind.Order> <System.Data.Linq.DataQuery<nwind.Order>}
Это проясняет, что вы имеете последовательность объектов nwind. Order. Технически вы получаете здесь IQueryable<nwind.Order>. но. если хотите, это можно присвоить IEnumerable<nwind.Order>, поскольку IQueryable<T> наследуется от IEnumerable<T>. Таким образом, вы можете переписать предыдущий код, плюс выполнить перечисление результатов, как показано в листинге 1.8.
Листинг 1.8. Пример кода из листинга 1.7, но с явными типами
Northwind db = new Northwind(@"Data Source=.\SQLEXPRESS;Initial Catalog-Northwind"); IEnumerable<Order> orders = db.Customers .Where (с => c.Country == "USA" && c.Region == "WA") .SelectMany(с => c.Orders); foreach(Order item in orders)
Console.WriteLine("{0} - {1} - {2}", item.OrderDate, item.OrderlD, item.ShipName);
Чтобы приведенный код работал, у вас должна быть директива using для пространства имен System. Collectiobs. Generic в дополнение к пространству имен System. Linq, которого следует ожидать при работе с кодом LINQ.
Этот код должен дать следующий сокращенный результат:
3/21/1997 12:00:00 AM - 10482 - Lazy К Kountry Store 5/22/1997 12:00:00 AM - 10545 - Lazy К Kountry Store
4/17/1998 12:00:00 AM - 11032 - White Clover Markets 5/1/1998 12:00:00 AM - 11066 - White Clover Markets
Использование операций Cast или Of Type для унаследованных коллекций
Вы обнаружите, что большинство стандартных операций запросов LINQ могут быть вызваны на коллекциях, реализующих интерфейс IEnumerable<T>. Ни одна из унаследованных коллекций С# из пространства имен System.Collection не реализует IEnumerable<T>. Поэтому возникает вопрос— как использовать LINQ с унаследованными коллекциями из вашего существующего кода?
Есть две стандартных операции запросов, специально предназначенных для этой цели — Cast и Of Туре. Обе они могут быть использованы для преобразования унаследованных коллекций в последовательности IEnumerable<T>. В листинге 1.9 показан пример.
Листинг 1.9. Преобразование унаследованной коллекции в lEnumerable<T> с применением операции Cast
// Построим унаследованную коллекцию. ArrayList arrayList = new ArrayList() ;
// Конечно, можно было бы использовать здесь инициализацию коллекций,
//но это не работает с унаследованными коллекциями.
arrayList.Add("Adams");
arrayList.Add("Arthur");
arrayList.Add("Buchanan");
IEnumerable<string> names = arrayList.Cast<string> () .Where(n => n.Length < 7); foreach(string name in names) Console.WriteLine(name);
В листинге 1.10 представлен пример использования операции Of Туре. Листинг 1.10. Использование операции Of Туре
// Построим унаследованную коллекцию. ArrayList arrayList = new ArrayList ();
// Конечно, можно бьшо бы использовать здесь инициализацию коллекций,
//но это не работает с унаследованными коллекциями.
arrayList.Add("Adams") ;
arrayList.Add("Arthur");
arrayList.Add("Buchanan");
IEnumerable<string> names = arrayList.OfType<string> () .Where(n => n.Length <7); foreach (string name in names) Console.WriteLine(name) ;
Оба примера дают одинаковый результат:
Adams Arthur
Разница между двумя операциями в том, что Cast пытается привести все элементы в коллекции к указанному типу, помещая их в выходную последовательность. Если в коллекции есть объект типа, который не может быть приведен к указанному, генерируется исключение. Операция OfType пытается поместить в выходную последовательность только те элементы, которые могут быть приведены к специфицированному типу.
Отдавайте предпочтение операции OfType перед Cast
Одной из наиболее важных причин добавления обобщений в С# была необходимость дать языку возможность создавать коллекции со статическим контролем типов. До появления обобщений приходилось создавать собственные специфические типы коллекций для каждого типа данных, которые нужно было в них хранить — не было никакой возможности гарантировать, чтобы каждый элемент, помещаемый в унаследованную коллекцию, был одного и того же корректного типа. Ничего в языке не мешало коду добавить объект TextBox в ArrayList, предназначенный для хранения только объектов Label.
С появлением обобщений в С# 2.0 разработчики получили в свои руки способ явно устанавливать, что коллекция может содержать только элементы определенного указанного типа. Хотя и операция OfType, и операция Cast могут работать с унаследованными коллекциями, Cast требует, чтобы каждый объект в коллекции был правильного типа, что было основным фундаментальным недостатком унаследованных коллекций, из-за которого были введены обобщения. При использовании операции Cast, если любой из объектов в коллекции не может быть приведен к указанному типу данных, генерируется исключение. Поэтому используйте операцию OfType. С ней в выходной последовательности IEnumerable<T> будут сохранены только объекты указанного типа, и никаких исключений генерироваться не будет. В лучшем случае все объекты будут правильного типа и все попадут в выходную последовательность. В худшем случае некоторые элементы будут пропущены, но в случае применения операции Cast они бы привели к исключению.
Не рассчитывайте на безошибочность запросов
В главе 3 речь пойдет о том, что запросы LINQ являются отложенными (deferred) и на самом деле не выполняются, когда вы инициируете их. Например, рассмотрим следующий фрагмент кода из листинга 1.1:
var items = from s in greetings where s.EndsWith("LINQ") select s;
foreach (var item in items) Console.WriteLine(item);
Хотя, кажется, что запрос выполняется при инициализации переменной items, на самом деле это не так. Поскольку операции Where и Select являются отложенными, запрос на самом деле не выполняется в этой точке. Запрос просто вызывается, объявляется, или определяется, но не выполняется. Все начинает происходить тогда, когда из него извлекается первый результат. Это обычно происходит тогда, когда выполняется перечисление результатов переменной запроса. В данном примере результат запроса не востребован до тех пор, пока не запустится оператор foreach. Именно в этой точке будет выполнен запрос. Таким образом, мы говорим, что запрос является отложенным.
Очень легко забыть о том. что многие операции запросов являются отложенными и не будут выполнены до тех пор, пока не начнется перечисление результатов. Это значит; что можно иметь неправильно написанный запрос, который сгенерирует исключение только тогда, когда начнется перечисление его результатов. Такое перечисление может начаться намного позже, так что можно легко забыть, что причиной неприятностей был неправильный запрос.
Рассмотрим код в листинге 1.11.
Листинг 1.11. Запрос с преднамеренным исключением, отложенным до перечисления
stringf] strings = { "one", "two", null, "three" };
Console.WriteLine("Before Where () is called.");
IEnumerable<string> ieStrings = strings. Where (s => s. Length == 3) ;
Console.WriteLine("After Where () is called.");
foreach (string s in ieStrings)
{
Console.WriteLine ("Processing " + s) ;
I
Я знаю, что третий элемент в массиве строк — null, и я не могу вызвать null. Length без генерации исключения. Выполнение кода благополучно пройдет строку, где вызывается запрос. И все будет хорошо до тех пор, пока я не начну перечисление последовательности ieStrings, и не доберусь до третьего элемента, где возникнет исключение. Вот результат этого кода, подтверждающий мои слова:
Before Where() is called.
After Where () is called.
Processing one
Processing two
Unhandled Exception: System.NullReferenceException: Object reference not set to an
instance of an object.
Как видите, я вызвал операцию Where без исключения. Оно не появилось до тех пор, пока я не попытался в перечислении обратиться к третьему элементу последовательности. где и возникло исключение. Теперь представьте, что последовательность ieStrings передана функции, которая дальше выполняет перечисление последовательности— возможно, чтобы наполнить выпадающий список или какой-то другой элемент управления. Легко подумать, что исключение вызвано сбоем в этой функции, а не самим запросом LINQ.
Используйте преимущество отложенных запросов
В главе 3 л раскрою тему отложенных запросов более глубоко. Однако хочу отметить, что если запрос является отложенным, который в конечном итоге возвращает IEnumerable<T>, этот объект IEnumerable<T> может перечисляться снова и снова, получая последние данные из источника. Вам не нужно ни вызывать, ни, как отмечалось ранее, объявлять запрос заново.
В большинстве примеров кода этой книги вы увидите вызов запроса и возврат IEnumerable<T> для некоторого типа Т, сохраняемый в переменной. Затем я обычно вызываю foreach на последовательности IEnumerable<T>. Это делается в целях демонстрации. Если код выполняется много раз. повторный вызов запроса — лишняя работа. Более оправданным может быть наличие метода инициализации запроса, который вызывается однажды в жизненном цикле контекста, и там конструировать все запросы. Затем вы можете выполнять перечисление конкретной последовательности, чтобы получить последнюю версию результатов запроса.
Использование Log из DataContext
При работе с LINQ to SQL не забудьте, что класс базы данных, генерируемый SQLMetal, унаследован от System.Data.Linq.DataContext. Это значит, что ваш сгенерированный класс DataContext имеет некоторую полезную встроенную функциональность, такую как объект TextWriter по имени Log.
Одной из прелестей объекта Log является то, что он выводит эквивалентный SQL-оператор запроса IQueryable<T> до подстановки параметров. Случалось ли вам сталкиваться с отказом кода в рабочей среде, который, как вам кажется, вызван данными? Не правда ли, было бы хорошо запустить запрос на базе данных, вводя его в SQL Enterprise Manager или Query Analyzer, чтобы увидеть в точности, какие данные он возвращает? Объект Log класса DataContext выведет для вас запрос SQL. Пример показан в листинге 1.12.
Листинг 1.12. Пример использования объекта DataContext. Log
Northwind db = new Northwind (@"Data Source=. \SQLEXPRESS; Initial Catalog-Northwind");
db.Log = Console.Out;
IQueryable<Order> orders = from с in db.Customers
from о in с .Orders
where c.Country == "USA" && c.Region == "WA" select o;
foreach(Order item in orders)
Console.WriteLine (" {0} - {1} - {2}", item.OrderDate, item.OrderlD, item.ShipName);
Этот код производит следующий вывод:
SELECT [tl].[OrderlD], [tl].[CustomerlD], [tl].[EmployeelD] , [tl].[OrderDate], [tl].[RequiredDate] , [tl].[ShippedDate], [tl].[ShipVia], [tl].[Freight], [tl].[ShipName], [tl].[ShipAddress], [tl].[ShipCity], [tl].[ShipRegion], ' [tl].[ShipPdstalCode], [tl].[ShipCountry] FROM [dbo] . [Customers] AS [tO] , [dbo] . [Orders] AS [tl]
WHERE ([tO] . [Country] - @p0) AND ([tO] . [Region] = @pl) AND ([tl J. [CustomerlD] ® [tO].[CustomerlD])
-- SpO: Input String (Size = 3; Prec » 0; Scale = Q) [USA]
— @pl: Input String (Size = 2; Prec 0; Scale = 0) [WA]
— Context: SqlProvider (Sql2005) Model: AttributedMetaModel Build: 3.5.20706.1
3/21/1997 12:00:00 AM - 10482 - Lazy К Kountry Store 5/22/1997 12:00:00 AM - 10545 - Lazy К Kountry Store 6/19/1997 12:00:00 AM - 10574 - Trail1s Head Gourmet Provisioners
6/23/199? 12:00:00 AM - 10577 - Trail's Head Gourmet Provisioned
1/8/1998 12:00:00 AM - 10822 - Trail's Head Gourmet provisioners
7/31/1996 12:00:00 AM - 10269 - White Clover Markets
11/1/1996 12:00:00 AM - 10344 - White Clover Markets
3/10/1997 12:00:00 AM - 10469 - White Clover Markets
3/24/1997 12:00:00 AM - 10483 - White Clover Markets
4/11/1997 12:00:00 AM - 10504 - White Clover Markets
7/11/1997 12:00:00 AM - 10596 - White Clover Markets
10/6/1997 12:00:00 AM - 10693 - White Clover Markets
10/8/1997 12:00:00 AM - 10696 - White Clover Markets^
10/30/1997 12:00:00 AM - 10723 - White Clover Market^
11/13/1997 12:00:00 AM - 10740 - White Clover Market
1/30/1998 12:00:00 AM - 10861 - White Clover Markets
2/24/1998 12:00:00 AM - 10904 - White Clover Markets
4/17/1998 12:00:00 AM - 11032 - White Clover Markets
5/1/1998 12:00:00 AM - 11066 - White Clover Markets
0 коммент.:
Отправить комментарий