Suraj Gharat Suraj Gharat - 21 days ago 8
C# Question

Inconsistency in C# language syntax

I'm learning C# and came across few things that made me little uncomfortable about the its syntax.

Case 1

byte num1 = 10; // works
int ten = 10;
byte num2 = ten; // Compile error: Cannot implicitly convert 'int' to byte. An explicit conversion exists.


In first statement compiler implicitly casts literal 10 (of type int) to byte, conversely it does not do same in third statement.

Case 2

int[] numbers1 = { 10, 20, 30 }; // works
int[] numbers2;
numbers2 = { 10, 20, 30 }; // Compiler error: Invalid expression term: {


The above shortened array-initializer does not work in every statement.

And there may be many more such inconsistencies...

It seems the error-versions are right because those are as per the defined syntax, and non-error-versions (for similar cases) are language created constructs to just make the language easy to code in.

But still shouldnt it be consistent in every place we use it?

Answer

Case 1 is a special case covered explicitly by the C# Language Specification.

From §6.1.9 Implicit constant expression conversions:

An implicit constant expression conversion permits the following conversions:

• A constant-expression (§7.19) of type int can be converted to type sbyte, byte, short, ushort, uint, or ulong, provided the value of the constant-expression is within the range of the destination type.

• A constant-expression of type long can be converted to type ulong, provided the value of the constant-expression is not negative.

Because the variable ten is not declared as const then the above rule does not apply, and no implicit conversion is allowed and you get a compile error.

Note that if you change ten to be const int ten = 10; then it will work because, of course, it is now a constant.

Case 2 is enabled by a feature of the new operator.

From §1.8 Arrays:

The new operator permits the initial values of the array elements to be specified using an array initializer, which is a list of expressions written between the delimiters { and }.

The following example allocates and initializes an int[] with three elements.

int[] a = new int[] {1, 2, 3};

Note that the length of the array is inferred from the number of expressions between { and }. Local variable and field declarations can be shortened further such that the array type does not have to be restated.

int[] a = {1, 2, 3};

Observe how it allows you to omit the new keyword, even though it is actually using the new operator behind the scenes.

So because this syntactic sugar is provided by the new operator, it can only be used when implicitly using new.

In your example, you can still partially use this syntax when you separate declaration and initialisation, but you must then explicitly use the new keyword:

int[] numbers2;
numbers2 = new [] { 10, 20, 30 };

Now you could argue that the compiler could allow the syntax without new, since it knows the type of numbers2 and could infer that the presence of a { and } means that it must be an array initialisation. However, that would require a new rule and I imagine that the language designers didn't think that it would be used enough to justify adding it.