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.
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
.
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 D
s are B
s.
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 D
s are B
s.
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With