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
  • 19,465
  • 20
  • 98
  • 152
  • 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
  • 59,176
  • 9
  • 122
  • 152
  • 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
  • 647,829
  • 179
  • 1,238
  • 2,067
1

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".

Weeble
  • 17,058
  • 3
  • 60
  • 75
  • 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
  • @glorfindel thanks for the edit, but I felt the "link link link" text was less helpful than just showing the URLs. I've replaced it with the article titles, which should be better than either. – Weeble Oct 18 '22 at 10:35
  • @Weeble yes, that's on my to do list, to automatically replace those with the titles of the pages instead. Thanks for the ping! – Glorfindel Oct 18 '22 at 10:45
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
  • 167,459
  • 57
  • 363
  • 519
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
  • 125,917
  • 54
  • 300
  • 447