1

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.
Shawn
  • 18,847
  • 20
  • 96
  • 151
  • read through the co-/contravariance FAQ. It is a very good starting point for people unfamiliar with this topic, as it is somewhat a brain-teaser until you get used to it. http://blogs.msdn.com/b/csharpfaq/archive/2010/02/16/covariance-and-contravariance-faq.aspx – Lucero Jun 23 '10 at 21:28

5 Answers5

6

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
  • 57,903
  • 8
  • 117
  • 151
  • 1
    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 Jun 23 '10 at 21:19
  • @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 Jun 23 '10 at 21:21
6

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
  • 630,995
  • 172
  • 1,214
  • 2,051
1

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
  • 15,923
  • 3
  • 54
  • 71
  • interesting, but the article doesnt say anything about covariance in generic arguments? – Shawn Jun 23 '10 at 21:21
  • Ah, it's the first in a series. I didn't realise there weren't links between them. I'll add the others... – Weeble Jun 23 '10 at 21:27
  • Be careful not to post outdated links. Co- and contravariance on generic interfaces (and delegates) have been introduced with .NET 4 / VS2010. – Lucero Jun 23 '10 at 21:30
  • 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 Jun 23 '10 at 21:37
1

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.

Community
  • 1
  • 1
Heinzi
  • 159,022
  • 53
  • 345
  • 499
1

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
  • 122,418
  • 53
  • 286
  • 437