Что предпочтительнее: Nullable<t>.HasValue или Nullable<t> ! = Null?

Я всегда использовал Nullable<>.HasValue, потому что мне понравилась семантика. Тем не менее, недавно я работал над кем-то еще существующей кодовой базой, где они использовали исключительно Nullable<> != null.

Есть ли причина использовать один над другим или это просто предпочтение?

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

против

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

Ответ 1

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

Ответ 2

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

Ответ 3

Я сделал несколько исследований по этому вопросу, используя разные методы для присвоения значений обнуляемому int. Вот что случилось, когда я делал разные вещи. Должен уточнить, что происходит. Имейте в виду: Nullable<something> или сокращенное something? - это структура, для которой компилятор, похоже, много работает, чтобы позволить нам использовать с нулевым, как если бы это был класс.
Как вы увидите ниже, SomeNullable == null и SomeNullable.HasValue всегда возвращают ожидаемое значение true или false. Хотя это не показано ниже, SomeNullable == 3 также является допустимым (предполагается, что SomeNullable является int?).
Пока SomeNullable.Value получает ошибку времени выполнения, если мы назначили null - 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.")

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

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); с нулевым значением, переданным конструктору, вызвала бы ошибку времени COMPILE, так как объект с нулевым значением VALUE НЕ имеет значения NULL. Только объект-оболочка может равняться нулю.

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

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

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

val.Value = 3;

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

val = 3;

Не нужно беспокоиться о том, что важно, что вы думаете, если оно работает правильно?:)

Ответ 4

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

Ответ 5

Если вы используете linq и хотите, чтобы ваш код был коротким, я рекомендую всегда использовать !=null

И вот почему:

Предположим, что у нас есть некоторый класс Foo с SomeDouble двойной переменной SomeDouble

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

Если где-то в нашем коде мы хотим получить все Foo с ненулевыми значениями SomeDouble из коллекции Foo (предполагая, что некоторые foos в коллекции также могут быть нулевыми), мы получим по крайней мере три способа написать нашу функцию (если мы используйте С# 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
}

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

Ответ 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; // or t.HasValue?
}

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