tags:

views:

66

answers:

1

a) Compiles

        Func<string, bool> f1 = (Func<object, bool>)null;

b) Not

        Func<int, bool> f2 = (Func<object, bool>)null;

Why value types are special here? Is contravariance broken with value types?

+6  A: 

Generic variance only works with reference types, yes. (This is so that the CLR knows that everything's still just a reference, so the JITted code is still the same... the bits involved in a reference are the same whatever type you're talking about, whereas treating int as object requires a boxing conversion. Basically you can keep representational identity with reference types).

From the C# 4 spec, section 13.1.3.2:

A type T<A1, …, An> is variance-convertible to a type T<B1, …, Bn> if T is either an interface or a delegate type declared with the variant type parameters T<X1, …, Xn>, and for each variant type parameter Xi one of the following holds:

  • Xi is covariant and an implicit reference or identity conversion exists from Ai to Bi
  • Xi is contravariant and an implicit reference or identity conversion exists from Bi to Ai
  • Xi is invariant and an identity conversion exists from Ai to Bi

It's the "implicit reference conversion" rather than just "implicit conversion" bit which is a problem for value types.

For much more detail around generic variance, see Eric Lippert's blog series on the topic.

Jon Skeet
thx, i had a feeling that answer is "because of spec", but it is very disappointing that value types are not invited to the party.
Andrey
@Andrey: "because of the spec" implies that the decision was arbitrary, but it was not. Jon explains why not: because int and object are not *compatible* types. A function that returns an int cannot be used as a function that returns an object because a function that returns an int writes a 32 bit integer into the return storage, and a function that returns an object writes a, say, 64 bit, pointer to a heap-allocated object into the return storage. The storage *size* requirements aren't even the same, so the types simply are not compatible.
Eric Lippert
@Andrey: Now, we could, in theory, have made variance legal amongst value types that have representation preserving conversions. Func<int> and Func<uint> for example could have been made compatible. But that just seemed like a fair amount of work for no compelling benefit. Very few value types are both bit-compatible and have the same semantics; you wouldn't want a Func<double> being used as a Func<long> even though they are both 64 bit types.
Eric Lippert