Знакомство с LINQ

 

Листинг 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

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