c# 表示通用成员的可空性

问题描述 投票:3回答:1

在这个人为的C# 8例子中,我有三个设计目标。

#nullable enable
class Fred<T>
{
    T Value;  // If T is a nullable type, Value can be null.
    public Fred()                 { }
    public void SetValue(T value) { Value = value; }
    public T GetValue()           { return Value; }
    public string Describe()      { return Value.ToString() ?? "oops"; }
}
class George
{
    George()
    {
        Fred<George> fredGeorge = new Fred<George>();
        George g = fredGeorge.GetValue();
        Fred<float> fredFloat = new Fred<float>();
        float f = fredFloat.GetValue();
    }
}

我有三个设计目标

  1. 如果我为Fred写的方法盲目地假定 "Value "永远不会是null,编译器应该警告我。
  2. 如果我在Fred之外(比如在George中)写了任何盲目假设'GetValue'永远不会返回null的方法,编译器应该警告我。
  3. 没有编译器警告(如果我写的代码没有盲目假设值不会是null)。

所以第一个版本还不错,我在Fred中得到一个警告,Describe()可能会取消引用一个null引用(满足目标#1),但我也得到一个警告,在Fred的构造函数中Value未被初始化(违反目标#3),而George在编译时没有任何警告(违反目标#2)。 如果我做了这样的改变,George还是会在编译时没有任何警告(违反目标#2)。

public Fred() { Value = default; }

George仍然是无警告的编译(违反目标#2),并且我在Fred的构造函数中得到一个不同的警告 "Possible null reference assignment"(违反目标#3)。

我可以通过使用null-forgiving操作符来消除可能的空引用赋值。

public Fred() { Value = default!; }

现在Fred只有正确的警告(Describe()中可能的减引用),但George也没有警告地编译了(违反目标#2)。

如果我试着指出 "Value "可以是空的。

T? Value;

我得到一个编译器错误,"一个可空类型参数必须是已知的值类型或非空引用类型",所以这不好。

如果我再回到 "Value",我就会得到 "一个可空的类型参数必须是已知的值类型或非可空的引用类型 "的编译器错误,这样就不好了。

T Value;

并添加 "MaybeNull "属性。

[return: MaybeNull]
public T GetValue() { return Value; }

我收到两个警告:一个是在Fred.Describe()中警告可能的null减引(正确),另一个是在George中警告fredGeorge.GetValue()可能是null(正确)。 没有警告fredFloat.GetValue()是空的(正确)。

所以在添加了期望空引用的代码后,我最终得到的是这样的结果。

class Fred<T>
{
    T Value;

    public Fred()
    {
        Value = default!;
    }

    public void SetValue(T value)
    {
        Value = value;
    }

    [return: MaybeNull]
    public T GetValue()
    {
        return Value;
    }

    public string Describe()
    {
        return (Value == null) ? "null" : (Value.ToString() ?? "ToString is null");
    }
}

class George
{
    George()
    {
        Fred<George> fredGeorge = new Fred<George>();
        George? g = fredGeorge.GetValue();
        Fred<float> fredFloat = new Fred<float>();
        float f = fredFloat.GetValue();
    }
}

这是这个功能的正确模式吗?

c# nullable c#-8.0 nullable-reference-types
1个回答
1
投票

System.Diagnostics.CodeAnalysis 有一种属性 AllowNullAttribute 其中规定 null 被允许作为输入,即使相应的类型不允许。我将在你的最终示例中使用这个属性。

  • 装饰字段 Value. 这将使我们能够删除 null-forgiving operator 在任务中 Value = default. 编译器不会警告我们 Possible null reference assignment因为现在它知道 null 值可以被分配到属性 Value.
  • 裝飾論點 T value 的方法 SetValue. 它将允许通过 null 的值到该方法 SetValue 而不引起编译器警告 Cannot convert null literal to non-nullable reference type. (目前如果我们通过 null 的值到该方法 SetValue 我们会得到这个警告)

这里是最后的示例,并提出了修改建议。

class Fred<T>
{
    // AllowNull attribute says that a null value
    // can be assigned to the field Value.
    [AllowNull]
    private T Value;

    public Fred()
    {
        // Now we can delete null-forgiving operator, because compiler knows
        // that null value can be assigned to the field Value.
        Value = default;
    }

    // AllowNull attribute says that a null value
    // can be passed to the method SetValue.
    public void SetValue([AllowNull] T value)
    {
        Value = value;
    }

    [return: MaybeNull]
    public T GetValue()
    {
        return Value;
    }

    public string Describe()
    {
        return (Value == null) ? "null" : (Value.ToString() ?? "ToString is null");
    }
}

class George
{
    George()
    {
        Fred<George> fredGeorge = new Fred<George>();
        George? g = fredGeorge.GetValue();

        // Compiler does not warn us "Cannot convert null literal to
        // non-nullable reference type" because it knows that a null
        // value can be passed to the method SetValue.
        fredGeorge.SetValue(null);

        Fred<float> fredFloat = new Fred<float>();
        float f = fredFloat.GetValue();
    }
}

如果我们用一个常规属性来代替字段的话 Value 用一对方法 GetValueSetValue 那么我们就可以更清晰地重写最终的样本。

class Fred<T>
{
    // Here we tell that:
    // 1) a null value can be assigned;
    // 2) a null value can be returned.
    [AllowNull, MaybeNull]
    public T Value { get; set; }

    public Fred()
    {
        // Compiler does not warn us "Possible null reference assignment".
        // It knows that a null value can be assigned. It is correct.
        // We can delete null-forgiving operator.
        Value = default;
    }

    public string Describe()
    {
        // If we delete null checking, then we get a warning "Dereference of
        // a possibly null reference". It is correct. Compiler helps us to avoid
        // NullReferenceException.
        return (Value == null) ? "null" : (Value.ToString() ?? "ToString is null");
    }
}

class George
{
    George()
    {
        Fred<George> fredGeorge = new Fred<George>();

        // Compiler warns us "Converting null literal or possible null
        // value to non-nullable type". It is correct.
        // We should use nullable reference type George?.
        George g = fredGeorge.Value;

        // Compiler does not warn us "Cannot convert null literal to
        // non-nullable reference type". It knows that a null value
        // can be passed to the method SetValue. It is correct.
        fredGeorge.Value = null;

        Fred<float> fredFloat = new Fred<float>();
        float f = fredFloat.Value;
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.