Пожалуйста, помогите мне понять полиморфизм при использовании обобщений в 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;

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

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

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

9
задан Steve Rukuts 25 August 2010 в 12:04
поделиться

3 ответа

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

4
ответ дан 4 December 2019 в 22:26
поделиться

это проблема ковариации

http://msdn.microsoft.com/en-us/library/dd799517.aspx

вы не можете преобразовать MyContainer в MyContainer , потому что тогда вы могли бы делать такие вещи, как Contents.Add (new AnotherClassThatImplementsIMyInterface ())

1
ответ дан 4 December 2019 в 22:26
поделиться

Вау, этот вопрос в последнее время часто возникает.

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

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 собственно свойство).

3
ответ дан 4 December 2019 в 22:26
поделиться
Другие вопросы по тегам:

Похожие вопросы: