Пользователь
0,1
рейтинг
4 ноября 2008 в 23:01

Разработка → Новые возможности C# 4.0. Часть 3: Ковариантность обобщений перевод

.NET*
Когда обобщения(generic) пришли к нам вместе с C# 2.0, они стали одной из лучших возможностей в этом языке. Те, кто когда-либо создавал классы строготипизированных коллекций в C# 1.0 знает, насколько они упростили нам жизнь и уменьшили количество кода. Единственная проблема заключалась в том, что обобщенные типы не следовали тем же правилам наследования, которые были в силе для обычных типов.

Начнем, пожалуй с определения двух простых классов, которые мы будем использовать в этой статье:
  1. public class Shape
  2. {
  3. }
  4.  
  5. public class Circle : Shape
  6. {
  7. }
* This source code was highlighted with Source Code Highlighter.
Это классическая иерархия классов, которая пока ничего конкретного не делает, но это нам сейчас и не нужно. Теперь определим dummy-класс, который может быть контейнером для любого типа.
  1. public interface IContainer<T>
  2. {
  3.   T GetItem();
  4. }
  5.  
  6. public class Container<T>: IContainer<T>
  7. {
  8.   private T item;
  9.  
  10.   public Container(T item)
  11.   {
  12.     this.item = item;
  13.   }
  14.  
  15.   public T GetItem()
  16.   {
  17.     return item;
  18.   }
  19. }
* This source code was highlighted with Source Code Highlighter.
У нас есть иерархия и контейнер. Теперь посмотрим на то, что мы сейчас не можем делать в текущей версии C# — 3.0.
  1. static void Main(string[] args)
  2. {      
  3.   IContainer<Shape> list = GetList();
  4. }
  5.  
  6. public static IContainer<Shape> GetList()
  7. {
  8.   return new Container<Circle>(new Circle());
  9. }
* This source code was highlighted with Source Code Highlighter.
У нас есть метод GetList, у которого тип возращаемого значения определен как IContainer<Shape>, а возвращает он Container<Circle>. Так как Circle наследуется от Shape, а Container реализует интерфейс IContainer может показаться, что это сработает. Но, как вы догадались, C# 3.0 на это не способен.

В C# 4.0 у нас есть путь заставить это работать — нам нужно просто добавить ключевое слово out к параметру-типу в нашем определении интерфейса IContainer (замечу, что ковариантность в C# 4.0 ограничена интерфейсами и делегатам).
  1. public interface IContainer<out T>
  2. {
  3.   T GetItem();
  4. }
* This source code was highlighted with Source Code Highlighter.
Данная конструкция говорит компилятору, что тип T ковариантен, что означет то, что любой IContainer<T> будет принимать любой тип эквивалентный или более конкретный, чем T. Как мы видели выше, типом возвращаемого значения был IContainer<Shape>, но если мы поставим параметр out к нашему интерфейсу, то мы легко сможем вернуть и IContainer<Circle>.Так почему же решено использовать ключевое слово out? Все потому, что когда вы определяете параметр-тип как ковариантный вы сможете только вернуть этот тип из интерфейса. Например такая контрукция недопустима:
  1. public interface IContainer<out T>
  2. {
  3.   void SetItem(T item);
  4.   T GetItem();
  5. }
* This source code was highlighted with Source Code Highlighter.
Но почему это не будет работать? Ответ на самом деле очень прост — безопасность типов. Давайте посмотрим на последствия того, что мы сделали:
  1. static void Main(string[] args)
  2. {
  3.   IContainer<Shape> container = new Container<Circle>();
  4.   SetItem(container);
  5. }
  6.  
  7. public static void SetItem(IContainer<Shape> container)
  8. {
  9.   container.SetItem(new Square()); // BOOM!!!
  10. }
* This source code was highlighted with Source Code Highlighter.
Так как T ковариантен и, поэтому, мы можем присвоить Container<Circle> к переменной типа IContainer<Shape>, передав его дальше в наш статический метод SetItem, который принимает параметр типа IContainer<Shape> и, затем мы берем этот парметр и пытаемся добавить переменную типа Square в него. Кажется, что все верно — тип параметра IContainer<Shape> и это дает нам право добавить в него Square. Неверно. Это выражение «взорвется», так как мы пытаемся добавить Square в контейнер, который держит Circle. Поэтому, ключевым словом out, они ограничили ковариантность только одним направлением.

