Когда приходится иметь дело с обобщениями, то нередко возникает вопрос: не приведет ли применение обобщенного класса к неоправданному раздуванию кода? Ответ на этот вопрос прост: не приведет. Дело в том, что в C# обобщения реализованы весьма эффективным образом: новые объекты конструируемого типа создаются лишь по мере надобности. Этот процесс описывается ниже.
Когда обобщенный класс компилируется в псевдокод MSIL, он сохраняет все свои параметры типа в их обобщенной форме. А когда конкретный экземпляр класса по требуется во время выполнения программы, то JIT-компилятор сконструирует кон кретный вариант этого класса в исполняемом коде, в котором параметры типа заме няются аргументами типа. В каждом экземпляре с теми же самыми аргументами типа будет использоваться один и тот же вариант данного класса в исполняемом коде.
Так, если имеется некоторый обобщенный класс Gen, то во всех объектах типа Gen будет использоваться один и тот же исполняемый код данного класса. Сле довательно, раздувание кода исключается благодаря тому, что в программе создают ся только те варианты класса, которые действительно требуются. Когда же возникает потребность сконструировать объект другого типа, то компилируется новый вариант класса в исполняемом коде.
Как правило, новый исполняемый вариант обобщенного класса создается для каж дого объекта конструируемого типа, в котором аргумент имеет тип значения, например int или double. Следовательно, в каждом объекте типа Gen будет использовать ся один исполняемый вариант класса Gen, а в каждом объекте типа Gen — другой вариант класса Gen, причем каждый вариант приспосабливается к конкретно му типу значения. Но во всех случаях, когда аргумент оказывается ссылочного типа, используется только один вариант обобщенного класса, поскольку все ссылки имеют одинаковую длину (в байтах). Такая оптимизация также исключает раздувание кода. Некоторые ограничения, присущие обобщениям
Ниже перечислен ряд ограничений, которые следует иметь в виду при использова нии обобщений.
Свойства, операторы, индексаторы и события не могут быть обобщенными. Но эти элементы могут использоваться в обобщенном классе, причем с параметрами обобщенного типа этого класса.
К обобщенному методу нельзя применять модификатор extern.
Типы указателей нельзя использовать в аргументах типа.
Если обобщенный класс содержит поле типа static, то в объекте каждого конструируемого типа должна быть своя копия этого поля. Эго означает, что во всех экземплярах объектов одного конструируемого типа совместно используется одно и то же поле типа static. Но в экземплярах объектов другого конструируемого типа совместно используется другая копия этого поля. Следовательно, поле типа static не может совместно использоваться объектами всех конструируемых типов. Заключительные соображения относительно обобщений
Обобщения являются весьма эффективным дополнением С#, поскольку они упро щают создание типизированного, повторно используемого кода. Несмотря на несколь ко усложненный, на первый взгляд, синтаксис обобщений, их применение быстро входит в привычку. Аналогично, умение применять ограничения к месту требует не которой практики и со временем не вызывает особых затруднений. Обобщения теперь стали неотъемлемой частью программирования на С#. Поэтому освоение этого важно го языкового средства стоит затраченных усилий.
ГЛАВА 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(); } }