Страница 297 из 502
<b> public void Set(T о) {</b>
<b> ob1 = о;</b>
<b> }</b>
<b> public void Set(V o) {</b>
<b> ob2 = o;</b>
<b> }</b>
<b>}</b>
<b>class AmbiguityDemo {</b>
<b> static void Main() {</b>
<b> Gen<int, double> ok = new Gen<int, double>();</b>
<b> Gen<int, int> notOK = new Gen<int, int>();</b>
<b> ok.Set(10); // верно, поскольку аргументы типа отличаются</b>
<b> notOK.Set(10); // неоднозначно, поскольку аргументы ничем не отличаются!</b>
<b> }</b>
<b>}</b>
Рассмотрим приведенный выше код более подробно. Прежде всего обратите внимание на то, что класс Gen объявляется с двумя параметрами типа: Т и V. В классе Gen метод Set() перегружается по параметрам типа Т и V, как показано ниже.
<b>public void Set (T о) {</b>
<b> ob1 = о;</b>
<b>}</b>
<b>public void Set(V o) {</b>
<b> ob2 = o;</b>
<b>}</b>
Такой подход кажется вполне обоснованным, поскольку типы Т и V ничем внешне не отличаются. Но подобная перегрузка таит в себе потенциальную неоднозначность.
При таком объявлении класса Gen не соблюдается никаких требований к различению типов Т и V. Например, нет ничего принципиально неправильного в том, что объект класса Gen будет сконструирован так, как показано ниже.
<b>Gen<int, int> notOK = new Gen<int, int>();</b>
В данном случае оба типа, Т и V, заменяются типом int. В итоге оба варианта метода Set() оказываются совершенно одинаковыми, что, разумеется, приводит к ошибке. Следовательно, при последующей попытке вызвать метод Set() для объекта notOK в методе Main() появится сообщение об ошибке вследствие неоднозначности во время компиляции.
Как правило, методы с параметрами типа перегружаются при условии, что объект конструируемого типа не приводит к конфликту. Следует, однако, иметь в виду, что ограничения на типы не учитываются при разрешении конфликтов, возникающих при перегрузке методов. Поэтому ограничения на типы нельзя использовать для исключения неоднозначности. Конструкторы, операторы и индексаторы с параметрами типа могут быть перегружены аналогично конструкторам по тем же самым правилам.
Ковариантность и контравариантность в параметрах обобщенного типа
В главе 15 ковариантность и контравариантность были рассмотрены в связи с необобщенными делегатами. Эта форма ковариантности и контравариантности по-прежнему поддерживается в С#, поскольку она очень полезна. Но в версии C# 4.0 возможности ковариантности и контравариантности были расширены до параметров обобщенного типа, применяемых в обобщенных интерфейсах и делегатах. Ковариантность и контравариантность применяется, главным образом, для рационального разрешения особых ситуаций, возникающих в связи с применением обобщенных интерфейсов и делегатов, определенных в среде .NET Framework. И поэтому некоторые интерфейсы и делегаты, определенные в библиотеке, были обновлены, чтобы использовать ковариантность и контравариантность параметров типа. Разумеется, преимуществами ковариантности и контравариантности можно также воспользоваться в интерфейсах и делегатах, создаваемых собственными силами. В этом разделе механизмы ковариантности и контравариантности параметров типа поясняются на конкретных примерах.
Применительно к обобщенному интерфейсу ковариантность служит средством, разрешающим методу возвращать тип, производный от класса, указанного в параметре типа. В прошлом возвращаемый тип должен был в точности соответствовать параметру типа в силу строгой проверки обобщений на соответствие типов. Ковариантность смягчает это строгое правило таким образом, чтобы обеспечить типовую безопасность. Параметр ковариантного типа объявляется с помощью ключевого слова out, которое предваряет имя этого параметра.
Для того чтобы стали понятнее последствия применения ковариантности, обратимся к конкретному примеру. Ниже приведен очень простой интерфейс IMyCoVarGenIF, в котором применяется ковариантность.
<b>//В этом обобщенном интерфейсе поддерживается ковариантность,</b>
<b>public interface IMyCoVarGenIF<out Т> {</b>
<b> Т GetObject();</b>
<b>}</b>
Обратите особое внимание на то, как объявляется параметр обобщенного типа Т. Его имени предшествует ключевое слово out. В данном контексте ключевое слово out обозначает, что обобщенный тип Т является ковариантным. А раз он ковариантный, то метод GetObject() может возвращать ссылку на обобщенный тип Т или же ссылку на любой класс, производный от типа Т.
Несмотря на свою ковариантность по отношению к обобщенному типу Т, интерфейс IMyCoVarGenIF реализуется аналогично любому другому обобщенному интерфейсу. Ниже приведен пример реализации этого интерфейса в классе MyClass.
<b>// Реализовать интерфейс IMyCoVarGenIF.</b>
<b>class MyClass<T> : IMyCoVarGenIF<T> {</b>
<b> T obj;</b>
<b> public MyClass(T v) {</b>
<b> obj = v;</b>
<b> }</b>
<b> public T GetObject() {</b>
<b> return obj;</b>