views:

223

answers:

5

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.
+6  A: 

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.

Lucero
For additional reference, see this MSDN article, which pretty much exactly describes several ways to work around this: http://msdn.microsoft.com/en-us/library/ms228359%28VS.80%29.aspx
jdmichal
@jdmichal, thanks for posting the link! It is however outdated in some parts. For the current status of co- and contravariance, have a look at the FAQ: http://blogs.msdn.com/b/csharpfaq/archive/2010/02/16/covariance-and-contravariance-faq.aspx
Lucero
+2  A: 

EDIT - Lucero's link is better because it describes the co- and contravariance mechanism for interfaces that is in C# 4.0. These links are from 2007, but I feel they're still extremely illuminating.

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:

http://blogs.msdn.com/b/ericlippert/archive/2007/10/16/covariance-and-contravariance-in-c-part-one.aspx

http://blogs.msdn.com/b/ericlippert/archive/2007/10/17/covariance-and-contravariance-in-c-part-two-array-covariance.aspx

http://blogs.msdn.com/b/ericlippert/archive/2007/10/19/covariance-and-contravariance-in-c-part-three-member-group-conversion-variance.aspx

http://blogs.msdn.com/b/ericlippert/archive/2007/10/22/covariance-and-contravariance-in-c-part-four-real-delegate-variance.aspx

http://blogs.msdn.com/b/ericlippert/archive/2007/10/24/covariance-and-contravariance-in-c-part-five-higher-order-functions-hurt-my-brain.aspx

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

http://blogs.msdn.com/b/ericlippert/archive/tags/covariance+and+contravariance/

Weeble
interesting, but the article doesnt say anything about covariance in generic arguments?
Shawn Simon
Ah, it's the first in a series. I didn't realise there weren't links between them. I'll add the others...
Weeble
@Weeble: Be careful not to post outdated links. Co- and contravariance on generic interfaces (and delegates) have been introduced with .NET 4 / VS2010.
Lucero
@Weeble: Thanks for mentioning my post. :-)Note that not only interfaces, but also delegates benefit from the new feature. Also the FAQ points to some of Eric's more current blog articles as well.
Lucero
+1  A: 

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.

Heinzi
+6  A: 

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.

Eric Lippert
+1  A: 

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());
Dan Tao