Добавить в цитаты Настройки чтения

Страница 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&lt;int, double&gt; ok = new Gen&lt;int, double&gt;();</b>

<b>    Gen&lt;int, int&gt; notOK = new Gen&lt;int, int&gt;();</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&lt;int, int&gt; notOK = new Gen&lt;int, int&gt;();</b>

В данном случае оба типа, Т и V, заменяются типом int. В итоге оба варианта метода Set() оказываются совершенно одинаковыми, что, разумеется, приводит к ошибке. Следовательно, при последующей попытке вызвать метод Set() для объекта notOK в методе Main() появится сообщение об ошибке вследствие неоднозначности во время компиляции.

Как правило, методы с параметрами типа перегружаются при условии, что объект конструируемого типа не приводит к конфликту. Следует, однако, иметь в виду, что ограничения на типы не учитываются при разрешении конфликтов, возникающих при перегрузке методов. Поэтому ограничения на типы нельзя использовать для исключения неоднозначности. Конструкторы, операторы и индексаторы с параметрами типа могут быть перегружены аналогично конструкторам по тем же самым правилам.

Ковариантность и контравариантность в параметрах обобщенного типа

В главе 15 ковариантность и контравариантность были рассмотрены в связи с необобщенными делегатами. Эта форма ковариантности и контравариантности по-прежнему поддерживается в С#, поскольку она очень полезна. Но в версии C# 4.0 возможности ковариантности и контравариантности были расширены до параметров обобщенного типа, применяемых в обобщенных интерфейсах и делегатах. Ковариантность и контравариантность применяется, главным образом, для рационального разрешения особых ситуаций, возникающих в связи с применением обобщенных интерфейсов и делегатов, определенных в среде .NET Framework. И поэтому некоторые интерфейсы и делегаты, определенные в библиотеке, были обновлены, чтобы использовать ковариантность и контравариантность параметров типа. Разумеется, преимуществами ковариантности и контравариантности можно также воспользоваться в интерфейсах и делегатах, создаваемых собственными силами. В этом разделе механизмы ковариантности и контравариантности параметров типа поясняются на конкретных примерах.

Применительно к обобщенному интерфейсу ковариантность служит средством, разрешающим методу возвращать тип, производный от класса, указанного в параметре типа. В прошлом возвращаемый тип должен был в точности соответствовать параметру типа в силу строгой проверки обобщений на соответствие типов. Ковариантность смягчает это строгое правило таким образом, чтобы обеспечить типовую безопасность. Параметр ковариантного типа объявляется с помощью ключевого слова out, которое предваряет имя этого параметра.

Для того чтобы стали понятнее последствия применения ковариантности, обратимся к конкретному примеру. Ниже приведен очень простой интерфейс IMyCoVarGenIF, в котором применяется ковариантность.

<b>//В этом обобщенном интерфейсе поддерживается ковариантность,</b>

<b>public interface IMyCoVarGenIF&lt;out Т&gt; {</b>

<b>  Т GetObject();</b>

<b>}</b>

Обратите особое внимание на то, как объявляется параметр обобщенного типа Т. Его имени предшествует ключевое слово out. В данном контексте ключевое слово out обозначает, что обобщенный тип Т является ковариантным. А раз он ковариантный, то метод GetObject() может возвращать ссылку на обобщенный тип Т или же ссылку на любой класс, производный от типа Т.

Несмотря на свою ковариантность по отношению к обобщенному типу Т, интерфейс IMyCoVarGenIF реализуется аналогично любому другому обобщенному интерфейсу. Ниже приведен пример реализации этого интерфейса в классе MyClass.

<b>// Реализовать интерфейс IMyCoVarGenIF.</b>

<b>class MyClass&lt;T&gt; : IMyCoVarGenIF&lt;T&gt; {</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>