Пожалуйста, помогите мне понять полиморфизм при использовании обобщений в C # - PullRequest
9 голосов
/ 25 августа 2010

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

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;

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

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

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

Ответы [ 3 ]

4 голосов
/ 25 августа 2010

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

РЕДАКТИРОВАТЬ: еще одна альтернатива заключается в удалении общего ограничения из класса 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 голосов
/ 25 августа 2010

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

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

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

И вот почему то, что вы пытались , не возможно (взято из моего недавнего ответа ):

Рассмотрим тип 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));

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

Между прочим, тип является контравариантным , если его универсальный параметр типа только когда-либо появляется в форме ввода - то есть, параметров метода.Если тип T<B> является контравариантным, его можно привести к T<D>, как бы странно это ни казалось.

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

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

1 голос
/ 25 августа 2010

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

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

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

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