C # Странность приведения типов - интерфейс как универсальный тип - PullRequest
4 голосов
/ 20 января 2010

Я только что натолкнулся на то, что мне кажется странным в приведении типов. У меня есть код, подобный следующему:

interface IMyClass { }

class MyClass: IMyClass { }

class Main
{
  void DoSomething(ICollection<IMyClass> theParameter) { }

  HashSet<MyClass> FillMyClassSet()
  {
    //Do stuff
  }

  void Main()
  {
    HashSet<MyClass> classSet = FillMyClassSet();
    DoSomething(classSet);
  }
}

Когда он попадает в DoSomething (classSet), компилятор жалуется, что он не может привести HashSet к ICollection . Это почему? HashSet реализует ICollection, MyClass реализует IMyClass, так почему же приведение не является допустимым?

Кстати, это не сложно обойти, подумал, что это немного неловко.

void Main()
{
  HashSet<MyClass> classSet = FillMyClassSet();
  HashSet<IMyClass> interfaceSet = new HashSet<IMyClass>();
  foreach(IMyClass item in classSet)
  {
    interfaceSet.Add(item);
  }
  DoSomething(interfaceSet);
}

Для меня тот факт, что это работает, делает невозможным сотворение еще более загадочным.

Ответы [ 3 ]

8 голосов
/ 20 января 2010

Это не будет работать, потому что все экземпляры MyClass, являющиеся IMyClass, не означают автоматически, что все экземпляры HashSet<MyClass> также HashSet<IMyClass>. Если бы это сработало, вы могли бы:

ICollection<IMyClass> h = new HashSet<MyClass>();
h.Add(new OtherClassThatImplementsIMyClass()); // BOOM!

Технически, это не работает, потому что дженерики C # (<= 3.0) инвариантны. C # 4.0 вводит безопасную ковариацию, которая также не помогает в этом случае. Это, однако, помогает, когда интерфейсы используют параметры типа только на входе или только на выходных позициях. Например, вы сможете передать <code>HashSet<MyClass> как IEnumerable<IMyClass> какому-либо методу.

Кстати, они являются более легким обходным путем, чем заполнение вручную другого HashSet, например:

var newSet = new HashSet<IMyClass>(originalSet);

или вы можете использовать метод Cast, если хотите привести набор к IEnumerable<IMyClass>:

IEnumerable<IMyClass> sequence = set.Cast<IMyClass>(set);
2 голосов
/ 20 января 2010

Такой бросок на самом деле не будет безопасным.

Рассмотрим этот код:

class MyOtherClass: IMyClass { }

void DoSomething(ICollection<IMyClass> theParameter) { theParameter.Add(new MyOtherClass(); } 

ICollection<MyClass> myClassSet = ...;
DoSomething(classSet);   //Oops - myClassSet now has a MyOtherClass

То, что вы просите, называется ковариацией; он доступен для IEnumerable<T> (только для чтения) в C # 4.

0 голосов
/ 20 января 2010

Это не странность, это ограничение универсальной дисперсии типов в 2.0. Этот пост в блоге описывает ситуацию достаточно хорошо. Они внесли некоторые улучшения в 4.0 CLR, IIRC.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...