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

Kon*_* S. 12 c# generics c#-8.0 nullable-reference-types

添加<Nullable>enable</Nullable>or 后#nullable enable,我的通用方法遇到了以下问题:

这不起作用:

public T? GetDefault<T>()
{
    return default;
}
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

这适用于警告:

public T GetDefault<T>()
{
   return default;
}
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

这单独工作,但不能一起工作。

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

public T? GetDefault<T>() where T : struct
{
    return default;
}
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

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

另外,我在这里问了这个问题

Rik*_*son 11

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
    }
}
Run Code Online (Sandbox Code Playgroud)

此属性仅包含在 .NET Core 3.0+ 中,但可以在项目内部声明和使用该属性(尽管这不受官方支持,但没有理由假设该行为会破坏线路)。为此,您只需在代码中添加命名空间+类声明,如下所示:

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 { }
}
Run Code Online (Sandbox Code Playgroud)


Ili*_*hev 9

问题说明

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

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

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

对于类型参数TT?仅当T已知为值类型或已知为引用类型时才允许。

在这篇文章中可以找到对这个问题的一个很好的解释:Try out Nullable Reference Types。滚动到“与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;
}
Run Code Online (Sandbox Code Playgroud)

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


Kon*_* S. 5

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

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

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