2010-08-25 2 views
9

У меня возникла проблема с пониманием того, как полиморфизм работает при использовании дженериков. В качестве примера, я определил следующую программу:Пожалуйста, помогите мне понять полиморфизм при использовании дженериков в C#

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; 

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

У меня есть план полностью взломать эту проблему, если это необходимо, но я бы предпочел сделать это правильно.

Заранее спасибо.

+2

Здесь люди рыскают страшные слова, такие как ковариация и контравариантность. – Greg

+0

@Greg: С положительной стороны, я чувствую, что мое * собственное понимание этих концепций действительно появилось недавно из-за множества таких вопросов, которые возникали! –

+0

Концепции хороши, но имена страшны. :) – Greg

ответ

4

Примечания: Во всех случаях вам придется инициализировать 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<T> класса, как это:

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.

3

Wow, этот вопрос был придумывают много в последнее время.

Короткий ответ: Нет, это невозможно.Вот что это возможно:

public void CallAllMethodsInContainer<T>(MyContainer<T> container) where T : IMyInterface 
{ 
    foreach (IMyInterface myClass in container.Contents) 
    { 
     myClass.MyMethod(); 
    } 
} 

И вот почему то, что вы пытались не возможно (из this recent answer of mine):

Рассмотрим List<T> типа. Скажем, у вас есть List<string> и List<object>. строка выводится из объекта, но из этого не следует, что List<string> происходит от List<object>; если это так, то вы могли бы иметь такой код:

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)); 

Приведенный выше код показывает, что значит быть (и не быть) covariant type. Обратите внимание, что отливка типа T<D> на другой тип T<B>, где D происходит от B, возможно (в .NET 4.0), если T is covariant; общий тип является ковариантным, если его аргумент общего типа только когда-либо появляется в виде вывода - то есть свойства только для чтения и возвращаемые значения функции.

Подумайте об этом так: если какой-то тип T<B> всегда поставляет B, то один, который всегда поставляет D (T<D>) будет иметь возможность работать как T<B>, так как все D s являются B s.

Кстати, тип contravariant, если его параметр типового типа только когда-либо появляется в виде ввода - то есть параметров метода. Если тип T<B> контравариантен, тогда он может быть отлит до T<D>, как это ни странно.

Подумайте об этом так: если какой-то тип T<B> всегда требует B, то он может уйти в тот, который всегда требует D так, опять же, все D s являются B s.

MyContainer Ваш класс не является ни ковариантны ни контравариантен, поскольку его параметр типа появляется в обоих контекстах - в качестве входных данных (через Contents.Add) и в качестве выходного сигнала (через Contents самого имущества).

Смежные вопросы