Полное руководство. С# 4.0 - Шилдт Герберт
Шрифт:
Интервал:
Закладка:
Применительно к обобщенному интерфейсу ковариантность служит средством,разрешающим методу возвращать тип, производный от класса, указанного в параметре типа. В прошлом возвращаемый тип должен был в точности соответствоватьпараметру типа в силу строгой проверки обобщений на соответствие типов. Ковариантность смягчает это строгое правило таким образом, чтобы обеспечить типовуюбезопасность. Параметр ковариантного типа объявляется с помощью ключевого словаout, которое предваряет имя этого параметра.
Для того чтобы стали понятнее последствия применения ковариантности, обратимся к конкретному примеру. Ниже приведен очень простой интерфейс IMyCoVarGenIF,в котором применяется ковариантность.// В этом обобщенном интерфейсе поддерживается ковариантность.public interface IMyCoVarGenIF<out Т> { Т GetObject();}
Обратите особое внимание на то, как объявляется параметр обобщенного типа Т.Его имени предшествует ключевое слово out. В данном контексте ключевое слово outобозначает, что обобщенный тип Т является ковариантным. А раз он ковариантный, тометод GetObject() может возвращать ссылку на обобщенный тип Т или же ссылку налюбой класс, производный от типа Т.
Несмотря на свою ковариантность по отношению к обобщенному типу Т, интерфейс IMyCoVarGenIF реализуется аналогично любому другому обобщенному интерфейсу. Ниже приведен пример реализации этого интерфейса в классе MyClass.// Реализовать интерфейс IMyCoVarGenIF.class MyClass<T> : IMyCoVarGenIF<T> { T obj; public MyClass(T v) { obj = v; } public T GetObject() { return obj; }}
Обратите внимание на то, что ключевое слово out не указывается еще раз в выражении, объявляющем реализацию данного интерфейса в классе MyClass. Это не тольконе нужно, но и вредно, поскольку всякая попытка еще раз указать ключевое слово outбудет расцениваться компилятором как ошибка.
А теперь рассмотрим следующую простую реализацию иерархии классов.// Создать простую иерархию классов.class Alpha { string name; public Alpha(string n) { name = n; } public string GetName() { return name; } // ...}class Beta : Alpha { public Beta(string n) : base(n) { } // ...}
Как видите, класс Beta является производным от класса Alpha.С учетом всего изложенного выше, следующая последовательность операций будетсчитаться вполне допустимой.// Создать ссылку из интерфейса IMyCoVarGenIF на объект типа MyClass<Alpha>.// Это вполне допустимо как при наличии ковариантности, так и без нее.IMyCoVarGenIF<Alpha> AlphaRef = new MyClass<Alpha>(new Alpha("Alpha #1"));Console.WriteLine("Имя объекта, на который ссылается переменная AlphaRef: " +AlphaRef.GetObject().GetName());// А теперь создать объект MyClass<Beta> и присвоить его переменной AlphaRef.// *** Эта строка кода вполне допустима благодаря ковариантности. ***AlphaRef = new MyClass<Beta>(new Beta("Beta #1"));Console.WriteLine("Имя объекта, на который теперь ссылается " + "переменная AlphaRef: " + AlphaRef.GetObject().GetName());
Прежде всего, переменной AlphaRef типа IMyCoVarGenIF в этом фрагменте кода присваивается ссылка на объект типа MyClass. Это вполне допустимая операция, поскольку в классе MyClass реализуется интерфейс IMyCoVarGenIF,причем и в том, и в другом в качестве аргумента типа указывается Alpha. Далее имяобъекта выводится на экран при вызове метода GetName() для объекта, возвращаемого методом GetObject(). И эта операция вполне допустима, поскольку Alpha — этои тип, возвращаемый методом GetName(), и обобщенный тип Т. После этого переменной AlphaRef присваивается ссылка на экземпляр объекта типа MyClass,что также допустимо, потому что класс Beta является производным от класса Alpha,а обобщенный тип Т — ковариантным в интерфейсе IMyCoVarGenIF. Если бы любоеиз этих условий не выполнялось, данная операция оказалась бы недопустимой.Ради большей наглядности примера вся рассмотренная выше последовательностьопераций собрана ниже в единую программу.// Продемонстрировать ковариантность в обобщенном интерфейсе.using System;// Этот обобщенный интерфейс поддерживает ковариантность.public interface IMyCoVarGenIF<out Т> { Т GetObject();}// Реализовать интерфейс IMyCoVarGenIF.class MyClass<T> : IMyCoVarGenIF<T> { T obj; public MyClass(T v) { obj = v; } public T GetObject() { return obj; }}// Создать простую иерархию классов.class Alpha { string name; public Alpha(string n) { name = n; } public string GetName() { return name; } // ...}class Beta : Alpha { public Beta(string n) : base(n) { } // ...}class VarianceDemo { static void Main() { // Создать ссылку из интерфейса IMyCoVarGenIF на объект типа MyClass<Alpha>. // Это вполне допустимо как при наличии ковариантности, так и без нее. IMyCoVarGenIF<Alpha> AlphaRef = new MyClass<Alpha>(new Alpha("Alpha #1")); Console.WriteLine("Имя объекта, на который ссылается переменная " + "AlphaRef: " + AlphaRef.GetObject().GetName()); // А теперь создать объект MyClass<Beta> и присвоить его // переменной AlphaRef. // *** Эта строка кода вполне допустима благодаря ковариантности. *** AlphaRef = new MyClass<Beta>(new Beta("Beta #1")); Console.WriteLine("Имя объекта, на который теперь ссылается переменная " + "AlphaRef: " + AlphaRef.GetObject().GetName()); }}
Результат выполнения этой программы выглядит следующим образом.Имя объекта, на который ссылается переменная AlphaRef: Alpha #1Имя объекта, на который теперь ссылается переменная AlphaRef: Beta #1
Следует особо подчеркнуть, что переменной AlphaRef можно присвоить ссылкуна объект типа MyClass благодаря только тому, что обобщенный тип Т указанкак ковариантный в интерфейсе IMyCoVarGenIF. Для того чтобы убедиться в этом,удалите ключевое слово out из объявления параметра обобщенного типа Т в интерфейсе IMyCoVarGenIF и попытайтесь скомпилировать данную программу еще раз.Компиляция завершится неудачно, поскольку строгая проверка на соответствие типовне разрешит теперь подобное присваивание.
Один обобщенный интерфейс может вполне наследовать от другого. Иными словами, обобщенный интерфейс с параметром ковариантного типа можно расширить,как показано ниже.public interface IMyCoVarGenIF2<out Т> : IMyCoVarGenIF<T> { // ...}
Обратите внимание на то, что ключевое слово out указано только в объявлении расширенного интерфейса. Указывать его в объявлении базового интерфейса не только ненужно, но и не допустимо. И последнее замечание: обобщенный тип Т допускается неуказывать как ковариантный в объявлении интерфейса IMyCoVarGenIF2. Но при этомисключается ковариантность, которую может обеспечить расширенный интерфейсIMyCoVarGetIF. Разумеется, возможность сделать интерфейс IMyCoVarGenIF2 инвариантным может потребоваться в некоторых случаях его применения.
На применение ковариантности накладываются некоторые ограничения. Ковариантность параметра типа может распространяться только на тип, возвращаемый методом. Следовательно, ключевое слово out нельзя применять в параметре типа, служащем для объявления параметра метода. Ковариантность оказывается пригоднойтолько для ссылочных типов. Ковариантный тип нельзя использовать в качестве ограничения в интерфейсном методе. Так, следующий интерфейс считается недопустимым.public interface IMyCoVarGenIF2<out Т> { void M<V>() where V:T; // Ошибка, ковариантный тип T нельзя // использовать как ограничение}Применение контравариантности в обобщенном интерфейсе
Применительно к обобщенному интерфейсу контравариантность служит средством, разрешающим методу использовать аргумент, тип которого относится к базовому классу, указанному в соответствующем параметре типа. В прошлом тип аргумента метода должен был в точности соответствовать параметру типа в силу строгойпроверки обобщений на соответствие типов. Контравариантность смягчает это строгоеправило таким образом, чтобы обеспечить типовую безопасность. Параметр контравариантного типа объявляется с помощью ключевого слова in, которое предваряет имяэтого параметра.