Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Confused about boxing, casting, implicit etc

Tags:

c#

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

like image 581
user257412 Avatar asked Mar 02 '10 13:03

user257412


2 Answers

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

like image 137
Adam Robinson Avatar answered Sep 30 '22 10:09

Adam Robinson


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.

like image 45
Eric Lippert Avatar answered Sep 30 '22 09:09

Eric Lippert