From the book:
1) int i = 7;
2) Object o = i; // Implicit boxing int-->Object
3) Object[] a3 = new int[] { 1, 2 }; // Illegal: no array conversion
The assignments in 3) is illegal because int is not a reference type and so int[] is not implicitly convertible to Object[]
I don't get this . on line 2) it shows that int is implicitly convertible to Object, and in the third line, it says int[] is not implicitly convertable. wha ??
Not to restate the problem, but it's because int[]
is not implicitly (or explicitly, for that matter) convertible to Object[]
.
Arrays are types in and of themselves. int[]
simply means "a series of int
variables", just like Object[]
means "a series of Object
variables".
Incidentally, all arrays are reference types, it's just that int[]
is a reference type representing a series of a particular value type.
EDIT
Don't allow the talk about covariance to confuse you. This is a widely misunderstood topic, and one that's not really beneficial for a beginning developer to try to tackle. Yes, .NET 4.0 introduces the ability to specify an operation as being covariant or contravariant, but that won't allow for assignment compatibility between incompatible types like Object[]
and int[]
. Consider the following example, assuming that it would compile.
int[] ints = new int[] { 1, 2, 3 };
Object[] objects = ints;
objects[1] = "hello";
int foo = ints[1];
Now we have a problem. If it were legal to assign an int[]
to an Object[]
, then I now suddenly (magically) have a string
value in my int[]
array--something that should never happen, and actually can't happen, since an int
variable cannot hold a string
value.
Others have (correctly) pointed out that something like this does compile:
string[] strings = new string[] { "a", "b", "c" };
Object[] objects = strings;
objects[1] = 4;
I'll leave it to someone like Eric Lippert to explain why this sort of operation works (it's essentially assuming covariance when it isn't necessarily the case) [EDIT: Thanks to Tim Goodman, who actually posts Eric's explanation, or at least statement, about this], but fundamentally any reference type is technically capable of holding a reference to any type. In other words, when I declare a string
variable it allocates the same amount of memory (for the variable) as if I were to declare a DbConnection
variable; they're both reference types. For value types, the amount of memory allocated depends on the type, and they are fundamentally incompatible.
You will note, however, that you will get a runtime exception (ArrayTypeMismatchException
) when performing the last step (assigning an int
to the second array element), since the underlying array is actually a string[]
.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With