Что предпочтительнее: Nullable.HasValue или Nullable!= null?

Я всегда использовал (a)Nullable<>.HasValue потому что мне понравилась семантика. Однако недавно я работал над чьей-то существующей базой кода, где они использовали (b) Nullable<> != null вместо этого исключительно. Есть ли причина использовать одно над другим, или это чисто предпочтение?

(a)

int? a;
if (a.HasValue)
    ...

(b)

int? b;
if (b != null)
    ...

6 ответов


компилятор заменяет нулевые сравнения вызовом HasValue, Так что нет никакой реальной разницы. Просто делайте то, что более читаемо/имеет больше смысла для вас и ваших коллег.


предпочитаю (a != null) чтобы синтаксис соответствовал ссылочным типам.


я провел некоторые исследования по этому вопросу, используя различные методы для присвоения значений nullable int. Вот что происходило, когда я делал разные вещи. Должен прояснить, что происходит. Имейте в виду: Nullable<something> или стенографию something? - это структура, для которой компилятор, кажется, делает много работы, чтобы позволить нам использовать null как если бы это был класс.
Как вы увидите ниже, SomeNullable == null и SomeNullable.HasValue всегда возвращает ожидаемое значение true или false. Хотя не продемонстрировано ниже,SomeNullable == 3 это тоже действует (предполагая, что SomeNullable является int?).
В то время как SomeNullable.Value возвращает нам ошибку времени выполнения, если мы назначили null to SomeNullable. Это фактически единственный случай, когда nullables могут вызвать у нас проблему, благодаря комбинации перегруженных операторов, перегруженных object.Equals(obj) метод, и оптимизация компилятора и обезьяний бизнес.

Вот описание некоторого кода, который я запустил, и какой вывод он произвел в метках:

int? val = null;
lbl_Val.Text = val.ToString(); //Produced an empty string.
lbl_ValVal.Text = val.Value.ToString(); //Produced a runtime error. ("Nullable object must have a value.")
lbl_ValEqNull.Text = (val == null).ToString(); //Produced "True" (without the quotes)
lbl_ValNEqNull.Text = (val != null).ToString(); //Produced "False"
lbl_ValHasVal.Text = val.HasValue.ToString(); //Produced "False"
lbl_NValHasVal.Text = (!(val.HasValue)).ToString(); //Produced "True"
lbl_ValValEqNull.Text = (val.Value == null).ToString(); //Produced a runtime error. ("Nullable object must have a value.")
lbl_ValValNEqNull.Text = (val.Value != null).ToString(); //Produced a runtime error. ("Nullable object must have a value.")

Ok, давайте попробуем следующую инициализацию метод:

int? val = new int?();
lbl_Val.Text = val.ToString(); //Produced an empty string.
lbl_ValVal.Text = val.Value.ToString(); //Produced a runtime error. ("Nullable object must have a value.")
lbl_ValEqNull.Text = (val == null).ToString(); //Produced "True" (without the quotes)
lbl_ValNEqNull.Text = (val != null).ToString(); //Produced "False"
lbl_ValHasVal.Text = val.HasValue.ToString(); //Produced "False"
lbl_NValHasVal.Text = (!(val.HasValue)).ToString(); //Produced "True"
lbl_ValValEqNull.Text = (val.Value == null).ToString(); //Produced a runtime error. ("Nullable object must have a value.")
lbl_ValValNEqNull.Text = (val.Value != null).ToString(); //Produced a runtime error. ("Nullable object must have a value.")

все так же, как и раньше. Имейте в виду, что инициализация с int? val = new int?(null);, С null, переданным конструктору, вызвал бы ошибку времени компиляции, так как значение объекта nullable не является nullable. Только сам объект-оболочка может быть равен null.

аналогично, мы получим ошибку времени компиляции от:

int? val = new int?();
val.Value = null;

не говоря уже о том, что val.Value это свойство только для чтения в любом случае, то есть мы даже не можем использовать что-то например:

val.Value = 3;

но опять же, полиморфные перегруженные операторы неявного преобразования позволяют нам делать:

val = 3;

не нужно беспокоиться о polysomthing whatchamacallits, хотя, если он работает правильно? :)


In VB.Net - ... Не используйте "IsNot ничего", когда вы можете использовать ".Свойство hasvalue". Я просто решил, что "операция может дестабилизировать ошибку среднего доверия во время выполнения", заменив "IsNot Nothing "на".HasValue " в одном месте. Я действительно не понимаю, почему, но что-то происходит по-другому в компиляторе. Я бы предположил, что "!= null " в C# может иметь ту же проблему.


