3

I have a generic interface IConstrained which is implemented by the generic Constrained class. When I attempt to do the code below I get an invalid cast exception.

IConstrained<decimal> decimalLimit = new Constrained<decimal>(1);
IConstrained<IComparable> comparableLimit = (IConstrained<IComparable>) decimalLimit;

Why is it not possible to do this if decimal implements IComparable? What would be the correct way to do this? Thanks.

RFBoilers
  • 33
  • 1
  • 4

6 Answers6

7

Generic types are not covariant in .NET 2.0. This includes .NET 3.0/3.5 as well since they use the same 2.0 runtime. .NET 4.0 will support covariance, however.

bobbymcr
  • 23,769
  • 3
  • 56
  • 67
  • 3
    Though in this case, it will not work because we only support variance on generic interfaces constructed with *reference* types. Decimal is a *value* type. – Eric Lippert Sep 01 '09 at 15:41
4

Casting IConstrained<decimal> to IConstrained<IComparable> is called covariance. You can't do it in C# 3. However, it is coming in C# 4.

Erik Lippert has a series of blog posts detailing Contravariance and Covariance.

To get around it, you will have to cast the decimal to IComparable when you use it.

John Fisher
  • 22,355
  • 2
  • 39
  • 64
Lance Fisher
  • 25,684
  • 22
  • 96
  • 122
1

This is a common trip-up with C# (as well as other languages with Generics).

In C#, you can only cast to classes in a class hierarchy (superclasses, subclasses). But IConstrained<IComparable> is neither a superclass, nor a subclass of IConstrained<decimal>, even though decimal implements IComparable. The reason C# disallows this is because allowing it would mean you can do very bad things.

For a detailed explanation of why this is, check out this similar question

Community
  • 1
  • 1
Smashery
  • 57,848
  • 30
  • 97
  • 128
1

For a class to be castable like this it would need to implement both interfaces IConstrained<decimal> and IConstrained<IComparable>

class A:IConstrained<decimal>,IConstrained<IComparable>

It does not happen automatically, because .NET 2.0 does not implement covariance or contravariance. IConstrained<decimal> doesn't implement IConstrained<IComparable>. Yes, it is a but frustrating and counter intuitive. There will actually be some support for this kind of scenario in C# 4.0 in one form or another from what I understand. It is called covariance or contravariance.

Edit: I'm not familier with the Constrained class, but you might be able to construct a new Contrained<IComparable> and pass to it a decimal. If it had a constructor of the form Constrained<T> (T copyFrom) then you can declare a new Constrained<IComparable> and pass the decimal to it. Kind of like making a copy.

Edit 2: About half way down this page, search for "2.0", there is an example of how to work around this issue in .NET 2.0: http://blog.t-l-k.com/dot-net/2009/c-sharp-4-covariance-and-contravariance

AaronLS
  • 37,329
  • 20
  • 143
  • 202
  • ".NET 2.0 does not implement covariance or contravariance" isn't actually true. The platform itself (i.e. CLR) supports it just fine, it's just that no class in the standard class library used variance annotations in 2.0, and languages weren't aware of them either. But you could code covariant and contravariant generic interfaces in MSIL in 2.0, and cast them directly from MSIL as well, or via `object` in C#. – Pavel Minaev Sep 01 '09 at 02:28
  • I should have been more specific and said C# 2.0 – AaronLS Sep 01 '09 at 03:01
1

Here's an answer to "how to do it".

IList<decimal> decimalLimit = new List<decimal>(1);
IEnumerable<IComparable> asComparable = decimalLimit.Cast<IComparable>();
IList<IComparable> comparableLimit = asComparable.ToList();
John Fisher
  • 22,355
  • 2
  • 39
  • 64
0

When it comes to generics, two instances of the same generic type with different type arguments have no direct relationship to each other. In other words:

IConstrained<decimal> !== IConstrained<IComparable>

This is a variance problem. Even though decimal is IComparable, IConstrained<decimal> is not IConstrained<IComparable>. I believe this kind of automatic conversion will be possible under C# 4.0 with its improved co/contravariance. However it is not possible with C# 3.0 or earlier.

jrista
  • 32,447
  • 15
  • 90
  • 130
  • 2
    It will not be, because we only support variance on generic interfaces constructed with *reference* types. Decimal is a *value* type. – Eric Lippert Sep 01 '09 at 15:43
  • So, no variance on generics constructed with value types. Will there not be any variance on value types at all? Or only in the case of generics? – jrista Sep 01 '09 at 20:37