Вам, наверное, интересно как это все реализовано в CLR 4.0? В реализации нет нужды. Обобщенные типы работали в CLR 2.0 и они уже позволяли подобное поведение. Так как C# пытался сохранить безопасность типов — нам такое не дозволялось, но CLR справляется с этим на раз. И, небольшая ремарка — массивы в C# позволяют такое поведение, так что попробуйте. Я надеюсь, что вам понравилась эта статья и новая в этой серии не за горами!

Перевод статьи C# 4.0 New Features Part 3 — Generic Covariance

Кросспост из моего блога

Перевод: Justin Etheredge
Сергей @SynteZZZ
карма
31,5
рейтинг 0,1
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (26)

  • +1
    :))) отлично! возможно местами упростит работу :)
  • +2
    Ага, в прошлом топике я упоминал, что шарп стремительно летит к плюсам, так нет же, не оценили.
    Дык нате, ковариантные типы, как наглядный пример. Наконец-то, но что мы видим? В однеу сторону? О боже! Как же так? Ах, безопасность.
    Да-да, все ближе и ближе к плюсам, что бы там ни говорили.
    • +1
      скорее к функциональным языкам, чем к плюсам
    • +2
      В плюсах это есть?
      • 0
        Да, там есть Return Type Covariance:
        class Mammal
        {
        public:
          virtual Mammal* GetValue();
        };

        class Dog : public Mammal
        {
        public:
          virtual Dog* GetValue();
        };


        * This source code was highlighted with Source Code Highlighter.
  • +1
    жду новой статьи, спасибо
  • +1
    Как раз на прошлой неделе решал подобную задачу на третем фреймворке:
    Была коллекция List, и хотел к ней добавить элементы коллекции Listчерез AddRange. Ну, как и понятно — компилятор ругнулся. Но вот так: comparableList.AddRange( stringList.ToList() ) всё прокатило! ;)
  • +1
    Мне уже любопытно, что же приподнесет нам C# 5.0…
    • –2
      Nemerle?
  • –3
    Статья хорошая, но форматирование кода отвратительное. Лучше бы его переформачили при переносе из оригинала.
    • +2
      А что конкретно вам не нравится? Классическое форматирование кода для C#. Вы видимо просто не привыкли к такому.
      • –4
        Открывающие фигурные скобки на новой строке, не компактно. Ну да ладно, об этом можно спорить бесконечно. :-)
        • +3
          Спорить можно долго, но MS рекомендует использовать именно такой code convention и сама использует его (например, в МСДН). Кроме того я видел такие рекомендации и в MS C# Coding Standart. Ну а ИМХО такой код читабельнее — доводилось работать с разным форматированием.
        • +2
          Компактно удобно в книгах, здесь у вас места хоть отбавляй. Зато более наглядно.
  • +1
    Отличная фича, несколько раз сталкивался с необходимостью ее использования и жалел, что ее нет в шарпе 2.0 и 3.0.
  • 0
    Спасибо за перевод и труд в его оыормлении!
  • 0
    Интересно, что многие нововведения ничего не изменяют в CLR. Т.е. они сделаны на уровне компилятора. Почему это раньше нельзя было сделать?
    • 0
      Тогда не было бы смысла выпускать C# 4.0 ;)
    • 0
      Дайте подумать… Наверное, потому что на то, чтобы что-то сделать, нужно время?
  • 0
    Жаль только что до дотнета 4 конечные пользователи обновятся ой как нескоро…
    Ведь у большинства то даже 3.0 не стоит.
    • 0
      Все эти навороты — изменение компилятора, а не CLR. Например, фичи 3тьего можно компилировать под второй.
      • +1
        Это как тогда компилировать под 2ю версию код для 3й? ;)
        • +1
          В 2008 студии создаешь консольное приложение, в референсах оставляешь только System.Core (ей надо проставить «Copy Local»), используешь LINQ, собираешь, копируешь на тазик, где установлен только .NET 2.0 и пробуешь запустить.

          В простейшем случае достаточно.
          • 0
            Спасибо!
            Еще немного и мы узнаем способ, как все это хозяйство заставить работать на .NET 1.0
    • 0
      У конечных пользователей может стоять хоть 2.0.

      Все последние изменения (начиная с .NET 3.0) не затрагивают CLR, а лишь компиляторы и библиотеки классов.
  • 0
    Там ещё и контрвариантность таких обобщённых интерфейсов появится.
    Пока же в есть только вариантности массивов и делегатов.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.