Search code examples
c#asp.netgenericsasp.net-mvc-2nested-generics

Please help me understand polymorphism when using generics in c#


I am having a problem understanding how polymorphism works when using generics. As an example, I have defined the following program:

public interface IMyInterface
{
    void MyMethod();
}

public class MyClass : IMyInterface
{
    public void MyMethod()
    {
    }
}

public class MyContainer<T> where T : IMyInterface
{
    public IList<T> Contents;
}

I can then do this, which works just fine:

MyContainer<MyClass> container = new MyContainer<MyClass>();
container.Contents.Add(new MyClass());

I have many classes that implement MyInterface. I would like to write a method that can accept all MyContainer objects:

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

Now, I'd like to call this method.

MyContainer<MyClass> container = new MyContainer<MyClass>();
container.Contents.Add(new MyClass());
this.CallAllMethodsInContainer(container);

That didn't work. Surely, because MyClass implements IMyInterface, I should be able to just cast it?

MyContainer<IMyInterface> newContainer = (MyContainer<IMyInterface>)container;

That didn't work either. I can definitely cast a normal MyClass to IMyInterface:

MyClass newClass = new MyClass();
IMyInterface myInterface = (IMyInterface)newClass;

So, at least I haven't completely misunderstood that. I am unsure exactly how I am to write a method that accepts a generic collection of classes that conform to the same interface.

I have a plan to completely hack around this problem if need be, but I would really prefer to do it properly.

Thank you in advance.


Solution

  • Note: In all cases, you will have to initialize the Contents field to a concrete object that implements IList<?>

    When you keep the generic constraint, you can do:

    public IList<T> Contents = new List<T>();
    

    When you don't, you can do:

    public IList<MyInterface> Contents = new List<MyInterface>();
    

    Method 1:

    Change the method to:

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

    and the snippet to:

    MyContainer<MyClass> container = new MyContainer<MyClass>();
    container.Contents.Add(new MyClass());
    this.CallAllMethodsInContainer(container);
    

    Method 2:

    Alternatively, move the CallAllMethodsInContainer method to the MyContainer<T> class like this:

    public void CallAllMyMethodsInContents()
        {
            foreach (T myClass in Contents)
            {
                myClass.MyMethod();
            }
        }
    

    and change the snippet to:

    MyContainer<MyClass> container = new MyContainer<MyClass>();
    container.Contents.Add(new MyClass());
    container.CallAllMyMethodsInContents();
    

    Method 3:

    EDIT: Yet another alternative is to remove the generic constraint from the MyContainer class like this:

    public class MyContainer
    {
        public IList<MyInterface> Contents;
    }
    

    and to change the method signature to

      public void CallAllMethodsInContainer(MyContainer container)
    

    Then the snippet should work as:

    MyContainer container = new MyContainer();
    container.Contents.Add(new MyClass());
    this.CallAllMethodsInContainer(container);
    

    Note that with this alternative, the container's Contents list will accept any combination of objects that implement MyInterface.