如何识别泛型类型的可为空引用类型?

Jas*_* Yu 6 c# generics c#-8.0 nullable-reference-types

在启用了可为空的 C# 8 中,有没有办法为泛型类型识别空的引用类型?

对于null 的值类型,有一个专用于它的部分。 https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/nullable-value-types#how-to-identify-a-nullable-value-type

我们正在尝试根据泛型类型进行可选的空检查

#nullable enable
public static Result<T> Create<T>(T value)
{
   if (!typeof(T).IsNullable() && value is null)
      throw new ArgumentNullException(nameof(value));

   // Do something
}

public static bool IsNullable(this Type type)
{
   // If type is SomeClass, return false
   // If type is SomeClass?, return true
   // If type is SomeEnum, return false
   // If type is SomeEnum?, return true
   // If type is string, return false
   // If type is string?, return true
   // If type is int, return false
   // If type is int?, return true
   // etc
}
Run Code Online (Sandbox Code Playgroud)

因此,ArgumentNullExceptionT不可为空时,以下将抛出但允许值为 null 时T为空,但可以为空时,例如

Create<Anything>(null); // throw ArgumentNullException

Create<Anything?>(null); // No excception
Run Code Online (Sandbox Code Playgroud)

Ili*_*hev 5

在启用了可为空的 C# 8 中,有没有办法为泛型类型识别空的引用类型?

C# 8没有办法检查是否传递给一个通用的方法的类型的参数是可为空的引用类型或没有。

问题是任何可为空的引用类型T?都由相同的类型表示T但使用编译器生成的属性对其进行注释),而不是T?由实际的 .NET 类型表示的可为空的值类型Nullable<T>

当编译器生成调用泛型方法的代码时F<T>,其中T可以是可空引用类型,也可以不是可空引用类型,如果T是可空引用类型的信息将丢失。让我们考虑下一个示例方法:

public void F<T>(T value) { }
Run Code Online (Sandbox Code Playgroud)

对于下一次调用

F<string>("123");
F<string?>("456");
Run Code Online (Sandbox Code Playgroud)

编译器将生成下一个IL代码(我简化了一点):

call    F<string>("123")
call    F<string>("456")
Run Code Online (Sandbox Code Playgroud)

您可以看到,向第二个方法string传递了一个类型参数,而不是string?因为string?在执行期间可空引用类型的表示是相同的类型string

因此,在执行过程中,无法定义传递给泛型方法的类型参数是否为可空引用类型。


我认为对于您的情况,最佳解决方案是传递一个bool值,该值将指示引用类型是否可为空。这是一个示例,它是如何实现的:

public static Result<T> Create<T>(T value, bool isNullable = false)
{
    Type t = typeof(T);

    // If type "T" is a value type then we can check if it is nullable or not.
    if (t.IsValueType) 
    {
        if (Nullable.GetUnderlyingType(t) == null && value == null)
            throw new ArgumentNullException(nameof(value));
    }
    // If type "T" is a reference type then we cannot check if it is nullable or not.
    // In this case we rely on the value of the argument "isNullable".
    else
    {
        if (!isNullable && value == null)
            throw new ArgumentNullException(nameof(value));
    }

    ...
}
Run Code Online (Sandbox Code Playgroud)