narrowing conversions with arrays

  • Thread starter Thread starter Erik Frey
  • Start date Start date
E

Erik Frey

Hello,

Say I have the following two classes:

class Base
{
}

class Inherited : Base
{
}

Then I create two arrays of each, and fill the base array with an inherited
instance:

Inherited[] inheriteds = new Inherited[1];
Base[] bases = new Base[1];

bases[0] = new Inherited();

Why is it possible to do the following narrowing conversion:

inheriteds[0] = (Inherited) bases[0];

But NOT possible to do the following narrowing conversion:

inheriteds = (Inherited[]) bases;

Furthermore, why is this a run-time error and not a compile time error?

Thanks,

Erik
 
I can understand *why*, without checking each every element of the array it
wouldn't be possible to ensure that the array contains objects of type
'Inherited' or 'Base' -- it may contain objects of other types derived from
Base that are not Inherited and therefore accessing the array afterwards
might fail.

The bit that confuses me is why this wouldn't be caught by the compiler.
Unless there *is* a situation where it could work at runtime, to me to seems
like a compiler bug?
 
John,
The bit that confuses me is why this wouldn't be caught by the compiler.
Unless there *is* a situation where it could work at runtime, to me to seems
like a compiler bug?

There is. If you replace

bases[0] = new Inherited();

with

inheriteds[0] = new Inherited();
bases = inheriteds;

the code runs successfully.



Mattias
 
I think his point was why this:

inheriteds = (Inherited[]) bases;

causes a runtime error and not a compile time error. I think he knows how to
get it to work... was just curious?!

Mattias Sjögren said:
John,
The bit that confuses me is why this wouldn't be caught by the compiler.
Unless there *is* a situation where it could work at runtime, to me to seems
like a compiler bug?

There is. If you replace

bases[0] = new Inherited();

with

inheriteds[0] = new Inherited();
bases = inheriteds;

the code runs successfully.



Mattias
 
John Wood said:
I think his point was why this:

inheriteds = (Inherited[]) bases;

causes a runtime error and not a compile time error. I think he knows how to
get it to work... was just curious?!

It's not a compile error because bases *could* be a reference to an
Inherited[]. For instance:

class Base
{
}

class Inherited : Base
{
}

class Test
{
static void Main()
{
Base[] bases = new Inherited[1];
Inherited[] inheriteds = (Inherited[]) bases;
}
}
 
That's what I thought -- but the code you wrote below should produce a
runtime error also.

Jon Skeet said:
John Wood said:
I think his point was why this:

inheriteds = (Inherited[]) bases;

causes a runtime error and not a compile time error. I think he knows how to
get it to work... was just curious?!

It's not a compile error because bases *could* be a reference to an
Inherited[]. For instance:

class Base
{
}

class Inherited : Base
{
}

class Test
{
static void Main()
{
Base[] bases = new Inherited[1];
Inherited[] inheriteds = (Inherited[]) bases;
}
}
 
John Wood said:
That's what I thought -- but the code you wrote below should produce a
runtime error also.

No it shouldn't, and it doesn't.

From section 11.5 of the C# spec (ECMA numbering):

<quote>
For any two reference-types A and B, if an implicit reference
conversion (§13.1.4) or explicit reference conversion (§13.2.3) exists
from A to B, then the same reference conversion also exists from the
array type A[R] to the array type B[R], where R is any given rank-
specifier (but the same for both array types). This relationship is
known as array covariance. Array covariance, in particular, means that
a value of an array type A[R] may actually be a reference to an
instance of an array type B[R], provided an implicit reference
conversion exists from B to A.
</quote>
 
What ver of .net are you running?
When I run the code on 1.0 it causes a runtime error. Perhaps it's fixed in
1.1.

John Wood said:
That's what I thought -- but the code you wrote below should produce a
runtime error also.

No it shouldn't, and it doesn't.

From section 11.5 of the C# spec (ECMA numbering):

