Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 827
michal.kohut Avatar asked Feb 26 '26 22:02

michal.kohut


1 Answers

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

like image 55
Jon Skeet Avatar answered Feb 28 '26 13:02

Jon Skeet



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!