Shawn
Shawn

Reputation: 19793

Why won't this cast work?

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

Answers (5)

Weeble
Weeble

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:

Actually, he just keeps writing and writing! Here's everything he's tagged with "covariance and contravariance".

Upvotes: 1

Dan Tao
Dan Tao

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

Eric Lippert
Eric Lippert

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

Heinzi
Heinzi

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

Lucero
Lucero

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

Related Questions