.NET 不可为 null 的引用类型和输出参数

.NET non-nullable reference type and out parameters

我修改了 csproj 文件以在 C#8 中启用空引用类型:

<Nullable>enable</Nullable>

给定以下代码:

private static void Method()
{
    var dictionary = new Dictionary<string, string>();
    string value = string.Empty;

    dictionary.TryGetValue("Key", out value);
}

带有TryGetValue()的行给出了警告:

CS8600: Converting null literal or possible null value to non-nullable type.

我不明白为什么。 TryGetValue() 的签名是:

public bool TryGetValue(string key, [MaybeNullWhen(false)] out string value);

代码示例只有不可为空的引用。为什么会出现此错误?

如果在字典中找不到 "Key",则 null 的值将分配给 value 变量。但是,您已将 value 声明为 string,这意味着它不应包含 null。因此编译器给你一个警告。

您最初将 string.Empty 分配给 value 的事实并不重要 - 它总是会被 TryGetValue 覆盖(并且您应该收到另一个警告,上面写着).

您应该将 value 声明为 string?,以表明它的值可能是 null

请注意,编译器非常智能。如果你写:

if (!dictionary.TryGetValue("Key", out string? value))
{
    value = string.Empty;
}

然后编译器知道 value 不能是 null,如果您随后尝试调用它的方法,它也不会报错。

canton7的答案是正确的(+1)。
这不是解释,而是解决方法:
您可以像这样向 Dictionary<TVey, TValue> 添加扩展方法:

public static bool TryGetValue<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key, TValue @default, out TValue @value) where TKey : notnull
{
    var result = dictionary.TryGetValue(key, out var val);
    @value = result ? val : @default;
    return result;
}

然后你可以这样使用它:

private static void Method()
{
    var dictionary = new Dictionary<string, string>();
    /// populate dictionary here...

    dictionary.TryGetValue("Key", string.Empty, out var value);
}

这应该使您能够将 value 保留为不可为 null 的字符串。

来自文档 Attributes for null-state static analysis interpreted by the C# compiler

In a nullable enabled context, the compiler performs static analysis of code to determine the null-state of all reference type variables:

  • not-null: Static analysis determines that a variable has a non-null value.
  • maybe-null: Static analysis can't determine that a variable is assigned a non-null value.

静态分析器认为一个变量可以是:

  • 可为空
  • 不可为空
  • 可能为空

当一个不可为空的变量用属性 MaybeNull 修饰时,静态分析器认为该变量可能可以为空。

[return: MaybeNull]
static string Find(string key)
{
    return key == "" ? null : key;
}

string value1 = Find("key"); // Warning CS8600 Converting null literal or possible null value to non-nullable type.
string? value2 = Find("key"); // No warning
var value3 = Find("key"); // The inferred type is 'string?'

MaybeNullWhen属性类似,但静态分析器可以根据方法的结果处理检查。

static bool TryGetValue(string key, [MaybeNullWhen(false)] out string value)
{
    if(key == "")
    {
        value = null;
        return false;
    }
    value = "Foo";
    return true;
}

string notnullable;

string value1;
if (TryGetValue("Key", out value1)) // Warning CS8600 Converting null literal or possible null value to non-nullable type.
    notnullable = value1;
else
    notnullable = value1; // Warning CS8600 Converting null literal or possible null value to non-nullable type.

string? value2;
if (TryGetValue("Key", out value2))
    notnullable = value2;
else
    notnullable = value2; // Warning CS8600 Converting null literal or possible null value to non-nullable type.

我同意,这在这个例子中没有意义。但是使用泛型方法,您可以指定一个不可为 null 的类型,而该方法可以 return/set null:

[return: MaybeNull]
static T Find<T>(string key);
static bool TryGetValue<T>(string key, [MaybeNullWhen(false)] out T value)