Please help me understand polymorphism when using generics in c#
Asked Answered
A

3

9

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.

Autoeroticism answered 25/8, 2010 at 12:4 Comment(4)
This is where people trot out scary words like covariance and contravariance.Inhabiter
@Greg: On the plus side, I feel like my own understanding of these concepts has really been fleshed out recently due to the plethora of questions like this that have been popping up!Honea
The concepts are good, but the names are scary. :)Inhabiter
@Greg: Agreed. I propose we change them to gobbling and spewing.Honea
W
4

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.

Wrongheaded answered 25/8, 2010 at 12:9 Comment(0)
H
3

Wow, this question's been coming up a lot lately.

Short answer: No, this isn't possible. Here's what is possible:

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

And here's why what you tried isn't possible (taken from this recent answer of mine):

Consider the List<T> type. Say you have a List<string> and a List<object>. string derives from object, but it does not follow that List<string> derives from List<object>; if it did, then you could have code like this:

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

The above code illustrates what it means to be (and not to be) a covariant type. Note that casting a type T<D> to another type T<B> where D derives from B is possible (in .NET 4.0) if T is covariant; a generic type is covariant if its generic type argument only ever appears in the form of output -- i.e., read-only properties and function return values.

Think of it this way: if some type T<B> always supplies a B, then one that always supplies a D (T<D>) will be able to operate as a T<B> since all Ds are Bs.

Incidentally, a type is contravariant if its generic type parameter only ever appears in the form of input -- i.e., method parameters. If a type T<B> is contravariant then it can be cast to a T<D>, as strange as that may seem.

Think of it this way: if some type T<B> always requires a B, then it can step in for one that always requires a D since, again, all Ds are Bs.

Your MyContainer class is neither covariant nor contravariant because its type parameter appears in both contexts -- as input (via Contents.Add) and as output (via the Contents property itself).

Honea answered 25/8, 2010 at 12:13 Comment(0)
D
1

this is problem of covariance

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

you can't cast MyContainer<MyClass> to MyContainer<IMyInterface> because then you could do things like Contents.Add(new AnotherClassThatImplementsIMyInterface())

Derivation answered 25/8, 2010 at 12:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.