Ковариантность и контравариантность в обобщениях

96

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

Применение ковариантности в обобщенном интерфейсе

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

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

// В этом обобщенном интерфейсе поддерживается ковариантность,
public interface IMyInfo<out Т> {
   T GetObject();
}

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

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

public interface IMyCoVarGenIF2<out Т> {
  void M<V>() where V:T; // Ошибка, ковариантный тип T нельзя использовать как ограничение
}

Применение контравариантности в обобщенном интерфейсе

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

Для того чтобы стали понятнее последствия применения ковариантности, вновь обратимся к конкретному примеру. Ниже приведен обобщенный интерфейс IMyContraVarGenlF контравариантного типа. В нем указывается контравариантный параметр обобщенного типа Т, который используется в объявлении метода Show():

// Это обобщенный интерфейс, поддерживающий контравариантность.
public interface IMyContraVarGenlFc<in Т> {
   void Show(T obj);
}

Как видите, обобщенный тип T указывается в данном интерфейсе как контравариантный с помощью ключевого слова in, предшествующего имени его параметра. Обратите также внимание на то, что T является параметром типа для аргумента obj в методе Show().

Контравариантность оказывается пригодной только для ссылочных типов, а параметр контравариантного типа можно применять только к аргументам методов. Следовательно, ключевое слово in нельзя указывать в параметре типа, используемом в качестве возвращаемого типа.

Использование ковариантности и контравариантности интерфейсов иллюстрирует следующий пример:

using System;

namespace ConsoleApplication1
{
    public interface IFont
    {
        string Name();
        int FontSize();
    }

    // Обобщенный интерфейс, имеющий ковариантность
    // по отношению к своему обобщенному типу
    public interface IFontColor<out T> : IFont
    {
        T BaseAndColorInfoFont();
    }

    // Обобщенный интерфейс обеспечивающий контравариантность
    public interface IFontInfo<in T> : IFont
    {
        void MyFontInfo(T obj);
    }

    class Font : IFontInfo<Font>
    {
        string name;
        int fontsize;

        public Font(string name, int fontsize)
        {
            this.name = name;
            this.fontsize = fontsize;
        }

        // Реализуем интерфейс IFontInfo
        public string Name()
        {
            return name;
        }

        public int FontSize()
        {
            return fontsize;
        }

        public virtual void MyFontInfo(Font obj)
        {
            Console.WriteLine(@"Информация о шрифте
__________________

Название:{0}
Размер: {1}px",obj.Name(),obj.FontSize());
        }
    }

    class FontColor : Font, IFontColor<FontColor>
    {
        string color;

        public FontColor(string name, int fontsize, string color)
            : base(name, fontsize)
        {
            this.color = color;
        }

        public FontColor BaseAndColorInfoFont()
        {
            FontColor fontBase = new FontColor("Arial", 20, "Red");
            return fontBase;
        }

        public override void MyFontInfo(Font obj)
        {
            base.MyFontInfo(obj);
            Console.WriteLine("Цвет: "+color);
        }
    }

    class Program
    {
        static void Main()
        {
            // Создадим экземпляр класса Font, ссылающийся
            // на класс FontColor благодаря ковариантности
            Font ob = new FontColor("Times New Roman", 22, "Black");
        }
    }
}
Пройди тесты
Лучший чат для C# программистов