<quote>
For any two reference-types A and B, if an implicit reference
conversion (§13.1.4) or explicit reference conversion (§13.2.3) exists
from A to B, then the same reference conversion also exists from the
array type A[R] to the array type B[R], where R is any given rank-
specifier (but the same for both array types). This relationship is
known as array covariance. Array covariance, in particular, means that
a value of an array type A[R] may actually be a reference to an
instance of an array type B[R], provided an implicit reference
conversion exists from B to A.
</quote>
 
Base[] bases = new Inherited[1];
Inherited[] inheriteds = (Inherited[]) bases;

I see now why this is a run-time error. Thanks Jon.

I do still wonder why arrays don't support this kind of casting, though.

Erik
 
Think of it this way: the type Base[] in not a base class for the type
Inherited[], so an actual object of type Base[] cannot be implicitly
converted to type Inherited[] -- thus no explicit reference conversion is
possible at runtime either. You can only do explicit reference conversion
successfully at runtime if the type of the actual object can be implicitly
converted to the desired type.

Or more technically:
http://msdn.microsoft.com/library/en-us/csspec/html/vclrfcsharpspec_6_2_3.asp?frame=true

"For an explicit reference conversion to succeed at run-time, the value of
the source operand must be null, or the actual type of the object referenced
by the source operand must be a type that can be converted to the
destination type by an implicit reference conversion (Section 6.1.4). If an
explicit reference conversion fails, a System.InvalidCastException is
thrown."
Brad Williams
 
Erik Frey said:
Base[] bases = new Inherited[1];
Inherited[] inheriteds = (Inherited[]) bases;

I see now why this is a run-time error. Thanks Jon.

I do still wonder why arrays don't support this kind of casting, though.

Because casting doesn't (unless there are actual conversions involved)
change the actual *type* of the array itself - it's just a way of
looking at it in a different reference type. It's the same as not being
able to do:

object o = new object();
string s = (string) o;
 
John Wood said:
What ver of .net are you running?
When I run the code on 1.0 it causes a runtime error. Perhaps it's fixed in
1.1.

It's certainly fine on 1.1, but I'm surprised it fails on 1.0. What
error are you getting? Are you running the *exact* code I posted?
 
ok slight confusion, sorry.
Base[] bases = new Base[1];
bases[0] = new Inherited();
inheriteds = (Inherited[])bases;

Causes the runtime error. You changed it to:
Base[] bases = new Inherited[1];
bases[0] = new Inherited();
inheriteds = (Inherited[])bases;

Which does work.

So the compiler doesn't raise an error because bases can contain an instance
of an array of any type derived from Base -- and the compiler doesn't know
which particular instance it contains.

Makes sense....

Learnt something else while playing with this... the following code produces
this exception:

An unhandled exception of type 'System.ArrayTypeMismatchException' occurred
Attempted to store an element of the incorrect type into the array.

class Base { }
class Base2 : Base { }
class Inherited : Base { }

Base[] bases;
Inherited[] inheriteds;

Base[] bases = new Inherited[1];
bases[0] = new Base2();
inheriteds = (Inherited[])bases;
 
Jon Skeet said:
Because casting doesn't (unless there are actual conversions involved)
change the actual *type* of the array itself - it's just a way of
looking at it in a different reference type. It's the same as not being
able to do:

object o = new object();
string s = (string) o;

Ah, makes sense now. Your replies are always illuminating.

Erik
 
Brad Williams said:
Think of it this way: the type Base[] in not a base class for the type
Inherited[], so an actual object of type Base[] cannot be implicitly
converted to type Inherited[] -- thus no explicit reference conversion is
possible at runtime either. You can only do explicit reference conversion
successfully at runtime if the type of the actual object can be implicitly
converted to the desired type.

Or more technically:
http://msdn.microsoft.com/library/en-us/csspec/html/vclrfcsharpspec_6_2_3.asp?frame=true

"For an explicit reference conversion to succeed at run-time, the value of
the source operand must be null, or the actual type of the object referenced
by the source operand must be a type that can be converted to the
destination type by an implicit reference conversion (Section 6.1.4). If an
explicit reference conversion fails, a System.InvalidCastException is
thrown."
Brad Williams

That makes sense. Thanks.

Erik
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Back
Top