为什么可为 null 的值类型分配不会因 "Converting null literal or possible null value to non-nullable type." 而失败

Why nullable value type assignment does not fail with "Converting null literal or possible null value to non-nullable type."

在 C#10 中,我试图创建一个有错误或值的结果类型(不使用 monadic 的东西)。

当我尝试使用它时,我期待一个编译器 warning/error 但我什么也没得到。

为什么?

我的 .csproj 有:

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

代码:

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
    }
}
Result<Guid> result = new Error();
var value = result.Value; // no error, why?

result 不为空:它是在上面的行中创建的,通过 Error 的隐式转换,隐式转换 returns a non-null Result<T>.所以访问result.Value不会失败。

您的 Value 属性 类型为 T?(忽略 MemberNotNull 内容——我们将在下一节中讨论)。由于 T 不受约束,因此 T? 表示“如果 T 是引用类型,则它可以为 null;如果 T 是值类型,则 ?被忽略”。由于 TGuid,这意味着 属性 Value 的类型只有 Guid。所以写var value = result.Value和写Guid value = result.Value是一样的,没问题。

如果 T 是引用类型(例如 string),则 Value 属性 的类型将是 string?,即可为空的字符串.即便如此,编译器仍会推断 var 表示 string?,因此赋值与写入 string? value = result.Value 相同,不会导致任何可空性错误。


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

这一次,Tstring,因此 Value 属性 的类型为 string?。您正在尝试将其分配给 string 类型的变量,因此出现错误。

编译器似乎无法通过隐式转换从 MemberNotNull 进行流分析,我认为这是有道理的。您传递的 属性 名称适用于 当前实例 上的 属性,但隐式转换是静态方法,因此没有适用的当前实例到.


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

此处,MemberNotNUll 表现符合预期。