Поддержка контравариантности в обобщенных интерфейсах. Ключевое слово in
Перед изучением данной темы рекомендуется ознакомиться со следующими темами:
- Иерархии обобщенных классов. Обобщенный базовый и производный классы
- Ковариантность в обобщенных интерфейсах. Ключевое слово out
Содержание
- 1. Понятие контравариантности. Ключевое слово in
- 2. Требования к методам, которые объявляются в контравариантном интерфейсе
- 3. Пример, демонстрирующий контравариантность
- 4. Рисунок, объясняющий контравариантность
- 5. Наследование в контравариантных интерфейсах
- 6. Ограничения, накладываемые на контравариантность
- Связанные темы
Поиск на других ресурсах:
1. Понятие контравариантности. Ключевое слово in
В языке C#, начиная с версии 4.0, в обобщениях были введены ковариантность и контравариантнисть. Более подробно об особенностях применения ковариантности для обобщенных типов можно прочитать здесь. В данной теме раскрывается понятие контравариантности.
Контравариантность (так же как и ковариантность) рассматривается в контексте иерархии некоторых классов. Контравариантность в обобщенных интерфейсах — это механизм, обеспечивающий возможность использовать в обобщенных классах, реализующих этот интерфейс, любой класс по некоторой иерархии классов.
Для реализации механизма контравариантности необходимы объявления следующих элементов:
- обобщенного интерфейса, который получает параметром некоторый тип T и реализует метод (методы), на который накладывается ряд требований в сигнатуре (смотрите далее). Этот интерфейс в дальнейшем будем называть контравариантный интерфейс. Объявление аргумента типа T в контравариантном интерфейсе осуществляется с помощью ключевого слова in;
- обобщенного класса, реализующего контравариантный интерфейс. Согласно синтаксису, класс должен реализовать все методы интерфейса;
- двух или более классов, образующих иерархию. Эти классы выступают в качестве аргументов типа T при объявлении ссылки на контравариантный интерфейс и создании экземпляров обобщенных классов.
Общая форма объявления контравариантного интерфейса, который получает аргументом некоторый тип T, имеет вид
interface InterfaceName<in T> { ... }
здесь
- InterfaceName – имя обобщенного интерфейса. Модификатор in указывает на то, что интерфейс поддерживает контравариантность;
- T — имя типа, который используется в объявлении методов интерфейса InterfaceName.
Как видно из вышеприведенного кода, для обеспечения контравариантности перед именем типа T задается ключевое слово in.
Класс, реализующий контравариантный интерфейс, объявляется стандартным способом
class ClassName<T> : InterfaceName<T> { ... }
здесь
- ClassName – имя обобщенного класса, который получает параметром тип T и реализует интерфейс InterfaceName<T>.
Как видно из вышеприведенного фрагмента, модификатор in перед объявлением типа T класса не ставится.
Пара интерфейс-класс позволяет реализовать механизм контравариантности для обобщенного типа T. В качестве типа T могут использоваться классы, образующие иерархию. Для двух классов Base и Derived, которые образуют иерархию
class Base { ... } class Derived : Base { ... }
можно записать следующий код, который работает благодаря контравариантности
// Объявить ссылку на интерфейс с типом заполнителем производного класса Derived InterfaceName<Derived> refDerived; // Создать экземпляр класса ClassName с типом-заполнителем базового класса Base ClassName<Base> obj = new ClassName<Base>(...); // Это присваивание работает благодаря контравариантности refDerived = obj;
Как видно из вышеприведенного кода, модификатор in в объявлении интерфейса InterfaceName<in T> разрешает чтобы экземпляр класса, который получает аргументом базовый класс Base, присваивался ссылке на интерфейс, который получает параметром производный класс Derived.
Иными словами, если из объявления интерфейса убрать ключевое слово in, то в строке
refDerived = obj;
возникнет ошибка на этапе компиляции. Контравариантнисть НЕ БУДЕТ поддерживаться, как следствие будет выдаваться стандартная ошибка несовместимости типов. Это объясняется тем, что компилятору требуется строгое совпадение аргументов типов чтобы обеспечить типовую безопасность. Контравариантнисть позволяет смягчить это требование.
⇑
2. Требования к методам, которые объявляются в контравариантном интерфейсе
Методы, объявляемые в контравариантным интерфейсе должны иметь сигнатуру, в которой метод не возвращает обобщенный тип T
interface IContraInterface<in T> { ... type MethodName(parameters); ... }
здесь
- type — любой тип в программе за исключением типа T. Это может быть один из базовых типов (int, float, char и т.д.) или любой другой тип программы (класс, структура и т.д.).
⇑
3. Пример, демонстрирующий контравариантность
В примере демонстрируется механизм контравариантности для ссылок refA и refB.
using System; namespace ConsoleApp1 { // Контравариантность в обобщенном интерфейсе. // Контравариантный обобщенный интерфейс, // получающий параметром тип T. interface IContraInterface<in T> { void Print(); // без параметров void PrintT(T value); } // Обобщенный класс, который реализует обобщенный интерфейс class GenClass<T> : IContraInterface<T> { // Внутреннее поле класса T value; // Конструктор public GenClass(T _value) { value = _value; } // Метод доступа к внутреннему полю public T GetT() { return value; } // Реализация метода PrintT() интерфейсу. // Метод устанавливает новое значение в переменной value. public void PrintT(T _value) { Console.WriteLine("{0}", _value); } public void Print() { Console.WriteLine(value.ToString()); } } // Некоторая иерархия классов A, B. // Базовый класс. class A { // Внутреннее поле класса A int a; // Конструктор public A(int _a) { a = _a; } // Метод доступа к полю a public int GetA() { return a; } // Метод доступа к полю a в строковом виде через класс Object public override string ToString() { return "a = " + Convert.ToString(a); } } // Производный класс class B : A { // Внутреннее поле int b; // Конструктор public B(int _a, int _b) : base(_a) { b = _b; } // Метод доступа к полю b public int GetB() { return b; } // Метод доступа к полю b в строковом виде через класс Object public override string ToString() { return "b = " + Convert.ToString(b); } } class Program { static void Main(string[] args) { // Демонстрация контравариантности // 1. Создать экземпляры классов A, B A objA = new A(10); B objB = new B(15, 20); // 2. Создать две ссылки на контравариантный интерфейс, // которые получают параметрами типа класы A и B IContraInterface<A> refA; IContraInterface<B> refB; // 3. Создать экземпляры класса MyContraclass<T>, // в котором параметрами типа выступают классы A, B. // Соответственно, конструкторы класса получают объекты objA, objB. GenClass<A> objGenClassA = new GenClass<A>(objA); GenClass<B> objGenClassB = new GenClass<B>(objB); // 4. Демонстрация контравариантности на примере ссылок refA, refB refA = objGenClassA; // можно в любом случае refA.Print(); // a = 10 refB = objGenClassB; // можно в любом случае refB.Print(); // b = 20 // так присваивать можно благодаря контравариантности refB = objGenClassA; // здесь нет строгого соблюдения типов refB.Print(); // a = 10 // так нельзя, чтобы это сделать нужно реализовать ковариантность // refA = objGenClassB; // здесь ошибка компиляции // Вызвать другой метод интерфейса PrintT(). GenClass<A> objGenClassA2 = new GenClass<A>(new A(777)); // так можно благодаря контравариантности refB = objGenClassA2; // GenClass<B> <= GenClass<A> refB.Print(); // a = 777 // Вызвать другой метод интерфейса PrintT(). refB.PrintT(new B(200, 300)); // b = 300 Console.WriteLine("Ok"); Console.ReadKey(); } } }
Результат выполнения программы
a = 10 b = 20 a = 10 a = 777 b = 300 Ok
⇑
4. Рисунок, объясняющий контравариантность
На рисунке 1 изображен один интерфейс и минимальный набор классов, которые необходимы для раскрытия деталей реализации контравариантности. Красными стрелками выделены соответствующие фрагменты кода.
Рисунок 1. Применение контравариантности для иерархии классов A, B
⇑
5. Наследование в контравариантных интерфейсах
Контравариантные интерфейсы могут наследовать друг друга. Общий синтаксис наследования контравариантных интерфейсов, получающих параметром тип T, следующий
interface IBaseInterface<in T> { ... } interface IDerivedInterface<in T> : IBaseInterface<T> { ... }
здесь
- IBaseInterface<in T> – базовый контравариантный интерфейс;
- IDerivedInterface<in T> – унаследованный контравариантный интерфейс. Если в унаследованном интерфейсе опустить модификатор in, то этот интерфейс не будет поддерживать контравариантность.
⇑
6. Ограничения, накладываемые на контравариантность
На контравариантность накладываются следующие ограничения:
- контравариантность может быть применена только для ссылочных типов (class);
- методы, которые объявляются в контравариантных обобщенных интерфейсах не должны возвращать обобщенный тип T;
- методы, которые объявляются в контравариантных интерфейсах могут получать тип T в качестве параметра.
⇑
Связанные темы
- Иерархии обобщенных классов. Обобщенный базовый и производный классы
- Ковариантность в обобщенных интерфейсах. Ключевое слово out
⇑