Home > Mobile >  Why nullable value type assignment does not fail with "Converting null literal or possible null
Why nullable value type assignment does not fail with "Converting null literal or possible null

Time:05-06

In C#10 I was trying to create a Result type that would have an error or a value (without going for monadic stuff).

When trying to use it I was expecting a compiler warning/error but I got nothing.

Why?

My .csproj has:

<Nullable>enable</Nullable>
<WarningsAsErrors>nullable</WarningsAsErrors>

The code:

public record Error();

public record Result<T> where T : notnull
{
    private Result()
    {
    }

    public Error? Error { get; private init; }
    public T? Value { get; private init; }
    
    [MemberNotNull(nameof(Error))]
    public static implicit operator Result<T>(Error error)
    {
        return new Result<T> {Error = error};
    }
    
    [MemberNotNull(nameof(Value))]
    public static implicit operator Result<T>(T result)
    {
        return new Result<T> {Value = result};
    }

    [MemberNotNullWhen(false, nameof(Value))]
    [MemberNotNullWhen(true, nameof(Error))]
    public bool IsError()
    {
        return Error is not null;
    }
}

public record TryResult
{
    public void ValueTypes()
    {
        Result<Guid> result = new Error();
        Guid value = result.Value; // no error, why?

        if (!result.IsError())
            value = result.Value;
    }
    public void RefTypes()
    {
        Result<string> result = new Error();
        string value = result.Value; // error
        
        if (!result.IsError())
            value = result.Value; // OK
    }
}

CodePudding user response:

Result<Guid> result = new Error();
var value = result.Value; // no error, why?

result is not null: it's created on the line above, through an implicit conversion from Error, and the implicit conversion returns a non-null Result<T>. So accessing result.Value can't fail.

Your Value property has type T? (ignoring the MemberNotNull stuff -- we'll come to that in the next section). Since T is unconstrained, the T? means "if T is a reference type, this can be null; if T is a value type, the ? is ignored". Since T is Guid, this means that the property Value just has type Guid. So writing var value = result.Value is the same as writing Guid value = result.Value, which is fine.

If T were a reference type (such as string), then the Value property would have type string?, i.e. a nullable string. Even so, the compiler would infer that the var represents a string?, so that assignment would be the same as writing string? value = result.Value, which wouldn't cause any nullability errors.


Result<string> result = new Error();
string value = result.Value; // error

This time, T is a string, so the Value property has type string?. You're trying to assign this to a variable of type string, hence the error.

It seems that the compiler isn't able to do flow analysis from MemberNotNull through an implicit conversion, which I suppose makes sense. The property name you pass applies to the property on the current instance, but implicit conversions are static methods, and so there is no current instance for it to apply to.


if (!result.IsError())
    value = result.Value; // OK

Here, MemberNotNUll is performing as expected.

  • Related