У меня проблема с пониманием того, как работает полиморфизм при использовании дженериков. В качестве примера я определил следующую программу:
public interface IMyInterface
{
void MyMethod();
}
public class MyClass : IMyInterface
{
public void MyMethod()
{
}
}
public class MyContainer<T> where T : IMyInterface
{
public IList<T> Contents;
}
Затем я могу сделать это, что прекрасно работает:
MyContainer<MyClass> container = new MyContainer<MyClass>();
container.Contents.Add(new MyClass());
У меня есть много классов, которые реализуют MyInterface. Я хотел бы написать метод, который может принимать все объекты MyContainer:
public void CallAllMethodsInContainer(MyContainer<IMyInterface> container)
{
foreach (IMyInterface myClass in container.Contents)
{
myClass.MyMethod();
}
}
Теперь я хотел бы вызвать этот метод.
MyContainer<MyClass> container = new MyContainer<MyClass>();
container.Contents.Add(new MyClass());
this.CallAllMethodsInContainer(container);
Это не сработало. Конечно, поскольку MyClass реализует IMyInterface, я должен иметь возможность просто разыграть его?
MyContainer<IMyInterface> newContainer = (MyContainer<IMyInterface>)container;
Это тоже не сработало. Я определенно могу привести нормальный MyClass к IMyInterface:
MyClass newClass = new MyClass();
IMyInterface myInterface = (IMyInterface)newClass;
Так что, по крайней мере, я не совсем понял это. Я не совсем уверен, как мне написать метод, который принимает общую коллекцию классов, которые соответствуют одному и тому же интерфейсу.
У меня есть план, чтобы полностью обойти эту проблему, если это необходимо, но я бы действительно предпочел сделать это правильно.
Заранее спасибо.
Примечание. Во всех случаях необходимо инициализировать поле Contents
конкретным объектом, который реализует IList>
При сохранении общее ограничение, вы можете сделать:
public IList<T> Contents = new List<T>();
Если вы этого не сделаете, вы можете сделать:
public IList<MyInterface> Contents = new List<MyInterface>();
Способ 1:
Измените метод на:
public void CallAllMethodsInContainer<T>(MyContainer<T> container) where T : IMyInterface
{
foreach (T myClass in container.Contents)
{
myClass.MyMethod();
}
}
и фрагмент на:
MyContainer<MyClass> container = new MyContainer<MyClass>();
container.Contents.Add(new MyClass());
this.CallAllMethodsInContainer(container);
Метод 2:
] В качестве альтернативы переместите метод CallAllMethodsInContainer
в класс MyContainer
следующим образом:
public void CallAllMyMethodsInContents()
{
foreach (T myClass in Contents)
{
myClass.MyMethod();
}
}
и измените фрагмент на:
MyContainer<MyClass> container = new MyContainer<MyClass>();
container.Contents.Add(new MyClass());
container.CallAllMyMethodsInContents();
Метод 3:
EDIT: пока другой альтернативой является удаление общего ограничения из класса MyContainer
следующим образом:
public class MyContainer
{
public IList<MyInterface> Contents;
}
и изменение подписи метода на
public void CallAllMethodsInContainer(MyContainer container)
Тогда фрагмент должен работать следующим образом:
MyContainer container = new MyContainer();
container.Contents.Add(new MyClass());
this.CallAllMethodsInContainer(container);
Обратите внимание, что с этим вариантом, список Contents
контейнера будет принимать любую комбинацию объектов, реализующих MyInterface
.
это проблема ковариации
http://msdn.microsoft.com/en-us/library/dd799517.aspx
вы не можете преобразовать MyContainer
в MyContainer
, потому что тогда вы могли бы делать такие вещи, как Contents.Add (new AnotherClassThatImplementsIMyInterface ())
Вау, этот вопрос в последнее время часто возникает.
Краткий ответ: Нет, это невозможно. Вот что возможно:
public void CallAllMethodsInContainer<T>(MyContainer<T> container) where T : IMyInterface
{
foreach (IMyInterface myClass in container.Contents)
{
myClass.MyMethod();
}
}
И вот почему то, что вы пробовали , невозможно (взято из этого моего недавнего ответа ):
Подумайте тип Список
. Допустим, у вас есть List
и List
. строка является производным от объекта, но из этого не следует, что List
является производным от List
; если бы это было так, то у вас мог бы быть такой код:
var strings = new List<string>();
// If this cast were possible...
var objects = (List<object>)strings;
// ...crap! then you could add a DateTime to a List<string>!
objects.Add(new DateTime(2010, 8, 23));23));
Приведенный выше код иллюстрирует, что значит быть (а не быть) ковариантным типом . Обратите внимание, что приведение типа T
к другому типу T
, где D
происходит от B
, возможно (дюйм. СЕТЬ 4.0) если T
ковариантно ; универсальный тип является ковариантным, если его аргумент универсального типа когда-либо появляется только в форме вывода, то есть свойства только для чтения и возвращаемые значения функции.
Подумайте об этом так: если какой-то тип T
всегда предоставляет B
, то тот, который всегда предоставляет D
( T
) сможет работать как T
, поскольку все D
s являются B
s.
Между прочим, тип является контравариантным , если его параметр универсального типа появляется только в форме ввода, то есть в параметрах метода. Если тип T
контравариантен, то его можно привести к T
, как бы странно это ни показалось.
Подумайте об этом так: если некоторый тип T
всегда требует B
, то он может заменить тот, который всегда требует D
, поскольку, опять же, все D
s являются B
s.
Ваш класс MyContainer
не является ни ковариантным, ни контравариантным, поскольку его параметр типа появляется в обоих контекстах - как вход (через Contents.Add
) и как выход (через Contents
собственно свойство).