Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Статья из №11 журнала Алгоритм о шаблонах aka generics.


.Net Собеседник #61

Содержание
  1. От автора

От автора

Здравствуйте, коллеги!

Готовится 11-й номер журнала "АЛГОРИТМ".  Здесь вы сможете прочесть предварительную версию одной из статей, которые будут размещены в этом номере. Кстати, новости регулярно выкладывются в моём ЖЖ по адресу http://hdrummer.livejournal.com/

Напоминаю адрес сайта журнала - http://dotnetgrains.sql.ru/alg/alg.htm  

Не забывайте о подписке - подписной индекс для Украины 91132 в Укрпочте, для РФ - подписка через WebMoney . Подробнее на сайте.

И ещё раз о новшествах в C# 2.0

Перевод с дополнениями: Чужа В.Ф.
Литература: Спецификация языка C# 2.0.

Решил ещё раз коснуться этой темы, поскольку перечитывание спецификации даёт дополнительную пищу для размышлений – где и как нужно применять те новые возможности, которые появились в языке C# версии 2.0.

Итак –

1. Шаблоны или Generics

Шаблоны позволяют классам, структурам, интерфейсам, делегатам и методам быть параметризованными тем типом данных, которые они будут хранить, и которыми они будут манипулировать. Понимание и использование шаблонов языка C# не составит труда тем программистам, которые знакомы с шаблонами (generics) языков Eiffel и Ada, или тем, кто использовал шаблоны (templates) языка C++.

1.1. Почему шаблоны?

До появления шаблонов, структуры данных общего назначения могли использовать тип object для хранения данных любого типа. Например, следующий пример класса Stack хранит данные в массиве object, а с помощью методов Push и Pop можно как поместить объект этого класса (класса object) в стэк, так и вытащить его оттуда:

public class Stack
{
object[] items;
int count;
public void Push(object item) {...}
public object Pop() {...}
}

И хотя использование класса object делает класс Stack очень гибким, в таком подходе есть и свои минусы. Например, при таком подходе у нас есть возможность поместить в стэк объект любого типа, например класса Customer. Но при получении объекта с помощью метода Pop, его значение должно быть явно приведено к соответствующему, «своему» типу. Держать в голове такие вещи достаточно утомительно, кроме того, проверки, проводящиеся во время выполнения программы, сказываются на её производительности:

Stack stack = new Stack();
stack.Push(new Customer());
Customer c = (Customer)stack.Pop();

Если мы будем использовать значение значимого типа, например int с методом Push, то это значение будет автоматически упаковано. При проведении обратной операции с помощью метода Pop, оно также должно быть явно распаковано:

Stack stack = new Stack();
stack.Push(3);
int i = (int)stack.Pop();

Операции упаковки и распаковки также влияют на производительность, поскольку в этом случае, случае их применения, задействуется дополнительная динамическая память, и выполняются проверки во время выполнения программы.

Ещё одна проблема с классом Stack – отсутствие возможности указать тип данных, помещенных в стэк. И правда, объект класса Customer может быть помещён в стэк, а затем случайно приведен к неверному типу:

Stack stack = new Stack();
stack.Push(new Customer());
string s = (string)stack.Pop();

И хотя выше мы неверно используем объект класса Stack, код технически верен, поэтому компилятор не выдаст сообщение об ошибке. Проблема не вылезет до тех пор, пока не исполнится код – будет сгенерировано исключение InvalidCastException.

Из вышесказанного ясно, что класс Stack получит несомненные преимущества от возможности указания используемого типа. Для этого и служат шаблоны.

1.2. Создание и использование шаблонов

Шаблоны дают возможность создавать типы, имеющие параметры . Эти параметры в свою очередь также являются типами, то есть их можно назвать типами-параметрами. В примере ниже объявлен шаблон класса Stack с параметром типа T. Параметр указан между разделителями < и >, указанных после имени класса. Вместо того чтобы делать конвертации в и из типа object, экземпляры класса Stack<T> принимают тот тип, для использования которого они и были созданы и хранят данные без всяких конверсий. Тип-параметр T выступает заменой до тех пор, пока не указан реально используемый тип. Заметьте, что T используется как тип элементов внутреннего массива, как тип параметра для метода Push, и тип возвращаемого значения метода Pop:

