c# - 使用 .NET 5 SDK 构建时引用类型错误的可空性差异

标签 c# c#-8.0 .net-5 nullable-reference-types

我在 IDictionary 上实现了以下扩展方法,它将尝试从字典中获取值,但返回默认值(default(T) 或用户提供的)如果 key 不存在。第一个没有用户提供值的方法将调用另一个具有 default 的方法。

[return: MaybeNull]
public static T GetValueOrDefault<TKey, T>(this IDictionary<TKey, T> source, TKey key) where TKey : notnull
{
    return GetValueOrDefault(source, key, defaultValue: default);
}

[return: MaybeNull]
public static T GetValueOrDefault<TKey, T>(this IDictionary<TKey, T> source, TKey key, [AllowNull] T defaultValue) where TKey : notnull
{
    if (source is null) throw new ArgumentNullException(nameof(source));
    if (key is null) throw new ArgumentNullException(nameof(key));
    
    if (source.TryGetValue(key, out var item))
    {
        return item;
    }

    return defaultValue;
}

使用 .NET SDK 3.1.100,此代码可以正常构建。但是,使用最新的 .NET SDK 5.0.101,我收到以下错误消息:

error CS8620: Argument of type 'IDictionary<TKey, T>' cannot be used for parameter 'source' of type 'IDictionary<TKey, T?>' in 'T? DictionaryExtensions.GetValueOrDefault<TKey, T?>(IDictionary<TKey, T?> source, TKey key, T? defaultValue)' due to differences in the nullability of reference types.

它提示在 GetValueOrDefault(source, key, defaultValue: default) 中使用 default。当然,使用 default! 会抑制错误消息,但该值应该可以为空(因此 defaultValue 上的 AllowNullAttribute )。或者可能由于属性和用法而推断 T 可为空,并且不允许使用不可为空的 T 进行调用?

仅当 T 是通用且不受 class 限制时才会产生错误。例如,以下代码不会产生错误:

var dict = new Dictionary<string, string>();
dict.GetValueOrDefault("key", null);

我做错了什么吗?新的 .NET 版本是否进一步收紧了对可空引用类型的限制?这只是 .NET SDK 5.0.101 的一个错误吗?

最佳答案

在这种情况下,我认为可空性分析刚刚得到改进。

[AllowNull]和 friend 不会影响编译器对泛型类型参数的推断。这里似乎发生的是编译器正在查看对 GetValueOrDefault(source, key, defaultValue: default) 的调用。并试图推断什么 TKeyT是。因为你正在通过default值为 T (忽略 [AllowNull] ),它意识到 TGetValueOrDefault必须可为空,即它正在调用 GetValueOrDefault<TKey, T?>(source, key, defaultValue: default) .

但是,它也意识到source可能是IDictionary<TKey, T> (因此 T 不可为空),并意识到这里存在冲突。

这都是学术性的,如 C# 9 introduced the T? syntax 。这比添加属性要简洁得多,支持诸如 Task<T?> 之类的东西。 ,并且与编译器集成得更好。

这正如您所期望的那样:

public static T? GetValueOrDefault<TKey, T>(this IDictionary<TKey, T> source, TKey key) where TKey : notnull
{
    return GetValueOrDefault(source, key, defaultValue: default);
}

public static T? GetValueOrDefault<TKey, T>(this IDictionary<TKey, T> source, TKey key, T? defaultValue) where TKey : notnull
{
    if (source is null) throw new ArgumentNullException(nameof(source));
    if (key is null) throw new ArgumentNullException(nameof(key));

    if (source.TryGetValue(key, out var item))
    {
        return item;
    }

    return defaultValue;
}

这里编译器仍然注意到您正在传递 default ,但它意识到 defaultValue有一个类型 T?而不是T (之前它忽略 [AllowNull] 属性),因此不会强制 T可以为空。


如果您坚持使用 C# 8,那么显式指定泛型类型参数似乎会阻止编译器推断 T作为T? ,这消除了警告:

return GetValueOrDefault<TKey, T>(source, key, defaultValue: default);

关于c# - 使用 .NET 5 SDK 构建时引用类型错误的可空性差异,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65406019/

相关文章:

c# - 使用.NET将多个ogg vorbis文件顺序连接到一个文件中?

c# - 类库 COM 互操作项目的 installshield

c# - WebAPI 中的 EFCore 3.1 应返回什么类型的集合?

c# - 在 Visual Studio 2019 中找不到 .NET 5.0 控制台应用程序项目模板

entity-framework - EF6 实体数据模型设计器不适用于目标框架 .net 5.0

c# - 使用 .NET 5 Azure Functions 中的 Content-Type multipart/form-data 从 HttpRequestData 获取 POST 参数

c# - 如何从 C# 保存到 Excel?

c# - 在特定桌面上启动进程

c# - vs2019.9.2 对 undefined variable 使用属性模式时引发运行时错误并编译失败,这是预期的编译错误

c# - 空合并赋值运算符的行为