- •А. А. Волосевич
- •Содержание
- •1. Общая характеристика платформы .Net
- •1.1. Инфраструктура платформы .Net
- •1.2. Версии платформы .Net
- •2. Общие концепции синтаксиса языка c#
- •3. Система типов clr и языка c#
- •4. Идентификаторы, ключевые слова и литералы
- •5. Выражения и операции
- •1. Первичные операции
- •2. Унарные операции
- •6. Операторы
- •6.1. Операторы объявления
- •6.2. Операторы выражений
- •6.3. Операторы перехода
- •6.4. Операторы выбора
- •6.5. Операторы циклов
- •6.6. Прочие операторы
- •7. Начальные сведения о массивах
- •8. Классы
- •8.1. Допустимые элементы класса
- •8.2. Модификаторы доступа для элементов и типов
- •8.3. Разделяемые классы
- •8.4. Использование класса
- •9. Методы
- •9.1. Описание метода
- •9.2. Вызов метода
- •9.3. Разделяемые методы
- •10. Свойства и индексаторы
- •11. Статические элементы и методы расширения
- •11.1. Статические элементы
- •11.2. Статические классы
- •11.3. Методы расширения
- •12. Конструкторы и инициализация объектов
- •13. Наследование классов
- •14. КлассSystem.Objectи иерархия типов
- •15. Структуры
- •16. Перечисления
- •17. Интерфейсы
- •18. Универсальные шаблоны
- •18.1. Универсальные классы и структуры
- •18.2. Ограничения на параметры универсальных типов
- •18.3. Универсальные методы
- •18.4. Ковариантность и контравариантность
- •19. Использование универсальных шаблонов
- •19.1. Кортежи
- •19.2. Типы, допускающие значение null
- •19.3. Прочие примеры универсальных шаблонов
- •20. Делегаты
- •21. Анонимные методы и лямбда-выражения
- •22. События
- •23. Перегрузка операций
- •24. Анонимные типы
- •25. Пространства имён
- •26. Генерация и обработка исключительных ситуаций
- •27. Директивы препроцессора
- •28. Документирование исходного кода
- •Литература
18.4. Ковариантность и контравариантность
Определим понятия ковариантности и контравариантности для сконструированных типов данных. Для этого введём отношение частичного порядка на множестве ссылочных типов:
наследуется (прямо или косвенно) от.
Если имеется тип C<T>, а также типыT1иT2(T1 ≤ T2), тоC<T>назовём:
– ковариантным, еслиC<T1> ≤ C<T2>;
– контравариантным, еслиC<T2> ≤ C<T1>;
– инвариантным, если не верно ни первое, ни второе утверждение.
Понятия частичного порядка типов, ковариантности и контравариантности связаны с приведением типов. Тот факт, что тип T1«меньше» типаT2, означает возможность неявного приведения переменной типаT1к типуT2. Как указывалось, массивы ковариантны для ссылочных типов (например, массив строк присваивается массиву объектов).
Покажем на примере, что универсальные классы не могут быть ни ковариантными, ни контравариантными. Рассмотрим следующий набор классов:
// два простых класса, связанных наследованием
public class Person
{
publicstringName;
}
publicclassStudent:Person
{
public string University;
}
// универсальный класс с открытым полем
publicclassC<T>
{
publicT Field;
}
// объявление и инициализация двух объектов универсального класса
varcp =newC<Person>();
varcs =newC<Student> {Field =newStudent()};
Допустим, разрешена ковариантность классов. Тогда последняя строка в следующем листинге вызовет ошибку доступа к памяти, так как фактически объекту класса-предка выполняется присваивание объекта класса-потомка.
// это присваивание было бы возможным при ковариантности
cp = cs;
cp.Field = newPerson();
// в следующей строке – ошибка доступа к памяти
cs.Field.University = "MIT";
Из аналогичных соображений не может быть разрешена контравариантность классов, т. е. присваивание cs = cp:
// это присваивание было бы возможным при контравариантности
cs = cp;
vars = cs.Field;
// в следующей строке – ошибка доступа к памяти
s.University = "Berkeley";
Таким образом, универсальные классы инвариантны, однако универсальные интерфейсы могут быть описаны как ковариантные или контравариантные относительно некоего параметра-типа. Чтобы указать на ковариантность относительно параметра T, следует использовать ключевое словоoutпри описании параметра типа. На контравариантность указывает ключевое словоinпри описании параметра типа.
publicinterfaceIOutOnly<outT>
{
T this[intindex] {get; }
}
publicinterfaceIInOnly<inT>
{
void Process(T x);
}
Для обеспечения безопасности типов компилятор отслеживает, чтобы ковариантные параметры всегда использовались как типы возвращаемых значений, а контравариантные параметры являлись типами аргументов. Один универсальный интерфейс может, при необходимости, содержать как ковариантные, так и контравариантные параметры.
19. Использование универсальных шаблонов
В данном параграфе приведено несколько примеров использования универсальных шаблонов в элементах платформы .NET.
19.1. Кортежи
В языках программирования кортежем(tuple) называется структура данных, содержащая фиксированный набор разнотипных значений. В платформе .NET для создания кортежей доступен набор универсальных классов видаSystem.Tuple<>. Всего имеется восемь универсальных классовTuple<>, которые различаются количеством параметров типов.
Tuple<string,int> t =newTuple<string,int>("Hello", 4);
Console.WriteLine("{0} - {1}", t.Item1, t.Item2);
Статический класс System.Tupleсодержит восемь перегруженных версий методаCreate()для конструирования кортежа с заданным числом элементов:
Tuple<string,int> t =Tuple.Create("Hello", 4);