Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't I cast one instantiation of a generic type to another?

Tags:

How can I implement a struct so that the following cast can be performed?

var a = new StatusedValue<double>(1, false);
var b = (StatusedValue<int>)a;

My implementation should behave similarly to Nullable<T>, which works fine. However, this code fails with System.InvalidCastException:

public struct StatusedValue<T>  where T : struct
{
    public StatusedValue(T value) : this(value, true)
    {

    }

    public StatusedValue(T value, bool isValid)
    {
        this.value = value;
        this.isValid = isValid;
    }

    private T value;
    private bool isValid;

    public static implicit operator StatusedValue<T>(T value)
    {
        return new StatusedValue<T>(value);
    }

    public static explicit operator T(StatusedValue<T> value)
    {
        return value.value;
    }
}

Result:

Unable to cast object of type 'StatusedValue`1[System.Double]' to type 'StatusedValue`1[System.Int32]'.

like image 378
Vladimir Sachek Avatar asked Sep 05 '14 11:09

Vladimir Sachek


2 Answers

This works for Nullable<T> types because they get special treatment from the compiler. This is called a "lifted conversion operator", and you cannot define your own.

From section 6.4.2 of the C# Specification:

6.4.2 Lifted conversion operators

Given a user-defined conversion operator that converts from a non-nullable value type S to a non-nullable value type T, a lifted conversion operator exists that converts from S? to T?. This lifted conversion operator performs an unwrapping from S? to S followed by the user-defined conversion from S to T followed by a wrapping from T to T?, except that a null valued S? converts directly to a null valued T?. A lifted conversion operator has the same implicit or explicit classification as its underlying user-defined conversion operator. The term “user-defined conversion” applies to the use of both user-defined and lifted conversion operators

like image 176
dcastro Avatar answered Oct 06 '22 16:10

dcastro


If you're happy calling a method, try

public StatusedValue<U> CastValue<U>() where U : struct
{
    return new StatusedValue<U>((U)Convert.ChangeType(value, typeof(U)), isValid);
}

This will unfortunately throw at runtime rather than compile time if T cannot be converted to U.

Edit: As pointed out below, if you constrain to IConvertible as well as/instead of struct then every conversion is theoretically possible at compile time, and you'll only get a runtime failure because of bad runtime values.

like image 22
Rawling Avatar answered Oct 06 '22 15:10

Rawling