关于c#:两个方法参数之间的泛型类型参数中的可空性不匹配

Nullability mismatch in generic type parameter between two method arguments

我编写了以下扩展方法:

1
2
3
4
5
// using System.Collections.Generic;

internal static class TExtensions {
    internal static bool In< T >(this T val, HashSet< T > hs) => hs.Contains(val);
}

并尝试按如下方式使用它:

1
2
3
var s = DateTime.Now.Hour < 15 ?"abcd" : null;
var hs = new HashSet<string>();
Console.WriteLine(s.In(hs));

编译器在最后一行给我一个警告:

CS8620 Argument of type 'HashSet' cannot be used for parameter 'hs' of type 'HashSet' in 'bool TExtensions.In(string? val, HashSet? hs)' due to differences in the nullability of reference types.

因为编译器将 T 类型参数解析为 sstring? 的类型;但散列集不是 HashSet<T>,它是可空 string (HashSet<string?>) 的散列集,而是不可空 string (HashSet<string>) 的散列集。

我可以通过package一个空检查来解决这个问题:

1
2
3
if (s is { }) {
    var result = s.In(hs);
}

或将哈希集显式键入为具有可为空的元素:

1
var hs = new HashSet<string?>();

但是有什么方法可以使用可为空的属性来允许这种情况吗?或者还有什么我可以在 In 扩展方法中改变的吗?


In方法的第一个参数可以使用[AllowNull]属性:

1
internal static bool In< T >([AllowNull] this T val, HashSet< T > hs) => hs.Contains(val);

但是,正如您在问题中描述的那样,这里的实际问题是推断的泛型类型参数由 s In 方法将被称为 In<string?>,它要求哈希集为 HashSet<string?>。您必须告诉编译器使用 string 而不是 string?s.In<string>(hs).

如果你可以约束到不可为空的引用类型,你可以使用这个:

1
internal static bool In< T >(this T? val, HashSet< T > hs) where T : class => val != null && hs.Contains(val);

对于值类型,实现将是:

1
internal static bool In< T >(this T? val, HashSet< T > hs) where T : struct => val.HasValue && hs.Contains(val.Value);

请注意,如果类型参数本身可以为空,则只有第一个版本(带有 [AllowNull] 属性)有效。

您当然可以使用 null-forgiving-operator 来关闭编译器,而不是更改扩展方法:s.In(hs!)s!.In(hs).