Georgii Oleinikov
Georgii Oleinikov

Reputation: 3915

Collection with inherited template class parameter

To me it makes perfect to do like that:

public class A { }
public class B : A { }

public class C
{
    public List<A> b = new List<B>();
}

List expects elements to be of class A, which is also true for List. I know there is a type mismatch, but logically it makes perfect sense for compiler to allow such mismatch. Why not?

Upvotes: 0

Views: 110

Answers (5)

antonijn
antonijn

Reputation: 5760

You can't achieve this using classes. You can using interfaces (look up covariance and contravariance), but not when using an List/IList. Imagine the following scenario:

public class MyClass<T> 
{
    T GetT() { /* Blah blah */ }
    void SetT(T value) { /* Blah Blah */ }
}

Writing this:

MyClass<object> example = new MyClass<string>();

That would work for the first method; example should return an object, and MyClass<string> returns a string, which is legal because of polymorphism.

The second method is more of a problem. Let's say you write this afterwards:

example.SetT(new object());

This is illegal since MyClass<string> expects a string but gets an object. Not good.

You can make this work for interfaces using covariance and contravariance as mentioned before. You could write an interface like this:

public interface Covariant<out T>
{
    T FunctionReturningT();
}

public class MyInterfaceImplementation<T> : Covariant<T>
{
    public T FunctionReturningT() { /* Blah Blah */ }
}

Making the interface covariant, meaning it's legal to write:

Covariant<object> example = new MyInterfaceImplementation<string>();

Whereas you could also write the following:

public interface Contravariant<in T>
{
    void FunctionAskingForT(T value);
}

public class MyInterfaceImplementation<T> : Contravariant<T>
{
    public void FunctionAskingForT(T value) { /* Blah Blah */ }
}

You've just made that interface contravariant, meaning it's legal to write this:

Contravariant<string> example = new MyInterfaceImplementation<object>();

Upvotes: 0

Yochai Timmer
Yochai Timmer

Reputation: 49231

It depends if the generic container is set with a covariant or contravariant generic parameter.

You can check the MSDN to see how it's declared.

Upvotes: 0

zstewart
zstewart

Reputation: 2177

Because if the list is stored in a List<A> variable, the program expects to be able to put objects of type A in that variable. Storing a List<B> object in a List<A> variable would prevent you from being able to put objects of type A in a list which is explicitly declared to be able to hold type A.

That is:

List<A> b = new List<B>()
// compiler knows list should be of type A, so it expects this to work:
b.add(new A());

But if you could assign a List<B> to a List<A> variable, that would produce a type error, even though the compiler knows that the variable b is of type List<A> and so should be able to hold a type A object.

Instead, you just use new List<A> and add elements of type B to it, which is allowed, or you change the type of the variable to List<B>.

Upvotes: 0

Jeppe Stig Nielsen
Jeppe Stig Nielsen

Reputation: 61952

You're expection List<T> to be covariant in T, but it's not.

In C#, some interfaces are covariant, but classes are not. With A and B as above, it is legat to say

IEnumerable<A> b = new List<B>();

or

IReadOnlyList<A> b = new List<B>();

because the interfaces in question are covariant, i.e. declared with "out" as in

public interface IEnumerable<out T> ...

public interface IReadOnlyLies<out T> ...

Upvotes: 0

O. R. Mapper
O. R. Mapper

Reputation: 20722

List<B> is not assignable to List<A> for a good reason.

Let's assume that the mismatch is allowed, and let's imagine the following imaginary method in your class C:

public void DoSomething()
{
    b.Add(new A()); // (1)
    List<B> tmp = (List<B>)b; // (2)
    foreach (B item in tmp) { // (3)
        // ...
    }
}
  • Line (1) works, because b is typed as a list of A items, so naturally, we can add a new A instance to b.
  • Line (2) works, because the instance referenced by b is actually an instance of List<B>, so the cast is valid.
  • Line (3) will crash, because b contains items that are not of type B. The fact that tmp is of type List<B> guarantees that all items of the list are of type B, but this is not the case any more if assigning List<B> to List<A> is allowed.

Therefore, the compiler does not allow this mismatch.

Upvotes: 1

Related Questions