可空的引用类型:如何指定“ T”?类型而不限制类或结构

时间:2019-05-03 18:16:39

标签: c# .net-core nullable c#-8.0

我想创建一个具有类型T的成员的通用类。 T可以是类,可为空的类,结构或可为空的结构。所以基本上什么都可以。这是一个简化的示例,显示了我的问题:

#nullable enable

class Box<T> {
    public T Value { get; }

    public Box(T value) {
        Value = value;
    }

    public static Box<T> CreateDefault()
        => new Box<T>(default(T));
}

由于使用了新的#nullable enable功能,我收到以下警告:Program.cs(11,23): warning CS8653: A default expression introduces a null value when 'T' is a non-nullable reference type.

此警告对我来说很有意义。然后,我尝试通过在属性和构造函数参数中添加?来解决此问题:

#nullable enable

class Box<T> {
    public T? Value { get; }

    public Box(T? value) {
        Value = value;
    }

    public static Box<T> CreateDefault()
        => new Box<T>(default(T));
}

但是现在我得到两个错误:

Program.cs(4,12): error CS8627: A nullable type parameter must be known to be a value type or non-nullable reference type. Consider adding a 'class', 'struct', or type constraint.
Program.cs(6,16): error CS8627: A nullable type parameter must be known to be a value type or non-nullable reference type. Consider adding a 'class', 'struct', or type constraint.

但是,我不想添加约束。我不在乎T是类还是结构。

一个明显的解决方案是在#nullable disable指令下包装有问题的成员。但是,像#pragma warning disable一样,除非必要,我想避免这样做。是否有另一种方法可以在不禁用可空性检查或CS8653警告的情况下编译我的代码?

$ dotnet --info
.NET Core SDK (reflecting any global.json):
 Version:   3.0.100-preview4-011223
 Commit:    118dd862c8

3 个答案:

答案 0 :(得分:4)

如关于该问题的评论中所述,您可能需要考虑具有默认值的Box<string>在可为空的上下文中是否有效,并可能相应地调整您的API表面。为了使包含默认值的实例有效,也许类型必须为Box<string?>。但是,在某些情况下,您将需要指定属性,方法返回值或参数等仍然为空,即使它们具有不可为空的引用类型。如果您属于该类别,则可能需要使用与可空性相关的属性。

.NET Core 3中引入了MaybeNullAllowNull属性来处理这种情况。

这些属性的某些特定行为仍在发展,但基本思想是:

  • [MaybeNull]表示某事物(读取字段或属性,方法返回等)的输出可能是null
  • [AllowNull]表示对某物(写字段或属性,方法参数等)的输入可以是null
#nullable enable
using System.Diagnostics.CodeAnalysis;

class Box<T>
{
    [MaybeNull]
    public T Value { get; }

    public Box([AllowNull] T value) // 1
    {
        Value = value;
    }

    public static Box<T> CreateDefault()
    {
        return new Box<T>(default(T)!); // 2
    }

    public static void UseStringDefault()
    {
        var box = Box<string>.CreateDefault();
        _ = box.Value.Length; // 3
    }

    public static void UseIntDefault()
    {
        var box = Box<int>.CreateDefault();
        _ = box.Value.ToString(); // 4
    }
}

注意:

  1. 流分析属性当前仅影响对带注释成员的调用,而不影响实现。这意味着,如果我们从属性[MaybeNull]中删除了Value属性,则不会收到有关为其分配[AllowNull] T value参数的警告。实际上,[AllowNull]只是阻止在呼叫站点发出可能为空的参数警告。
  2. 编译器对无约束(即不知道是值或引用类型)类型参数的分析是有限的。此时,这意味着编译器将发出警告:“当'T'是不可为空的引用类型时,默认表达式会引入一个空值。由于编译器无法始终确定T类型的值的使用是否为null安全,因此它在T可能是不可为null的引用类型但可能会引入'null'的地方发出警告。发生这种情况最明显的地方是使用default(T),但是也可能发生在调用以[MaybeNull]注释其返回值的泛型方法时。目前,我们通过使用default(T)!取消警告来解决该问题。
  3. 这是一个更简单的情况,由于属性上有box.Value注释,我们在取消引用[MaybeNull]时会给出可空性警告。
  4. 在这种情况下,MaybeNull属性不会在使用时引起警告,因为box.Value是不可为空的值类型。

有关更多信息,请参见https://devblogs.microsoft.com/dotnet/try-out-nullable-reference-types,尤其是“ the issue with T?”部分。

答案 1 :(得分:0)

杰夫·梅卡多(Jeff Mercado)在评论中提出了一个好观点:

  

我认为您的目标有些矛盾。您想拥有默认框的概念,但对于引用类型,还有什么合适的默认值?对于与使用可为空的引用类型直接冲突的引用类型,默认值为null。也许您需要将T约束为可以默认构造的类型(new())。

例如,then()的{​​{1}}将是default(T),因为在运行时T = stringnull之间没有区别。这是语言功能的当前限制。

我通过为每种情况创建单独的string方法来解决这种局限性:

string?

似乎类型对我来说很安全,但要花更麻烦的呼叫站点(CreateDefault而不是#nullable enable class Box<T> { public T Value { get; } public Box(T value) { Value = value; } } static class CreateDefaultBox { public static Box<T> ValueTypeNotNull<T>() where T : struct => new Box<T>(default); public static Box<T?> ValueTypeNullable<T>() where T : struct => new Box<T?>(null); public static Box<T> ReferenceTypeNotNull<T>() where T : class, new() => new Box<T>(new T()); public static Box<T?> ReferenceTypeNullable<T>() where T : class => new Box<T?>(null); } )。在我发布的示例类中,我将完全删除方法并直接使用CreateDefaultBox.ReferenceTypeNullable<object>()构造函数。哦,很好。

答案 2 :(得分:-5)

const regex = /^(.+ \?)\n((?:\n(?!.* \?$).*)*)/gm;
const str = `Combien ?

Lorem ipsum. Lorem ipsum. Lorem ipsum. Lorem ipsum. Lorem ipsum. Lorem ipsum. Lorem ipsum. Lorem ipsum. Lorem ipsum. Lorem ipsum. Lorem ipsum. Lorem ipsum. Lorem ipsum.

test

Combien 2 ?

Lorem ipsum.Lorem ipsum.Lorem ipsum.Lorem ipsum. Lorem ipsum.Lorem ipsum.Lorem ipsum. Lorem ipsum.Lorem ipsum.Lorem ipsum.Lorem ipsum.Lorem ipsum.`;
let m;

while ((m = regex.exec(str)) !== null) {
  if (m.index === regex.lastIndex) {
    regex.lastIndex++;
  }
  console.log("Question: " + m[1]);
  console.log("Answer: " + m[2]);
}

What does null! statement mean?