Ramin Rahimzada
Ramin Rahimzada

Reputation: 482

Strange type conversion on c# Expressions

Lets have a simple class

class User
{
   public byte IdByte { get; set; }
}

So when I write expressions like these:


Expression<Func<User, bool>> expression1 = x => x.IdByte == 3;

Expression<Func<User, bool>> expression2 = x => x.IdByte == (byte)3;

byte b = 3;
Expression<Func<User, bool>> expression3 = x => x.IdByte == b;

Expression<Func<User, bool>> expression4 = x => x.IdByte == byte.MaxValue;

And watch that expressions debug view, I see that there is an additional type conversion to type System.Int32 :

//expression1  ------>  x => (Convert(x.IdByte, Int32) == 3)
//expression2  ------>  x => (Convert(x.IdByte, Int32) == 3)
//expression3  ------>  x => (Convert(x.IdByte, Int32) == Convert(value(....c__DisplayClass1_0).b, Int32))
//expression4  ------>  x => (Convert(x.IdByte, Int32) == 255)

In first and second expressions converting right side - 3 to byte is more reasonable than converting left side to int.
In rest of the cases left and right side is byte
My question is why these conversions applied ?

Upvotes: 4

Views: 112

Answers (2)

DekuDesu
DekuDesu

Reputation: 2292

To build on canton7's Answer.

The reason why bytes(and other types) in particular are converted into integers is for language interoperability by ensuring that the default arithmetic behavior will have identical results on all implementations.

For the specific reason we would have to refer to the Common Language Infrastructure Standard(ECMA-335) that controls how the runtime and compiler should handle types, whether on the evaluation or verification stacks respectively.

ECMA-335 §III.1.1

While the [Common Type System] defines a rich type system and the [Common Language Specification] specifies a subset that can be used for language interoperability, the [Common Language Infrastructure] itself deals with a much simpler set of types.

ECMA-335 §III.1.1.1

The [Common Language Infrastructure] only operates on the numeric types int32 (4-byte signed integers), int64 (8-byte signed integers), native int (native-size integers), and F (native-size floating-point numbers).

ECMA-335 §III.1.1.1.2's Note

Short (i.e., 1- and 2-byte) integers are loaded as 4-byte numbers on all architectures and these 4-byte numbers are always tracked as distinct from 8-byte numbers. This helps portability of code by ensuring that the default arithmetic behavior will have identical results on all implementations.

Upvotes: 1

canton7
canton7

Reputation: 42225

Let's check the spec:

Integer comparison operators

The predefined integer comparison operators are:

bool operator ==(int x, int y);
bool operator ==(uint x, uint y);
bool operator ==(long x, long y);
bool operator ==(ulong x, ulong y);

bool operator !=(int x, int y);
bool operator !=(uint x, uint y);
bool operator !=(long x, long y);
bool operator !=(ulong x, ulong y);

bool operator <(int x, int y);
bool operator <(uint x, uint y);
bool operator <(long x, long y);
bool operator <(ulong x, ulong y);

bool operator >(int x, int y);
bool operator >(uint x, uint y);
bool operator >(long x, long y);
bool operator >(ulong x, ulong y);

bool operator <=(int x, int y);
bool operator <=(uint x, uint y);
bool operator <=(long x, long y);
bool operator <=(ulong x, ulong y);

bool operator >=(int x, int y);
bool operator >=(uint x, uint y);
bool operator >=(long x, long y);
bool operator >=(ulong x, ulong y);

Each of these operators compares the numeric values of the two integer operands and returns a bool value that indicates whether the particular relation is true or false.

As you can see, we don't actually have an == operator which compares two bytes, or two sbytes, chars, shorts, or ushorts.

However, there are implicit conversions from these types to int, and so the compiler applies these conversions to both sides, and uses bool ==(int x, int y).

If you write:

byte val = 3;
bool b = val == (byte)4;

The compiler will effectively turn this into:

byte val = 3;
bool b = (int)val == (int)(byte)4;

That (int)(byte)4 cast is of course completely unnecessary: the compiler will just turn this into an integer literal.

byte val = 3;
bool b = (int)val == 4;

Upvotes: 4

Related Questions