Lets assume we have two classes A
and B
in which B derives from A.
class A
{
}
class B : A
{
}
class C<T>
{
}
Now C<B>
doesn't derive from C<A>
.
This gets me a bit confused, because I tough that "everything" I can do with A, I can do with B.
I'm sure that i'm missing something because it seems contradicting to the basics of OOP.
Is there any concrete justification and example for that?
Edit:
I've read many similar posts like:
In C#, why can't a List<string> object be stored in a List<object> variable
Why can't I cast a dictionary of one value type to dictionary of another value type when the value types can be cast from one another?
Casting from IEnumerable<Object> to IEnumerable<string>
But in every post the answer is focused in a specific class/data structure, and making this specific code to work, rather then why it's not possible in general?
I though that "everything" I can do with A, I can do with B. I'm sure that I'm missing something because it seems contradicting to the basics of OOP.
Let's start by stating the basic principle that you are alluding to. From Wikipedia:
If S is a subtype of T, then objects of type T may be replaced with objects of type S without altering any of the desirable properties of that program
What you are missing is the key word: objects.
You can substitute an object of type B
anywhere that an object of type A
is required. But it is not the case that everything you can do with the type A
you can also do with the type B
. If someone asks you for a car and you give them a 1995 Ford Escort, they can use that as a car. But that doesn't mean that you can do a search-and-replace in Wikipedia and change every usage of the word "car" to "1995 Ford Escort" and have the articles still be correct!
In fact, the reason that you cannot use C<B>
where C<A>
is expected is because doing so violates the Liskov Substitution Principle:
class A {}
class B : A {}
class D : A {}
class C<T>
{
public T M(T t) { ... }
}
...
C<B> cb = new C<B>();
C<A> ca = cb; // Suppose this were legal
ca.M(new D());
You can use an object of type D
anywhere you can use an A
, but ca
is really a cb
, and you cannot use a D
anywhere you can use a B
! Therefore you cannot use a C<B>
anywhere you can use a C<A>
.
C# 4 and above do allow this kind of covariance when the compiler can prove that doing so is safe. Do a web search on C# covariance for details; you'll find plenty of articles on the subject. (Including mine.)
Look at this example:
class C<T>
{
public T Value { get; set; }
}
// ...
C<B> cb = new C<B>();
Now, if C<B>
derived from C<A>
, the following would be possible:
C<A> ca = cb;
ca.Value = new A(); // ca.Value is defined by C<A> and thus typed to A
Imagine what happens when we access cb.Value
now, assuming that the stored instance can only be of type B
, yet it actually is of type A
.
Therefore, C<B>
does not derive from (is not assignment-compatiable to) C<A>
.
Well, when you write a class C<T>
, it is all about C
, not T
. T
is merely a type info provided to denote a special C<>
type. C<>
has its own implementation and has no relation at all with T
. When you write:
var c = new C<int>();
the behaviour of variable c
is all in accordance with what you have written in class C<>
. It behaves the same whether T
is int
or string
or anything.
Also note that a class written like C<S>
is a special type and different from C<T>
which is another type, because all the behaviour is confined by it's C
-ness. We never know how is C<>
written and how it will behave. So the way you have described is not how generics play out. If you have an additional class like
class D<T> : C<T>
{
}
you can write:
C<A> c = new D<A>();
You see, it is again possible because C
derives from D
. The left side of the class matters. This is again illegal:
C<A> c = new D<B>();
even though A
derives from B
, because C
of A
has no relationship with D
of B
(or even C
of B
) which is a separate class.
This kind of generic polymorphism is actually meaningless when you think C<A>
as an entirely different type to C<B>
though they have a common behaviour. To illustrate with another example, think about this, a class can be generic by more than one type. Suppose there is
class C<S, T>
{
}
What about C<IList<A>, A>
and C<IList<int>, B>
? Are they type comparable because those two have IList
and A
and B
as generic types? Is C<T>
castable from C<S, T>
? No these are different types in short.
Now if you wan't to make the assignment
C<A> ca = new C<B>();
possible you can write your own implicit converters.
class C<T>
{
public static implicit operator C<T>(C<B> c)
{
type check, explode or return
}
}
//now you may write:
C<A> ca = new C<B>();
//or even
C<A> ca = new D<B>();
Mind you this does not imply inheritance relationship, but merely a conversion operation.
And no information is complete on generics without information on covariance and contravariance (which is applicable only for interfaces and delegates). See O. R. Mapper's answer as to the dangers if this was let to happen. The one case where C# lets this is in case of arrays. And sadly that's a broken design. This is possible in C#
object[] o = new string[1];
o[0] = 1; //explosion
I'm tempted to detail out, but anything I do would make it redundant. See this excellent answer by Eric Lippert for a clear info.