C# 8 中可空类型和泛型的问题

A problem with Nullable types and Generics in C# 8

添加 <Nullable>enable</Nullable> or #nullable enable 后,我 运行 使用我的通用方法解决了以下问题:

这不起作用:

public T? GetDefault<T>()
{
    return default;
}

这适用于警告:

public T GetDefault<T>()
{
   return default;
}

这可以单独使用,但不能一起使用。

public T? GetDefault<T>() where T : class
{
    return default;
}

public T? GetDefault<T>() where T : struct
{
    return default;
}

从逻辑上讲,第一种方法应该可行。
在不创建多个方法和抑制警告的情况下,摆脱这种情况的正确方法(在任何框架中)是什么?
[MaybeNull] 属性仅适用于 .Net Core 3.0+。

另外,我问了这个问题here

问题说明

您的第一个代码示例中出现问题是因为编译器以不同方式处理可为 null 的值类型和可为 null 的引用类型:

  • 可空值类型 T? 由类型 Nullable<T> 表示。
  • 可空引用类型 T?T 类型相同,但带有编译器生成的属性对其进行注释。

编译器无法生成同时涵盖这两种情况的代码,因此会出现编译错误。这个错误迫使我们指定 classstruct 约束。此行为也在 C# specification:

中说明

For a type parameter T, T? is only allowed if T is known to be a value type or known to be a reference type.

可以在这篇文章中找到对这个问题的很好的解释:Try out Nullable Reference Types。滚动到段落 "The issue with T?".


解决问题的解决方法

如果您不想创建两个具有不同名称的方法并禁止显示警告,则可以使用下一个解决方法:

// An overload that will be used by reference types.
public T? GetDefault<T>(T? t = default) where T : class
{
    return default;
}

// An overload that will be used by value types.
public T? GetDefault<T>(T? t = default) where T : struct
{
    return default;
}

这里我们向方法 GetDefault 添加了一个参数 t 以使编译器能够区分这两个方法。现在我们可以使用方法 GetDefault 并且编译器将定义要使用的重载。这种方法的缺点是 GetDefault 方法有不可用的参数 t.

看来这个问题的最佳解决方案只能在 C# 9 作为 T??

链接:
1. https://github.com/dotnet/csharplang/issues/3471#issuecomment-631722668
2.https://github.com/dotnet/csharplang/issues/3297

目前,Rikki Gibson 提供了一个可行的解决方案。它意味着额外的代码,但它可以正常工作。

T?只能在已知类型参数是引用类型或值类型时使用。否则,我们不知道是将其视为 System.Nullable<T> 还是可空引用类型 T.

相反,您可以使用 [MaybeNull] 属性在 C# 8 中表达这种情况。

#nullable enable
using System.Diagnostics.CodeAnalysis;

public class C
{
    [return: MaybeNull]
    public T GetDefault<T>()
    {
        return default!; // ! just removes warning
    }
}

此属性仅包含在 .NET Core 3.0+ 中,但可以在您的项目内部声明和使用该属性(虽然这不受官方支持,但没有理由假设该行为会破坏线)。为此,您只需在代码中添加一个名称空间+class 声明,类似于以下内容:

namespace System.Diagnostics.CodeAnalysis
{
    /// <summary>Specifies that an output may be null even if the corresponding type disallows it.</summary>
    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)]
    internal sealed class MaybeNullAttribute : Attribute { }
}