How do I cast from Nullable<T> explicitly

i have defined custom value type MyCustomValueType with implicit cast operator from long to MyCustomValueType.

public struct MyCustomValueType
{
    private readonly long number;

    public MyCustomValueType(long? number)
    {
        this.number = number.GetValueOrDefault(0);
    }

    public static implicit operator MyCustomValueType(long number)
    {
        return new MyCustomValueType(number);
    }
}

Then the compiler allows me to do the following:

// ...
long? value = null;
MyCustomValueType myCustomValueType = (MyCustomValueType)value;
Console.WriteLine(myCustomValueType);

Under the hood, the compiler translates the statement with casting into:

MyCustomValueType myCustomValueType = ((long?)null).Value;

I would like to know how (or better said WHY) is this happening? Why does the compiler even allow explicit casting no one is defined. What rules does the compiler apply here?


I should probably also mention that such casting is also possible when MyCustomValueType defines only explicit operator for casting, such as:

public static explicit operator MyCustomValueType(long number)

But in this case I somehow accept what compiler does and understand it. The case with the implicit operator is really confusing. Can someone please explain it?

Jon Skeet
people
quotationmark

Why does the compiler even allow explicit casting no one is defined. What rules does the compiler apply here?

It applies the lifted conversions defined in section 6.4.2 of the C# spec:

Given a used-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?.

So you can think of it as actually:

long? value = null;
long tmp1 = (long) value;       // Unwrapping
MyCustomValueType tmp2 = tmp1;  // User-defined conversion
MyCustomValueType? tmp3 = tmp2; // Wrapping
MyCustomValueType myCustomValueType = (MyCustomValueType) tmp3; // Unwrapping

I don't think it's particularly surprising, to be honest - and in particular, if you understand that something will be feasible when the declared conversion operator is explicit, then it's worth expecting the same usage to be feasible when the declared conversion operator is implicit. (But not necessarily the other way round, of course.)

people

See more on this question at Stackoverflow