если вы используете linq и хотите сохранить свой код коротким, Я рекомендую всегда использовать !=null

и вот почему:

давайте представим, что у нас есть какой-то класс Foo С обнуляемого типа двойной переменная SomeDouble

public class Foo
{
    public double? SomeDouble;
    //some other properties
}   

если где-то в нашем коде мы хотим получить все Foo с ненулевыми SomeDouble значениями из коллекции Foo (предполагая, что некоторые foos в коллекции также могут быть null), мы получаем по крайней мере три способа записи нашей функции (если мы использовать C# 6) :

public IEnumerable<Foo> GetNonNullFoosWithSomeDoubleValues(IEnumerable<Foo> foos)
{
     return foos.Where(foo => foo?.SomeDouble != null);
     return foos.Where(foo=>foo?.SomeDouble.HasValue); // compile time error
     return foos.Where(foo=>foo?.SomeDouble.HasValue == true); 
     return foos.Where(foo=>foo != null && foo.SomeDouble.HasValue); //if we don't use C#6
}

и в такой ситуации я рекомендую всегда идти за короткий


общий ответ и эмпирическое правило: если у вас есть опция (например, написание пользовательских сериализаторов) для обработки Nullable в другом конвейере, чем object - и используйте их конкретные свойства-сделайте это и используйте Nullable конкретные свойства. Итак, с точки зрения последовательного мышления HasValue должно быть предпочтительным. Последовательное мышление может помочь вам написать лучший код, не тратя слишком много времени на детали. Е. Г. есть второй способ будет во много раз эффективнее (в основном из-за компиляторов инлайнинг и бокс, но все же цифры очень выразительны):

public static bool CheckObjectImpl(object o)
{
    return o != null;
}

public static bool CheckNullableImpl<T>(T? o) where T: struct
{
    return o.HasValue;
}

тест:

BenchmarkDotNet=v0.10.5, OS=Windows 10.0.14393
Processor=Intel Core i5-2500K CPU 3.30GHz (Sandy Bridge), ProcessorCount=4
Frequency=3233539 Hz, Resolution=309.2587 ns, Timer=TSC
  [Host] : Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1648.0
  Clr    : Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1648.0
  Core   : .NET Core 4.6.25009.03, 64bit RyuJIT


        Method |  Job | Runtime |       Mean |     Error |    StdDev |        Min |        Max |     Median | Rank |  Gen 0 | Allocated |
-------------- |----- |-------- |-----------:|----------:|----------:|-----------:|-----------:|-----------:|-----:|-------:|----------:|
   CheckObject |  Clr |     Clr | 80.6416 ns | 1.1983 ns | 1.0622 ns | 79.5528 ns | 83.0417 ns | 80.1797 ns |    3 | 0.0060 |      24 B |
 CheckNullable |  Clr |     Clr |  0.0029 ns | 0.0088 ns | 0.0082 ns |  0.0000 ns |  0.0315 ns |  0.0000 ns |    1 |      - |       0 B |
   CheckObject | Core |    Core | 77.2614 ns | 0.5703 ns | 0.4763 ns | 76.4205 ns | 77.9400 ns | 77.3586 ns |    2 | 0.0060 |      24 B |
 CheckNullable | Core |    Core |  0.0007 ns | 0.0021 ns | 0.0016 ns |  0.0000 ns |  0.0054 ns |  0.0000 ns |    1 |      - |       0 B |

эталонный код:

public class BenchmarkNullableCheck
{
    static int? x = (new Random()).Next();

    public static bool CheckObjectImpl(object o)
    {
        return o != null;
    }

    public static bool CheckNullableImpl<T>(T? o) where T: struct
    {
        return o.HasValue;
    }

    [Benchmark]
    public bool CheckObject()
    {
        return CheckObjectImpl(x);
    }

    [Benchmark]
    public bool CheckNullable()
    {
        return CheckNullableImpl(x);
    }
}

https://github.com/dotnet/BenchmarkDotNet используется

PS. Люди говорят, что Совет "предпочесть HasValue из-за последовательного мышления" не связан и бесполезен. можете ли вы предсказать результативность этого?

public static bool CheckNullableGenericImpl<T>(T? t) where T: struct
{
    return t != null;
}

PPS Люди продолжают минус, но никто не пытается предсказать производительность CheckNullableGenericImpl. И там компилятор не поможет вам заменить !=null С HasValue. HasValue следует использовать напрямую.