7

Can anyone shed light on why contravariance does not work with C# value types?

The below does not work

private delegate Asset AssetDelegate(int m);

internal string DoMe()
{
    AssetDelegate aw = new AssetDelegate(DelegateMethod);
    aw(32);
    return "Class1";
}

private static House DelegateMethod(object m)
{
    return null;
}
Jack Kada
  • 24,474
  • 29
  • 82
  • 106
  • 1
    I would post an answer, but I don't want to fumble with the wording. Check Eric Lippert's blog entry here, specifically the comments. http://blogs.msdn.com/b/ericlippert/archive/2007/10/19/covariance-and-contravariance-in-c-part-three-member-group-conversion-variance.aspx It appears to be a case of reference type "values" having the same memory footprint, which is not the case for value types. With that said, the DelgateMethod could be made generic, which would then support ints, longs, etc. – Anthony Pegram Jun 09 '10 at 14:55

2 Answers2

5

The problem is that an int is not an object.

An int can be boxed to an object. The resulting object (aka boxed int) is, of course, an object, but it's not exactly an int anymore.

Note that the "is" I'm using above is not the same as the C# operator is. My "is" means "is convertible to by implicit reference conversion". This is the meaning of "is" used when we talk about covariance and contravariance.

An int is implicit convertible to an object, but this is not a reference conversion. It has to be boxed.

An House is implicit convertible to an Asset through a reference conversion. There's no need to create or modify any objects.

Consider the example below. Both variables house and asset are referencing the very same object. The variables integer and boxedInt, on the other hand, hold the same value, but they reference different things.

House house = new House();
Asset asset = house;

int integer = 42;
object boxedInt = integer;

Boxing and Unboxing is not as simple as it may look like. It has many subtleties, and might affect your code in unexpected ways. Mixing boxing with covariance and contravariance is an easy way to make anyone dazzle.

jpbochi
  • 4,366
  • 3
  • 34
  • 43
  • To my mind, trying to pretend that value types derive from `System.Object` is not helpful. It is far more helpful to recognize that for every value type there is an associated "boxed" class type which derives from `System.ValueType`. Value types are implicitly convertible to their class-type equivalents, and an explicit cast may be used to copy the contents of a boxed value type back to a real one. – supercat Jun 26 '12 at 20:28
1

I agree with Anthony Pegram's comment - it is based on reference types having a different memory footprint than the value types: the CLR can implicitly use a class of one type as a class of its super type, but when you start using value types, the CLR will need to box your integer so it can work in the place of the object.

If you're looking to make it work anyway, I have a tendency to wrap the declaration in an expression:

AssetDelegate aw = new AssetDelegate((m) => DelegateMethod(m));

I don't know if that's good practice or not as far as syntax goes, but remember that boxing and unboxing is expensive.

Matt DeKrey
  • 11,582
  • 5
  • 54
  • 69