user257412
user257412

Reputation: 724

Confused about boxing, casting, implicit etc

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 ??

Upvotes: 13

Views: 1413

Answers (5)

Eric Lippert
Eric Lippert

Reputation: 660169

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.

Upvotes: 13

Tim Goodman
Tim Goodman

Reputation: 23976

It's because int is a value type. Such a conversion would be legal for reference types.

According to Eric Lippert of the C# compiler team:

C#'s array covariance rule is "if X is a reference type implicitly convertible to reference type Y then X[] is implicitly convertible to Y[]"

The questioner asks why this is in contrast to the fact that int is convertible to object. Note that int inherits from object, but int[] does not inherit from object[]. Rather int[] and object[] both inherit from System.Array

EDIT

I found more from Eric on why this sort of conversion is allowed for reference types.

It was added to the CLR because Java requires it and the CLR designers wanted to be able to support Java-like languages. We then up and added it to C# because it was in the CLR. This decision was quite controversial at the time and I am not very happy about it, but there’s nothing we can do about it now.

Note however that C#'s rule for array covariance is actually subtly different from the CLR's rule, as described here. In short, the CLR's rule is "if X is assignment compatible with Y then X[] is assignment compatible with Y[]".

EDIT

See also my other answer, which I had deleted before posting this one, but have since undeleted.

Upvotes: 8

Tim Goodman
Tim Goodman

Reputation: 23976

EDIT: Undeleting this (in spite of the down votes) because of Eric Lippert's positive comment.

A lot of the answers mention that int[] is not convertible to object[] . . . this is certainly true, but that doesn't really get at the question of why this is the case. Not meaning to put words into the language designers' mouths, but I suspect this is because the process of converting int[] to object[] requires the allocation of a whole new array and boxing each element of the int array into an element of the object array ... this is a costly operation that is probably not what the programmer intends. More likely you want to box the entire int[] into a single object.

Upvotes: 4

Adam Robinson
Adam Robinson

Reputation: 185643

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[].

Upvotes: 22

Lee
Lee

Reputation: 144136

In C# the following is legal:

object[] objects = new string[] { "a", "b", "c" };

So you may expect that

object[] ints = new int[] { 1, 2, 3}

is also valid since like string, int is implicitly convertible to object. However, in the first instance, the string[] contains a sequence of string references, which are also valid object references. In the second case, the int[] contains a sequence of int values and theses are not valid object references. Now, you can assign an int to object as your example shows, but this causes the creation of a new object (boxing). Therefore, for this to be valid:

object[] ints = new int[] { 1, 2, 3}

would require the creation of a new array, whereas the first is only a single reference copy. Therefore, the solution is to explicitly create a new array:

object[] ints = (new int[] { 1, 2, 3}).Select(i => (object)i).ToArray();

Upvotes: 3

Related Questions