Ответ 1
Компилятор заменяет нулевые сравнения на вызов HasValue
, поэтому нет никакой реальной разницы. Просто сделайте то, что более читаемо/имеет больше смысла для вас и ваших коллег.
Я всегда использовал Nullable<>.HasValue
, потому что мне понравилась семантика. Тем не менее, недавно я работал над кем-то еще существующей кодовой базой, где они использовали исключительно Nullable<> != null
.
Есть ли причина использовать один над другим или это просто предпочтение?
int? a;
if (a.HasValue)
// ...
против
int? b;
if (b != null)
// ...
Компилятор заменяет нулевые сравнения на вызов HasValue
, поэтому нет никакой реальной разницы. Просто сделайте то, что более читаемо/имеет больше смысла для вас и ваших коллег.
Я предпочитаю (a != null)
, чтобы синтаксис соответствовал ссылочным типам.
Я сделал несколько исследований по этому вопросу, используя разные методы для присвоения значений обнуляемому 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;
Не нужно беспокоиться о том, что важно, что вы думаете, если оно работает правильно?:)
В VB.Net. НЕ используйте "IsNot Nothing", когда вы можете использовать ".HasValue". Я просто решил, что "Операция может дестабилизировать среду выполнения" Средняя ошибка доверия, заменив "IsNot Nothing" на ".HasValue" В одном месте. Я не понимаю, почему, но что-то происходит в компиляторе по-разному. Я бы предположил, что "!= Null" в С# может иметь такую же проблему.
Если вы используете 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
}
И в такой ситуации я рекомендую всегда идти на более короткую
Общий ответ и практическое правило: если у вас есть возможность (например, написание пользовательских сериализаторов) обрабатывать 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
следует использовать напрямую, если вы заинтересованы в производительности.