Using as operator on value tye array

K

KK

Dear All
I have a small problem with using as operator on value type array.

Here is an example what I am trying to do.

using System;

using System.Collections.Generic;

using System.Text;



namespace TestDynamicCast

{

class Program

{

static void Main(string[] args)

{

uint[] array1 = { uint.MaxValue - 1, uint.MaxValue - 2 };

MyMethod(array1);

}



public static void MyMethod(Array arr)

{

int[] afterCast1 = arr as int[];

if (null != afterCast1) //cast is successfull. Is it correct?

{

//DoSomeThing

}

else

{

float[] afterCast2 = arr as float[];

if (null != afterCast2) //cast fails which is OK

{

//DoSomeThingElse

}

}

}

}

}



I think first 'if' condition itself must fail because int[] and uint[] are
not same, but it's able cast it successfully.



Please let me know what's going wrong with my code and how to solve this
problem


Thanks in advance.

_____________________________________________________________________________
Krishna Rao K
Lucid Software Ltd 104, NSIC STP Complex | Guindy Industrial Estate |
Ekkattuthangal | Chennai 600032 ' +91 44 2225 2273 / 76 , +91 98407 28998
________________________________________________________
 
J

Jon Skeet [C# MVP]

Dear All
I have a small problem with using as operator on value type array.

if (null != afterCast1) //cast is successfull. Is it correct?

Just as an aside, do you find this easier to read than:

if (afterCast1 != null)

?

In C you might use the "constant != variable" order to avoid silent
bugs where you accidentally write "=" instead of "!=" - but in C# that
wouldn't be valid anyway as the condition in an "if" statement can
*only* be a Boolean. The only time it could be a problem is if you're
comparing Booleans that way - but there I would use if (condition) or
if (!condition) anyway.
Please let me know what's going wrong with my code and how to solve this
problem

Basically C# itself doesn't believe the conversion is valid, but the
CLR is happy to do it. You can do the same thing between an array of
enum values and an array of the enum's underlying type. If you want to
force it to throw an exception, you can call GetType on the array, and
then Type.GetElementType and compare it with your target array type.

Jon
 
K

KK

Dear Skeet
Thanks for your response.

I'm comfortable with ' if (null != afterCast1) ' notation because
basically I'm from C++ back-ground and still coding
using C++/CLI and mixed mode extensively.

Coming to the problem, when the cast fails when source in an unit array and
trying to cast to float array, why can't CLR perform
the same check against int & uint arrays? And strangely after the cast in my
previous code, the values displayed in the debugger
are still unit values and afterCast1.GetType() is still returning
'System.Int32[]'? This is what is confusing.

The same problem exists in C++/CLI, that means is it bug with CLR?
 
J

Jon Skeet [C# MVP]

I'm glad you've explained it (however briefly :) ). I just spent a fair
amount of time trying to integrate all of the different parts of the C#
spec that seem to address this, and all I could come up with was that it
shouldn't work.

And indeed it doesn't if you try to go straight from int[] to uint[] -
the compiler won't let you. You have to go via Array, or Object, or
something like that.
I am curious though: by what rule is the CLR allowed to override the C#
specification? Or, put another way, given that the CLR doesn't check this
itself, why isn't the C# compiler emitting the appropriate code to deal
with the situation?

In short, the C# and CLR specs aren't quite aligned on every aspect of
conversion. This is certainly unfortunate, but was likely to be the
case at some point.
If the C# spec had just left it as a general language semantics issue, I
could maybe see the current behavior. But the spec specifically says:

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.

In this case, there is no implicit reference conversion that could convert
the actual type (uint[]) to the destination type (int[]). If the C# spec
is making statements regarding the _run-time_ behavior, it seems to me
that when the run-time doesn't guarantee the behavior, the compiler needs
to.

Right. I think in this case the spec should explicitly call out that
the runtime is free to make more things valid. Otherwise it's being
*very* prescriptive of the runtime.
I realize this is sort of an esoteric question, but I'm wondering if you
have any insight as to whether this is actually being considered a bug by
someone who matters (as opposed to me or the OP...I know _I_ think it's a
bug), or is this something that a C# compiler person would claim is
working as intended?

I haven't actually checked the CLI spec, but I'd certainly imagine
it's allowed there. I suspect it would be deemed "working as expected,
but unfortunately misleading in the spec". I'll raise it with the C#
team and see what they say. I would personally like it to stay as it
is - if the compiler generated extra checks around every conversion,
it would be painfully slow, just to make things fail in very
occasional situations. I'd much prefer the spec to have a caveat that
the conversion is guaranteed to succeed in situation X, but *may* also
succeed in other situations.

Jon
 
D

Duggi

Hi KK,

I am sure you are running the code in unchecked mode otherwise the
statement

uint[] array = { uint.MinValue - 1, uint.MaxValue - 2 };

would have given you some compile error.

However that is not the cause for the issue you are facing.
There are two casting operations that this code does. First casting is
done when the uint[] is passed to method to Array. Second cast to
Array to int[], both are perfectly valid and so does the code.

If you want the first condition to be failed, try passing the unit[]
as uint[] itself.

public static void MyMethod(unit[] arr)
{
int[] afterCast1 = arr as
int[]; // This is an error.
if (null != afterCast1) //cast is successfull. Is it correct?
{
//DoSomeThing
}
}

Now you will hit by an error.

I hope this will help you!!!

-Cnu
 
K

kodehoved

I spend some time going through the language spec as well without
finding a clear answer to this.

Jon, could you elaborate a bit on why exactly this conversion is
allowed?

As far as I can tell this only works from "unsigned type[]" to
"type[]" (e.g. uint[] to int[]). Swapping uint[] for any other integer
array type makes the cast fail. However, you can do the same with e.g.
ulong[] to long[] and so forth.

Thanks,
Brian
 
J

Jon Skeet [C# MVP]

<snip>

Copying part of the reply I sent by email - in future, please keep
discussions to *either* the newsgroup *or* email, *or* state on the
message that it's going to both. If I get an email before I see the
group response, I'll normally reply to that - which then means I
either have to copy the response to the group or leave it unanswered
Coming to the problem, when the cast fails when source in an unit array and
trying to cast to float array, why can't CLR perform
the same check against int & uint arrays? And strangely after the cast in my
previous code, the values displayed in the debugger
are still unit values and afterCast1.GetType() is still returning
'System.Int32[]'? This is what is confusing.

The array still *is* an int[] at its heart, but the CLR is able to
treat signed and unsigned integers basically as the same thing. You'll
see short[] and ushort[] behave the same way. I've now tried to find
the relevant bit of the CLI spec, but can't. I'll ask the teams
involved.
The same problem exists in C++/CLI, that means is it bug with CLR?

Well, it means it's the CLR doing it. Whether or not it's a bug is a
different matter - I suspect the CLI spec just allows it somewhere, in
which case it's a mismatch between language expectations and CLI
expectations.

Interesting stuff though...

Jon
 
J

Jon Skeet [C# MVP]

Peter Duniho said:
[...] I'd much prefer the spec to have a caveat that
the conversion is guaranteed to succeed in situation X, but *may* also
succeed in other situations.

Sure, that'd be fine with me too. My complaint is with the apparent
disconnect between the statements in the spec and the actual behavior.
Absolutely.

If the fix is simply to edit the spec, I don't have an issue with that.
It's just that the way the spec is worded now, it seems to very clearly
say that not only is this not an acceptable conversion in C#, it shouldn't
work at run-time either. I don't mind if someone decides to change the
spec, justifying the change on the thought that the C# spec probably
doesn't have the authority over the run-time that it seems to think it
has. :)

Okay, I'll mail appropriate and post back with whatever I find :)
(Though, granted, the spec certainly does assume certain features that are
required to be in the CLR. But I think those are more of a "C# needs this
to work", than the current issue, which is more of a "C# wants this not to
work", so I don't really consider them the same sort of thing).

Exactly. Positive requirements are more understandable than negative
ones.

On the other hand, I *would* want the runtime to fail to convert, say,
a bare System.Object to a System.String by picking the empty string. I
think C# should be able to assume that won't happen - in which case,
where do you draw the line? Sooner or later it's going to be a
pragmatic decision...
 
J

Jon Skeet [C# MVP]

kodehoved said:
I spend some time going through the language spec as well without
finding a clear answer to this.

Jon, could you elaborate a bit on why exactly this conversion is
allowed?

Will do when I've heard back from CLR folks :)
As far as I can tell this only works from "unsigned type[]" to
"type[]" (e.g. uint[] to int[]). Swapping uint[] for any other integer
array type makes the cast fail. However, you can do the same with e.g.
ulong[] to long[] and so forth.

It would also happen for something like:

enum Foo : short {}

Foo[] x = new Foo[10];
Array y = x;
short[] z = (short[]) y;
 
J

Jon Skeet [C# MVP]

Jon Skeet said:
Okay, I'll mail appropriate and post back with whatever I find :)

<snip>

Right - Eric Lippert has replied. It's probably worth reproducing the
question as I asked it, to make the reply make more sense. Be warned,
it's a long and detailed answer - just the kind I like :)

Question:
I've been aware of a few differences between what the C# spec claims is
allowed and what the CLR allows when it comes to conversions. However,
I think this is a new one on me:

using System;

class Test
{
static void Main(string[] args)
{
Array ints = new int[] {1, 2, 3};
uint[] uints = (uint[]) ints;

Console.WriteLine(uints.GetType());
}
}

This runs with no exceptions, and produces the following output:
System.Int32[]

(Interestingly, if you box an element of "uints" that *does* get boxed
as a uint, because it's the compiler which specifies the type there.)

The relevant bits are the C# spec is 6.2.4:

<quote>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
(§6.1.6). If an explicit reference conversion fails, a
System.InvalidCastException is thrown.
</quote>

Now, there's no implicit reference conversion from int[] to uint[], so
already the spec has been violated. However, I'd expected to see why
the CLR allowed this in ECMA-335 - so we move on to partition 3, 4.3,
the castclass instruction (as emitted by the C# compiler):

<quote>
Note that:
1. Arrays inherit from System.Array.
2. If Foo can be cast to Bar, then Foo[] can be cast to Bar[].
3. For the purposes of note 2 above, enums are treated as their
underlying type: thus E1[] can be cast
to E2[] if E1 and E2 share an underlying type.
</quote>

Now, this would see to make a certain amount of sense, if we deem that
"int can be cast to uint" - except that I'm not sure of the use of the
word "cast" here. It certainly doesn't work for everything: you can
cast an int to a float, or a long, or a byte - but you can't convert
int[] to float[], long[] or byte[].


So, to sum up:
1) Assuming the C# spec wants to leave a little wiggle room for the
CLR, I don't think it should be quite so prescriptive - it would be
worth mentioning that the runtime may make some extra conversions
available. It's a bit of a shame to have areas of uncertainty like this
(we certainly wouldn't want the CLR to start converting completely
unrelated types, for instance) but I understand there's a small matter
of pragmatism.

2) It looks to me like either ECMA-335 is poorly worded, and/or the CLR
is violating it.

Anyone care to enlighten me?


------------------- End of question ---------------------

Eric's response:

Jon=3Fs analysis is pretty much correct; there are conversions which the
CLR allows which C# does not. Because of that, if you hammer on it hard
enough, you can make a C# program which you would think ought to throw
an invalid cast exception, but in fact succeeds.


However, though I hate to be contradictory, I must point out that this
is not correct.

you can cast an int to a float, or a long, or a byte

It is not legal to issue a castclass instruction from int to float.
Remember, you are talking about the CLR definition of =3Fcast=3F here, not
the C# definition, so do not confuse the two.

The confusion is our fault, in two ways.

First source of confusion: in C# we have conflated two completely
different operations as =3Fcast=3F operations. The two operations that we
have conflated are what the CLR calls casts and coercions.

8.3.2 Coercion

Sometimes it is desirable to take a value of a type that is not
assignment-compatible with a location, and convert the value to a
type that is assignment-compatible. This is accomplished through
coercion of the value.

Coercion takes a value of a particular type and a desired type and
attempts to create a value of the desired type that has equivalent
meaning to the original value. Coercion can result in
representation changes as well as type changes; hence coercion does
not necessarily preserve the identity of two objects.

There are two kinds of coercion: widening, which never loses
information, and narrowing, in which information might be lost. An
example of a widening coercion would be coercing a value that is a
32-bit signed integer to a value that is a 64-bit signed integer.
An example of a narrowing coercion is the reverse: coercing a 64-
bit signed integer to a 32-bit signed integer. Programming
languages often implement widening coercions as implicit
conversions, whereas narrowing coercions usually require an
explicit conversion.

Some widening coercion is built directly into the VES operations on
the built-in types (see §12.1). All other coercion shall be
explicitly requested. For the built-in types, the CTS provides
operations to perform widening coercions with no runtime checks and
narrowing coercions with runtime checks.

8.3.3 Casting

Since a value can be of more than one type, a use of the value
needs to clearly identify which of its types is being used. Since
values are read from locations that are typed, the type of the
value which is used is the type of the location from which the
value was read. If a different type is to be used, the value is
cast to one of its other types. Casting is usually a compile time
operation, but if the compiler cannot statically know that the
value is of the target type, a runtime cast check is done. Unlike
coercion, a cast never changes the actual type of an object nor
does it change the representation. Casting preserves the identity
of objects.

For example, a runtime check might be needed when casting a value
read from a location that is typed as holding a value of a
particular interface. Since an interface is an incomplete
description of the value, casting that value to be of a different
interface type will usually result in a runtime cast check.

We conflate these two things in C#, using the same operator syntax and
terminology for both casts and coercions.

So now it should be clear that there is no =3Fcast=3F from int to floatin
the CLR. That=3Fs a coercion, not a cast.


Second source of confusion: inconsistency in the CLR spec.


The CLR spec says in section 8.7


Signed and unsigned integral primitive types can be assigned to
each other; e.g., int8 := uint8 is valid. For this purpose, bool
shall be considered compatible with uint8 and vice versa, which
makes bool := uint8 valid, and vice versa. This is also true for
arrays of signed and unsigned integral primitive types of the same
size; e.g., int32[] := uint32[] is valid.

And in section 4.3:

If the class of the object on the top of the stack does not
implement class (if class is an interface), and is not a derived
class of class (if class is a regular class), then an
InvalidCastException is thrown.

=3F

2. If Foo can be cast to Bar, then Foo[] can be cast to Bar[].


Where does the spec for castclass say that int32[] can be cast to
uint32[]? It doesn=3Ft. It should! int32 and uint32 are assignment
compatible, so they can be cast from one to the other without changing
bits. But they do not implement or derive from each other, so a strict
reading of the spec says that this cast should fail, and therefore
int32[] to uint32[] should also fail.


Clearly that is not what was meant and not what was implemented.
Casting between assignment-compatible types should be legal. Really
what this should say is something like =3FIf Foo can be cast to Bar or
Foo is assignment compatible with Bar then Foo[] can be cast to Bar[]=3F


Fortunately, the CLR guys did NOT extend this goofy kind of type
variance to covariant and contravariant interfaces, which as you know
we are probably adding in a future version of C#. That is, if we make
IEnumerable<T> covariant in T, it will NOT be possible to do a clever
series of casts to trick the CLR into assigning an IEnumerable<int> to
an IEnumerable<uint>, even though it is possible to make int[] go to
uint[]. However, I think it is possible =3F I haven=3Ft checked this yet =3F
to leverage the fact that int[] goes to uint[] to similarly force
IEnumerable<int[]> to go to IEnumerable<uint[]>.


This situation =3F the CLR being more generous about what identity-
preserving casts are legal =3F may end up considerably complicating my
life in other ways involving covariance and contravariance as we
attempt to detect ambiguous conversions at compile time, but that=3Fs
another story and we are still researching it.

------------------- End of answer ---------------------

I'd just like to thank Eric for posting such a complete and
illuminating answer.
 
J

Jon Skeet [C# MVP]

Complete? I read through the whole thing, and I can't figure out if the
C# spec is considered to be in error or not. :)

You're right - that was slightly ducked. It looks like the CLR spec is
*definitely* in error, and there's an admission that C# would
sometimes want to prohibit things that the CLR will admit. I wouldn't
like to say whether either of them are likely to be fixed though.
That said, I did find the reply interesting and useful. Thanks! However,
I'm still confused by some aspects:
[...]
And in section 4.3 [of the CLR spec?]:

Yup. Partition 3 of ECMA-335. P428 in the PDF, which is P105 of that
partition. (It would be so nice if they'd split it into multiple
PDFs.)
Is there some typography that got lost here? There's a lot of "class" but
without qualification, which makes it hard to understand.

Yes, I the spec has appropriate italics etc in here (and Eric's mail
may have done too). Have a look for the relevant section in the spec
to get it in all its glory.
=3F
2. If Foo can be cast to Bar, then Foo[] can be cast to Bar[].

Interestingly, the C# spec has similar language, but only for reference
types. This conforms to the CLR spec language, in that coercion isn't
allowed but casting is.
Right.
Clearly that is not what was meant and not what was implemented.
Casting between assignment-compatible types should be legal. Really
what this should say is something like =3FIf Foo can be cast to Bar or
Foo is assignment compatible with Bar then Foo[] can be cast to Bar[]

Why does "assignment compatible" not simply imply "can be cast to"? Isn't
"assignment compatible" just a more relaxed (permissive) version of "can
be cast to"?

I wouldn't like to judge, to be honest. Especially not before the
first coffee of the day. Although isn't assignment compatibility
possible without an execution time check, whereas a cast might not be?

For instance, a reference of type Stream can be cast to MemoryStream,
but the two locations involved wouldn't be assignment compatible. (I
think.)
Fortunately, the CLR guys did NOT extend this goofy kind of type
variance to covariant and contravariant interfaces, which as you know
we are probably adding in a future version of C#. That is, if we make
IEnumerable<T> covariant in T, it will NOT be possible to do a clever
series of casts to trick the CLR into assigning an IEnumerable<int> to
an IEnumerable<uint>, even though it is possible to make int[] go to
uint[].

Interestingly, I think the C# spec might already have this "back door". I
noticed it when I was looking at the spec before. I don't recall the
specifics, but the basic issue was that an array of type T has an implicit
conversion to IList<S> and an explicit conversion from IList<T> back to an
array of type S (or vice a versa...I might have that backwards). My
recollection is that the only constraint on classes S and T were that they
be implicitly or explicitly convertible to the other, as appropriate.

There's certainly something around that - and having converted from
int[] to uint[] I would imagine you could end up with two variables, x
of type IEnumerable<int> and y of type IEnumerable<uint> which are
identical references. I might try that later... if it works, I'll post
a follow-up to Eric. If I can find a suitably tactful way of wording
the "will they fix the C# spec" question I'll ask that too :)
But my real question is: are they going to fix the C# spec or not? :)

The spec will certainly be changing for covariance etc. If we get the
chance to give feedback on a draft, we could try to push for it at
that point if it isn't already fixed :)

Jon
 
J

Jon Skeet [C# MVP]

I agree with that assessment...that's why I'm describing "assignment
compatibility" as "more permissive" than an actual cast. Maybe my
terminology isn't precise enough, but hopefully the point comes across.

I'm not sure it does. There are times when a cast is permitted but an
assignment isn't - doesn't that make casting more permissive in those
cases?

I'm very glad I'm not the one writing the spec :)
But that's my point. The spec already allows for the case where "If Foo
can be cast to Bar, then Foo[] can be cast to Bar[]" (at least, that was
the text in the message you posted from Eric). So since "assignment
compatibility" is already basically a "cast" in a more permissive setting
(that is, it works just like a cast, except there's no need for a run-time
check), I would think that the array-to-array cast is already explicitly
allowed by the CLR spec.

Except that the assignment compatibility doesn't imply castability -
as Eric pointed out, you can't cast from uint to int (in the CLI spec
terms) but the two are assignment compatible.
Given a particular reading of the spec, at least. :)

And that's the problem, of course. Mind you, if they're fixing up the
spec I'd far rather they spent time on the threading aspects than
little niggles like this. I've long held the belief that the spec's
description of what is and isn't allowed in terms of the memory model
leaves rather too much up to interpretation.
Yes, there's also the statement about the inheritance relationship, but
(having not actually gotten around to downloading and looking at the
actual spec) the "2." part in that quoted section from the spec seems to
me to supercede the previous paragraph for the array special case.
Agreed.

Anyway, thanks for looking into this. It's nice to know people with
"friends in high places". :) I look forward to future updates!

For what it's worth, this wasn't a direct mail to Eric (although I've
used that route too in the past - more than I should have, probably).
I'll mail you separately...

Jon
 
J

JS

Interesting thread, and good to know for future gotchas.

I can't figure out, though, what this is: =3F
(Rear view of naked lady sitting on the end of a bench maybe?)
 
J

Jon Skeet [C# MVP]

Interesting thread, and good to know for future gotchas.

I can't figure out, though, what this is: =3F
(Rear view of naked lady sitting on the end of a bench maybe?)

I cut and paste Eric's original reply from email - I suspect it was
curly quotes and apostrophes. Sorry about that.

Jon
 

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

Top