First source of confusion: in C# we have conflated two completely
different operations as 'cast' 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 'cast' from int to float in
the CLR. That's 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.
Code snippet taken from "Postmortems - Tale of Two Casts".