"Eric Lippert's Response"

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".