Roslyn: uint inconsitency

Created on 30 Dec 2016  路  6Comments  路  Source: dotnet/roslyn

uint u = -1;

Fails with the following compilation error:
Error CS0031 Constant value '-1' cannot be converted to a 'uint'

const int i = -1;
uint u = (uint)i;

Fails with
Error CS0221 Constant value '-1' cannot be converted to a 'uint' (use 'unchecked' syntax to override).
First, why we didn't have this error in the first case?

Now worse IMHO,

int i = -1;
uint u = (uint)i;

Succeeds in compile time (expected as C# does not do constant folding) but also at runtime.
Why does runtime does not throw an exception because we don't use unchecked?

If C# allows this, why does it fail in the 2 first scenarios? Isn't it inconsistent?

BTW note that if in the watch window, we use (uint)-1, we got 4294967295 on the Value column.

Most helpful comment

Sure, one that is 16 years old. The line between signed and unsigned has always been a bit blurred, which is why you can outright disable any checking via unchecked. The language could attempt to better enforce conversion but can't do so without breaking existing code and/or introducing performance overhead. Maybe casting an int to a uint or vice versa should've always been a compiler error outside of an unchecked context, but it's too late for that.

The alternative would be to ditch the compiler errors and to treat all code as unchecked, but I'm of the opinion that if the language can aid in preventing bugs due to accidentally casting between signed and unsigned values then it should.

All 6 comments

int and uint are not separate data types in IL. They're both represented by the same data type, int32 and the (un)signed behavior depends on the IL opcodes performed against it. So casting from int to uint is actually a no-op and the only way that C# could force a form of runtime exception would be to manually check that the value is less than zero.

Ok but then why does uint u = -1; returns an error. A warning should be better IMHO in this case.

Probably to force the intent of the developer. The C# language does mostly treat the two as separate data types.

So you don't think my 3 samples show an inconsistency?

Sure, one that is 16 years old. The line between signed and unsigned has always been a bit blurred, which is why you can outright disable any checking via unchecked. The language could attempt to better enforce conversion but can't do so without breaking existing code and/or introducing performance overhead. Maybe casting an int to a uint or vice versa should've always been a compiler error outside of an unchecked context, but it's too late for that.

The alternative would be to ditch the compiler errors and to treat all code as unchecked, but I'm of the opinion that if the language can aid in preventing bugs due to accidentally casting between signed and unsigned values then it should.

I belive this behavior is intentional. From The checked and unchecked operators in the C# spec:

For non-constant expressions (expressions that are evaluated at run-time) that are not enclosed by any checked or unchecked operators or statements, the default overflow checking context is unchecked unless external factors (such as compiler switches and execution environment configuration) call for checked evaluation.

For constant expressions (expressions that can be fully evaluated at compile-time), the default overflow checking context is always checked. Unless a constant expression is explicitly placed in an unchecked context, overflows that occur during the compile-time evaluation of the expression always cause compile-time errors.

Was this page helpful?
0 / 5 - 0 ratings