public class Stack
{
T[] items;
int count;
public void Push(T item) {...}
public T Pop() {...}
}

При использовании шаблона класса Stack, вместо T подставляется указанный тип. В следующем примере в качестве аргумента типа для T указан тип int:

Stack stack = new Stack();
stack.Push(3);
int x = stack.Pop();

Тип (класс) Stack<int> зовётся сконструированным типом. В типе Stack<int> каждое появление T заменяется аргументом типа int. При создании экземпляра класса Stack<int>, внутреннее хранилище массива items представляет собой скорее массив целых int[], а не массив объектов object[], что является куда более эффективным решением, чем в случае с классическим типом Stack, реализованным без использования шаблонов. Подобным же образом методы Push и Pop сконструированного типа Stack<int> работают со значениями типа int, позволяя отсекать ошибки времени компиляции при ошибочном использовании программистом другого типа. Также теперь становится ненужным явное приведение типов при извлечении их из стэка. 

Шаблоны поддерживают строгую типизацию, что означает генерирование ошибки при попытке разместить переменную типа int в стэке объектов типа Customer. Класс Stack<int> должен работать с переменными типа int, также как класс Stack<Customer> ограничен объектами типа Customer, поэтому компилятор сгенерирует ошибки для двух последних строк следующего примера:

Stack stack = new Stack();
stack.Push( new Customer());
Customer c = stack.Pop();
stack.Push(3);
// ошибка несоответствия типов данных
int x = stack.Pop();
// ошибка несоответствия типов данных

При объявлении шаблона типа можно использовать любое количество параметров типов. Ранее мы рассмотрели шаблон класса Stack, имеющий только один тип-параметр, если же взять шаблон класса Dictionary,то у него должно быть два типа-параметра, один для ключей, а второй для значений:

public class Dictionary
{
public void Add(K key, V value) {...}
public V this[K key] {...}
}

При использовании шаблона класса Dictionary<K,V> в реальном приложении нужно указать два аргумента типа:

Dictionary<string,Customer> dict = new Dictionary<string,Customer>();
dict.Add("Peter"new Customer());
Customer c = dict["Peter"];

1.3. Создание экземпляра шаблона типа

Скомпилированным представлением шаблона типа, как и обычного, классического типа, являются инструкции на промежуточном языке (intermediate language или IL) и метаданные. Конечно, такое представление также включает поддержку наличия и использования типов-параметров.

При первом создании приложением экземпляра сконструированного шаблона типа такого, как Stack, JIT-компилятор (just-in-time или JIT) платформы .NET конвертирует код на языке IL и метаданные в родной код (native code), подставляя действительно используемые типы вместо типов-параметров в процессе конвертации. Последующие ссылки на этот сконструированный тип в дальнейшем используют именно этот родной код. Процесс создания указанного сконструированного типа из шаблона типа называют созданием экземпляра шаблона типа или его реализацией .

.NET Common Language Runtime создаёт специальную копию родного кода для каждой реализации шаблона типа в случае значимых типов, но использует лишь одну копию такого кода для ссылочных типов.

Окончание статьи - 1.4. Ограничения, 1.5. Шаблоны методов, 1.6. Анонимные методы, 1.7 Итераторы, 1.8. Неполные типы, 1.9. Null-допустимые типы, смотрите в №11 журнала АЛГОРИТМ в конце октября 2006 года.

На этом шестьдесят первый выпуск .Net Собеседника закончен.
До следующего номера.



Чужа Виталий Ф. aka hDrummer, MCAD.Net, MCDBA, MCP
hdrummer ухо gmail точка ru - жду ваши предложения и замечания.



В избранное