Что предпочтительнее: Nullable <T>.HasValue или Nullable <T>! = Null? - PullRequest
382 голосов
/ 24 марта 2009

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

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

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

против

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

Ответы [ 6 ]

420 голосов
/ 24 марта 2009

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

41 голосов
/ 14 августа 2009

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

20 голосов
/ 11 апреля 2014

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

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

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

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

val.Value = 3;

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

val = 3;

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

13 голосов
/ 23 апреля 2010

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

1 голос
/ 28 августа 2017

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

И вот почему:

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

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

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

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

0 голосов
/ 16 мая 2017

Общий ответ и практическое правило: если у вас есть возможность (например, написание пользовательских сериализаторов) обрабатывать 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 следует использовать напрямую, если вы заинтересованы в производительности.

...