Clearly this is a confusing issue. Unfortunately, Tim Goodman's answer, which I think is the one that most clearly and correctly addressed the actual stated question, was downvoted and deleted. Lee's answer also hits the mark pretty well.
To sum all this up, let me rephrase the question into a number of more precise questions.
What is "covariance" as it applies to assigment compatibility?
Briefly: Consider a mapping from one type to another. Say, int --> int[]
, string --> string[]
and so on. That is, the mapping "x maps to array of x". If that mapping preserves assignment compability then it is a covariant mapping. For short, we say "arrays are covariant", meaning "array assignment compatibility rules are the same as the assignment compatibility rules of their element types because the mapping from element type to array type is covariant".
For more on the relationship between covariance and assignment compatibility, see:
Difference between Covariance & Contra-variance
http://blogs.msdn.com/ericlippert/archive/2009/11/30/what-s-the-difference-between-covariance-and-assignment-compatibility.aspx
Are arrays actually covariant in C#?
They are covariant only when the element type is a reference type.
Is that kind of covariance type safe?
No. It is broken. Its lack of type safety means that operations which succeed at compile time can throw exceptions at run time. It also means that every assignment to an array which could cause such an exception needs to check to see whether the exception should be thrown, which is expensive. Basically what we have here is a dangerous, expensive feature that you cannot opt out of. I'm not particularly happy about that, but that's what we're stuck with.
See http://blogs.msdn.com/ericlippert/archive/2007/10/17/covariance-and-contravariance-in-c-part-two-array-covariance.aspx for more details on this broken form of covariance.
Does the CLR support covariance on value typed arrays at all?
Yes. See
http://blogs.msdn.com/ericlippert/archive/2009/09/24/why-is-covariance-of-value-typed-arrays-inconsistent.aspx
Which I wrote from my answer to:
Why does my C# array lose type sign information when cast to object?
Does C# support safe forms of covariance?
As of C# 4, yes. We support covariance and contravariance of generic interfaces and delegates that are parameterized with reference types. C# 3 does not support generic variance.
For example, in C# 4, an object that implements IEnumerable<string>
may be assigned to a variable of type IEnumerable<object>
.
See http://blogs.msdn.com/ericlippert/archive/tags/Covariance+and+Contravariance/default.aspx
for an extended discussion of this feature.
Now that we have the preliminaries out of the way, we can come to your actual question:
Why does generic and array covariance only work on reference types, not on value types?
Because some conversions are representation-preserving and some are representation-changing.
When you say
string x = "hello";
object y = x;
the contents of storage y are exactly the same as the contents of storage x. Both of them are a bit pattern which means "refer to this object on the GC heap". That bit pattern is the same no matter whether the CLR is interpreting the bits as a reference to object or a reference to string.
An array is nothing more than a whole bunch of storages. When you reinterpret the contents of a string[] as references to objects, nothing in their bits has to change. They just stay exactly the same and the CLR thinks of them as objects, not specifically as strings. You just look at the string funny and its an object.
When you convert an int to an object, we allocate a box to put the int into. The int is a 32 bit integer that contains its own value. The box is represented as a 32 or 64 bit managed address into the GC heap that then contains the 32 bit int value. Those are completely different bits! Turning an object into an int requires a huge amount of work. You can't just look at the int funny and hey, it looks like an object. You have to allocate memory to make that work.
And that's why you cannot turn an array of int into an array of object. An array of ten integers could take up 320 bits, each bit being a part of the integer itself. An array of ten objects is 320 or 640 bits of managed addresses onto the GC heap, and who is going to do all of that allocation? We want reference conversions to be fast and cheap; this conversion would require us to basically allocate an entire new array and make copies of the entire contents; the result would no longer have referential identity with the original array, so changes to it would be lost. Or, we'd have to write even more code that took changes to the new array and marshalled them back to the old one.
See
http://ericlippert.com/2009/03/03/representation-and-identity/
for more discussion of representation-preserving conversions.
Does that answer your question? This is a confusing topic, I know. It gets pretty deep into the fundamental design decisions of the CLR.