Reputation: 19793
I have the following code:
var commitmentItems = new List<CommitmentItem<ITransaction>>();
commitmentItems.Add(new CapitalCallCommitmentItem());
And I get the following error:
Argument '1': cannot convert from 'Models.CapitalCallCommitmentItem' to
'Models.CommitmentItem<Models.ITransaction>'
However, CapitalCallCommitmentItem
inherits from CommitmentItem<CapitalCall>
, and CapitalCall
implements ITransaction
. So why the error?
Here is a better example:
CapitalCall
implements ITransaction
var test = new List<ITransaction>();
test.Add(new CapitalCall());
var test2 = new List<List<ITransaction>>();
test.Add(new List<CapitalCall>()); // error.
Upvotes: 1
Views: 315
Reputation: 17910
Note - I think Lucero and Eric Lippert himself have provided better direct answers to the question, but I think this is still valuable supplementary material.
Because C# 3.0 doesn't support covariance or contravariance of generic arguments. (And C# 4.0 has limited support for interfaces only.) See here for an explanation of covariance and contravariance, and some insight into the thinking that went on as the C# team were looking at putting this features into C# 4.0:
Covariance and Contravariance in C#, Part Two: Array Covariance
Covariance and Contravariance in C#, Part Three: Method Group Conversion Variance
Covariance and Contravariance in C#, Part Four: Real Delegate Variance
Covariance and Contravariance In C#, Part Five: Higher Order Functions Hurt My Brain
Actually, he just keeps writing and writing! Here's everything he's tagged with "covariance and contravariance".
Upvotes: 1
Reputation: 128327
Understanding why this doesn't work can be kind of tricky, so here's an analogous example, replacing the classes in your code with some well-known classes from the framework to act as placeholders and (hopefully) illustrate the potential pitfalls of such desired functionality:
// Note: replacing CommitmentItem<T> in your example with ICollection<T>
// and ITransaction with object.
var list = new List<ICollection<object>>();
// If the behavior you wanted were possible, then this should be possible, since:
// 1. List<string> implements ICollection<string>; and
// 2. string inherits from object.
list.Add(new List<string>());
// Now, since list is typed as List<ICollection<object>>, our innerList variable
// should be accessible as an ICollection<object>.
ICollection<object> innerList = list[0];
// But innerList is REALLY a List<string>, so although this SHOULD be
// possible based on innerList's supposed type (ICollection<object>),
// it is NOT legal due to innerList's actual type (List<string>).
// This would constitute undefined behavior.
innerList.Add(new object());
Upvotes: 1
Reputation: 660038
Let's shorten these names.
C = CapitalCallCommentItem
D = CommitmentItem
E = CapitalCall
I = ITransaction
So your question is that you have:
interface I { }
class D<T>
{
public M(T t) { }
}
class C : D<E> { }
class E : I { }
And your question is "why is this illegal?"
D<E> c = new C(); // legal
D<I> d = c; // illegal
Suppose that was legal and deduce an error. c has a method M which takes an E. Now you say
class F : I { }
Suppose it was legal to assign c to d. Then it would also be legal to call d.M(new F()) because F implements I. But d.M is a method that takes an E, not an F.
Allowing this feature enables you to write programs that compile cleanly and then violate type safety at runtime. The C# language has been carefully designed so that the number of situations in which the type system can be violated at runtime are at a minimum.
Upvotes: 6
Reputation: 172240
Because "A
is subtype of B
" does not imply that "X<A>
is a subtype of X<B>
".
Let me give you an example. Assume that CommitmentItem<T>
has a method Commit(T t)
, and consider the following function:
void DoSomething(CommitmentItem<ITransaction> item) {
item.Commit(new SomethingElseCall());
}
This should work, since SomethingElseCall
is a subtype of ITransaction
, just like CapitalCall
.
Now assume that CommitmentItem<CapitalCall>
were a subtype of CommitmentItem<ITransaction>
. Then you could do the following:
DoSomething(new CommitmentItem<CapitalCall>());
What would happen? You'd get a type error in the middle of DoSomething
, because a SomethingElseCall
is passed where a CapitalCall
was expected. Thus, CommitmentItem<CapitalCall>
is not a subtype of CommitmentItem<ITransaction>
.
In Java, this problem can be solved by using the extends
and super
keywords, cf. question 2575363. Unfortunately, C# lacks such a keyword.
Upvotes: 1
Reputation: 60190
Because that would need CommitmentItem<CapitalCall>
to be covariant so that it is assignable to CommitmentItem<ITransaction>
, which it currently not supported.
C# 4 added support for co- and contravariance in interfaces, but not for classes.
Therefore, if you're using C# 4 and you can use an interface such as ICommitmentItem<>
instead of CommitmentItem<>
, you might be able to get what you want by using the new features of C# 4.
Upvotes: 6