c# Указывает на возможность нулевого значения generi c члена - PullRequest
4 голосов
/ 16 июня 2020

В этом надуманном примере 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. Компилятор должен предупредить меня, если я напишу какие-либо методы для Фреда, которые вслепую Предположим, что 'Value' никогда не будет нулевым
  2. Компилятор должен предупредить меня, если я напишу какие-либо методы за пределами Фреда (скажем, в George), которые слепо предполагают, что 'GetValue' никогда не вернет null. 1010 * Нет предупреждений компилятора (если я написал код, который слепо не предполагает, что значения не будут нулевыми)

Итак, эта первая версия неплохая, я получаю предупреждение от Фреда, которое описывает () может разыменовывать нулевую ссылку (удовлетворяет цели №1), но я также получаю предупреждение о том, что Value не инициализировано в конструкторе Фреда (нарушает цель №3) и Джордж компилируется без каких-либо предупреждений (нарушает цель №2). Если я сделаю это изменение:

public Fred() { Value = default; }

Джордж по-прежнему компилируется без предупреждений (нарушает цель № 2), и я получаю другое предупреждение в конструкторе Фреда о «Возможном присвоении нулевой ссылки» (нарушает цель № 3).

Я могу избавиться от возможного присвоения нулевой ссылки с помощью оператора, допускающего нулевые значения:

public Fred() { Value = default!; }

И теперь у Фреда есть только правильное предупреждение (возможное разыменование в Describe ()), но Джордж также компилируется без предупреждения (нарушает цель № 2).

Если я попытаюсь указать, что 'Value' может быть нулевым:

T? Value;

я получаю ошибку компилятора, которая "допускает значение NULL Параметр type должен быть известен как тип значения или ссылочный тип, не допускающий значения NULL, так что это бесполезно.

Если I go вернитесь к

T Value;

и добавьте «MaybeNull» attribute:

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

Я получаю два предупреждения - одно в предупреждении Fred.Describe () о возможном разыменовании нуля (правильно) и одно в предупреждении Джорджа о том, что fredGeorge.GetValue () может быть нулевым (правильно). Нет предупреждения о том, что fredFloat.GetValue () имеет значение null (правильно).

Итак, после добавления кода для ожидания нулевых ссылок я получаю следующее:

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();
    }
}

Это правильный шаблон для этой функции?

1 Ответ

1 голос
/ 17 июня 2020

В System.Diagnostics.CodeAnalysis есть атрибут AllowNullAttribute, который указывает, что null разрешен как вход, даже если соответствующий тип его запрещает. Я бы использовал этот атрибут в вашем последнем примере, чтобы:

  • Украсить поле Value. Это позволит нам удалить null-forgiving operator в назначении Value = default. Компилятор не будет предупреждать нас о Possible null reference assignment, потому что теперь он знает, что null значение может быть присвоено свойству Value.
  • Аргумент Decorate 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 с парой методов GetValue и SetValue, то мы можем переписать окончательный образец более четко:

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;
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...