避免对具有可空类型/字段的泛型发出警告的最佳方法?

时间:2019-12-06 12:13:47

标签: c# nullable-reference-types

考虑以下人为设计的类:

public sealed class Test<T>
{
    public void SetItem(T item)
    {
        _item = item;
    }

    public T GetItem()
    {
        return _item;
    }

    T _item; // warning CS8618: Non-nullable field '_item' is uninitialized.
}            // Consider declaring the field as nullable.

假定它必须与以下代码一起使用:

var test1 = new Test<int>();
test1.SetItem(1);
Console.WriteLine(test1.GetItem());

var test2 = new Test<string>();
test2.SetItem("One");
Console.WriteLine(test2.GetItem());

进一步假设在编译时启用了nullable

此实现产生一个编译器警告(如果像我们一样,启用了“警告作为错误”,则会变成错误):

  

警告CS8618:不可空字段'_item'未初始化。考虑将字段声明为可空。

我的问题很简单:

避免此警告的最佳方法是什么?

我尝试过的事情:

1)将_item声明为可空:

T? _item; // Error: A nullable type parameter must be known to be a value type or non-nullable reference type

2)将该类声明为:

public sealed class Test<T> where T: struct

这避免了警告,但是现在var test2 = new Test<string>();行将无法编译

3)将该类声明为:

public sealed class Test<T> where T: class
...
T? _item;

这也避免了警告,但是var test1 = new Test<int>();行现在不会编译。

所以我现在是这样的:

public sealed class Test<T>
{
    public void SetItem(T item)
    {
        _item = item;
    }

    public T GetItem()
    {
        return _item;
    }

    #pragma warning disable CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable.
    T _item;
    #pragma warning restore CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable.
}

这真的是我最好的选择吗?

注意:我见过this related question,但是它没有回答我的问题,因为答案表明要使用where T : struct,如上所述,它不能解决问题。


[收到正确答案后编辑]

因此,对于一般类型,无法使用nullable来表示“如果T是可为null的类型,则它可以为null”的情况。

但是,Resharper确实可以做到这一点,所以我将两者结合使用。

使用Resharper支持以及可为空时,该类如下所示:

public sealed class Test<T>
{
    public void SetItem([CanBeNull] T item)
    {
        _item = item;
    }

    [CanBeNull] public T GetItem()
    {
        return _item;
    }

    [CanBeNull] T _item = default!;
}

现在,当我编写这样的代码时:

var test = new Test<string>();
Console.WriteLine(test.GetItem().Length); // Warning: Possible null reference exception.

Resharper向我警告test.GetItem()可能返回null-即使编译器没有返回。

(可惜C#编译器无法执行此操作,但至少我们有Resharper!)


[经过进一步考虑后编辑]

如果我只使用这样的类:

public sealed class Test<T>
{
    public void SetItem(T item)
    {
        _item = item;
    }

    public T GetItem()
    {
        return _item;
    }

    T _item = default!;
}

然后,实例化代码有责任在需要时提供正确的T可以为null的类型,例如:

var test = new Test<string?>();

现在,如果您尝试取消引用可能为空的引用,则编译器本身会警告您:

var test = new Test<string?>(); // Note "string?"
Console.WriteLine(test.GetItem().Length); // Warning about test.GetItem() possibly null.

这绝对比我在第二次编辑中使用Resharper注释更好,因为这实际上是错误的,因为这意味着您不能说值不为空。


[希望最终澄清编辑内容]

除非明确设置了实际的类,否则不允许访问_item,因此更像这样:

public sealed class Test<T>
{
    public void SetItem(T item)
    {
        _wasSet = true;
        _item = item;
    }

    public T GetItem()
    {
        if (!_wasSet)
            throw new InvalidOperationException("No item set.");

        return _item;
    }

    T _item = default!;

    bool _wasSet;
}

由于使用实类的方式,在实例化对象时无法设置要设置的项目,因此无法在构造函数中设置默认值。

(真实类是某人为提供队列而编写的,您可以在其中访问队列中的第一项和第二项而不会使其出队,而_item实际上是一个单独的值,用于队列,剩余的队列存储在Queue<T>中。实际上,这不是我要做的方式,但这就是我正在使用的方法...)

1 个答案:

答案 0 :(得分:2)

作为一种可能的选择,您可以使用默认值和可为null的运算符的组合,例如

private T _item = default!;