В чем разница между Nullable ‹T› .HasValue и Nullable ‹T›! = Null?

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

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

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

vs.

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

person lc.    schedule 24.03.2009    source источник
comment
Я задал аналогичный вопрос ... получил хорошие ответы: stackoverflow.com/questions/633286/   -  person nailitdown    schedule 24.03.2009
comment
Лично я бы использовал HasValue, поскольку считаю, что слова более читабельны, чем символы. Однако все зависит от вас и от того, что подходит вашему стилю.   -  person Jake Petroules    schedule 31.05.2010
comment
.HasValue имеет больше смысла, поскольку обозначает, что тип имеет тип T?, а не тип, который может допускать значение NULL, например строки.   -  person user3791372    schedule 11.06.2016


Ответы (6)


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

person Rex M    schedule 24.03.2009
comment
Я бы добавил к этому, что более согласовано / соответствует существующему стилю кодирования. - person Josh Lee; 24.03.2009
comment
Вот это да. Я ненавижу этот синтаксический сахар. int? x = null дает мне иллюзию, что экземпляр, допускающий значение NULL, является ссылочным типом. Но правда в том, что Nullable ‹T› - это тип значения. Похоже, я бы сделал исключение NullReferenceException: int? x = null; Use(x.HasValue). - person KFL; 04.11.2014
comment
@KFL Если вас беспокоит синтаксический сахар, просто используйте Nullable<int> вместо int?. - person Cole Johnson; 21.03.2015
comment
На ранних этапах создания приложения вы можете подумать, что для хранения некоторых данных достаточно использовать тип значения, допускающий значение NULL, только чтобы через некоторое время понять, что вам нужен подходящий класс для ваших целей. Написание исходного кода для сравнения с нулевым значением имеет то преимущество, что вам не нужно искать / заменять каждый вызов HasValue () сравнением с нулевым значением. - person Anders; 16.12.2015
comment
Довольно глупо жаловаться на возможность установить для Nullable значение null или сравнить его с null, учитывая, что это называется Nullable. Проблема в том, что люди путают ссылочный тип с может быть нулевым, но это концептуальная путаница. В будущем C # будут ссылочные типы, не допускающие значения NULL. - person Jim Balter; 08.11.2016
comment
@KFL Иногда я использую var z = x ?? y для типов, допускающих значение NULL. Вы все еще предпочли бы var z = x.HasValue ? x : y в этом случае? Кажется странным. - person Nullius; 18.01.2017
comment
@Nullius из примера KFL, это довольно странная часть = null. Если бы null был экземпляром Nullable, так что можно было бы ожидать null.HasValue() == false, тогда было бы менее странно, что x.HasValue() работает. - person jpaugh; 18.05.2017

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

person cbp    schedule 14.08.2009
comment
Что, конечно, вводит в заблуждение, поскольку Nullable<> не ссылочный тип. - person Luaan; 05.08.2015
comment
Да, но этот факт обычно не имеет большого значения в момент, когда вы выполняете нулевую проверку. - person cbp; 07.08.2015
comment
Это только вводит в заблуждение концептуально запутанных людей. Использование согласованного синтаксиса для двух разных типов не означает, что они принадлежат к одному и тому же типу. В C # есть ссылочные типы, допускающие значение NULL (все ссылочные типы в настоящее время допускают значение NULL, но это изменится в будущем) и типы значений, допускающие значение NULL. Имеет смысл использовать согласованный синтаксис для всех типов, допускающих значение NULL. Это никоим образом не означает, что типы значений, допускающие значение NULL, являются ссылочными типами или что ссылочные типы, допускающие значение NULL, являются типами значений. - person Jim Balter; 11.10.2015
comment
Я предпочитаю HasValue, потому что он читается лучше, чем != null - person Konrad; 21.09.2018
comment
Согласованность кодирования более читабельна, если вы не смешиваете разные стили написания одного и того же кода. Поскольку не во всех местах есть свойство .HasValue, для повышения согласованности необходимо использовать! = Null. На мой взгляд. - person ColacX; 24.09.2019
comment
Определенно проголосуйте за это предпочтение, если ничто иное не упрощает кодирование изменений, поскольку переход от ссылочного типа к типу, допускающему значение NULL, не требует изменений кода где-либо еще, где использование .HasValue становится неправильным синтаксисом, как только он больше не будет явно Nullable, что может быть не обычным случаем, но если вы когда-либо писали структуру для Tuple, а затем превращали ее в класс, вы были в той области, к которой это применимо, и с появлением NullableRefs это станет гораздо более вероятным происходить. - person Captain Prinny; 13.02.2020

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

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

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

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

val.Value = 3;

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

val = 3;

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

person Perrin Larson    schedule 11.04.2014
comment
Имейте в виду: Nullable ‹something› или что-то сокращенное? это класс. Это не правильно! Nullable ‹T› - это структура. Он перегружает оператор Equals и ==, чтобы вернуть true по сравнению с null. Компилятор не делает ничего особенного для этого сравнения. - person andrewjsaid; 09.06.2015
comment
@andrewjs - Вы правы, что это структура (а не класс), но ошибаетесь, что она перегружает оператор ==. Если вы наберете Nullable<X> в VisualStudio 2013 и F12, вы увидите, что он только перегружает преобразование в X и метод Equals(object other). Однако я думаю, что оператор == использует этот метод по умолчанию, поэтому эффект тот же. На самом деле я уже давно хотел обновить этот ответ по этому факту, но я ленив и / или занят. Этот комментарий пока нужно сделать :) - person Perrin Larson; 11.06.2015
comment
Я быстро проверил ildasm, и вы правы насчет того, что компилятор творит чудеса; сравнение Nullable ‹T› объекта с null фактически переводится в вызов HasValue. Интересно! - person andrewjsaid; 12.06.2015
comment
@andrewjs На самом деле компилятор проделывает огромную работу по оптимизации значений NULL. Например, если вы присваиваете значение типу, допускающему значение NULL, оно вообще не будет иметь значение NULL (например, int? val = 42; val.GetType() == typeof(int)). Таким образом, структура, которая может быть равна нулю, не только допускает значение NULL, но также часто не допускает значения NULL! : D Точно так же, когда вы помещаете в коробку значение, допускающее значение NULL, вы упаковываете int, а не int? - и когда int? не имеет значения, вы получаете null вместо упакованного значения, допускающего значение NULL. Это в основном означает, что при правильном использовании nullable редко возникают какие-либо накладные расходы :) - person Luaan; 05.08.2015
comment
Без помощи компилятора попытка вызвать HasValue для объекта Nullable, переменной которого присвоено значение null, приведет к ошибке времени выполнения с нулевой ссылкой. -- Это нонсенс. Nullable ‹int› - это тип значения, а не ссылочный тип, поэтому он не может получить ошибку нулевой ссылки. Ряд комментариев на этой странице отражают эту путаницу, приравнивая обнуляемый тип к ссылочному типу. - person Jim Balter; 11.10.2015
comment
@Luaan это вообще не будет иметь значение NULL - это и остальная часть вашего комментария - ерунда. Типы допускают значение NULL; ценности нет. Тип T, допускающий значение NULL? может быть либо T, либо нулевым. Конечно, когда это T, GetType () возвращает typeof (T), потому что GetType возвращает тип runtime. накладные расходы бывают редко - конечно, накладные расходы есть; дополнительное хранилище необходимо для флага, который сообщает, является ли значение нулевым, и для проверки этого флага требуются проверки во время выполнения. - person Jim Balter; 08.11.2016
comment
@JimBalter Хорошо, скажите мне, как тип значения может иметь тип времени выполнения, отличный от типа времени компиляции, без обмана времени выполнения / компилятора. Можете ли вы создать свой собственный тип, допускающий значение NULL, который будет вести себя так же? Можете ли вы перегрузить оператор бокса для типа значения? Как вы перегружаете GetType? Какие накладные расходы вы получаете от Nullable.GetValueOrDefault? Какие накладные расходы вы получаете от упаковки типа, допускающего значение NULL, поверх упаковки любого другого типа значения? Единственный случай, когда вы получаете накладные расходы, - это когда вы не боксируете (флаг) и проверяете значение null (это то, что вы хотели!). - person Luaan; 08.11.2016
comment
@Luaan Переменная с int? типом времени компиляции имеет тип времени выполнения либо int, либо Null. Это не обман, это реализация абстракции. Переменная с Base типом времени компиляции может иметь Derived в качестве типа времени выполнения - никакого мошенничества. Это очень просто. - person Jim Balter; 10.11.2016
comment
@JimBalter Правда? Это очень интересно. Итак, что профилировщик памяти сообщает вам о поле класса, допускающем значение NULL? Как объявить тип значения, который наследуется от другого типа значения в C #? Как вы объявляете свой собственный тип, допускающий значение NULL, который ведет себя так же, как тип .NET, допускающий значение NULL? С каких это пор Null тип в .NET? Можете ли вы указать на ту часть спецификации CLR / C #, где это сказано? Nullables хорошо определены в спецификации CLR, их поведение не является реализацией абстракции - это контракт. Но если лучшее, что вы можете сделать, это атаки ad hominem, наслаждайтесь. - person Luaan; 10.11.2016

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

person Carter Medlin    schedule 23.04.2010
comment
Я бы предпочел HasValue из-за удобочитаемости. IsNot Nothing - действительно некрасивое выражение (из-за двойного отрицания). - person Stefan Steinegger; 08.07.2013
comment
@steffan IsNot Ничто не является двойным отрицанием. Ничто не является отрицательным, это дискретная величина, даже вне области программирования. В этом количестве нет ничего. грамматически это то же самое, что сказать: «Эта величина не равна нулю». и ни то, ни другое не является двойным отрицанием. - person jmbpiano; 06.03.2015
comment
Это не значит, что я не хочу не соглашаться с отсутствием истины здесь, но давай сейчас. IsNot Ничего явно отрицательного. Почему бы не написать что-нибудь позитивное и ясное, например HasValue? Это не тест по грамматике, это кодирование, где главная цель - ясность. - person Randy Gamage; 13.08.2015
comment
jmbpiano: Я согласен, что это не двойное отрицание, а единичное отрицание, и это почти так же уродливо и не так ясно, как простое положительное выражение. - person Kaveh Hadjari; 05.01.2016

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

И вот почему:

Представьте, что у нас есть класс Foo с переменной SomeDouble, допускающей обнуляемое значение double

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

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

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

person yan yankelevich    schedule 28.08.2017
comment
Да, foo?.SomeDouble.HasValue - это ошибка времени компиляции (а не ошибка в моей терминологии) в этом контексте, потому что ее тип - bool?, а не просто bool. (Метод .Where хочет Func<Foo, bool>.) Конечно, разрешено делать (foo?.SomeDouble).HasValue, поскольку он имеет тип bool. Это то, во что ваша первая строка транслируется внутри компилятором C # (по крайней мере, формально). - person Jeppe Stig Nielsen; 07.07.2018

Второй метод будет во много раз эффективнее (в основном из-за встраивания и бокса компиляторов, но все же числа очень выразительны):

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

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

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

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

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

person Roman Pokrovskij    schedule 16.05.2017
comment
Ваши CheckObjectImpl помещают значения, допускающие значение NULL, в object, тогда как CheckNullableImpl не использует боксы. Таким образом, сравнение неуместное. Мало того, что это не плата за проезд, это также бесполезно, потому что, как отмечено в принятом ответе, компилятор переписывает != на HasValue в любом случае. - person GSerg; 16.05.2017
comment
GSerg ваш комментарий не в курсе. Я могу только повторить, что это конкретная ситуация, но это обычное дело в некоторых областях, например. в сериализаторах. Кроме того, чтобы ответить, я продемонстрирую, как все может стать более сложным, если вы проигнорируете структурную природу Nullable ‹T›. Читатели решат, бесполезно это или нет, мне это интересно, и я разделяю это. - person Roman Pokrovskij; 16.05.2017
comment
Читатели не игнорируют структурный характер Nullable<T>, вы делаете (помещая его в object). Когда вы применяете != null с допускающим значение NULL слева, упаковка не происходит, поскольку поддержка != для значений NULL работает на уровне компилятора. Другое дело, когда вы скрываете значение NULL от компилятора, сначала помещая его в object. Ни CheckObjectImpl(object o), ни ваш тест в принципе не имеют смысла. - person GSerg; 16.05.2017
comment
Моя проблема в том, что я забочусь о качестве контента на этом сайте. То, что вы опубликовали, либо вводит в заблуждение, либо неверно. Если вы пытались ответить на вопрос OP, то ваш ответ категорически неверен, что легко доказать, заменив call на CheckObjectImpl его body внутри CheckObject. Однако ваши последние комментарии показывают, что вы на самом деле имели в виду совершенно другой вопрос, когда решили ответить на этот 8-летний вопрос, что делает ваш ответ вводящим в заблуждение в контексте исходного вопроса. Это было не то, о чем спрашивал ОП. - person GSerg; 16.05.2017
comment
Да, я понимаю, что мой ответ более общий и в то же время более конкретный, чем вопрос и показывает ситуацию под другим углом. И я согласен, что ваш комментарий имеет смысл. За исключением двух моментов: а) ситуация реальная б) вы хотите не только прокомментировать, но и удалить мой ответ. Во-вторых, я могу объяснить только с помощью жевательной резинки, которую вы ожидаете получить за это. Тебе должно быть стыдно. - person Roman Pokrovskij; 16.05.2017
comment
Я твердо верю, что этот веб-сайт столь же успешен, как и именно потому, что он построен по принципу узких конкретных вопросов с узкими конкретными ответами, поэтому я считаю правильным проголосовать за удаление этого содержания из этого вопроса . Очень приветствуется, если у вас есть контент, которым вы хотите поделиться, просто сделайте это по более подходящему вопросу. Если его нет, совершенно нормально создать вопрос и ответить на него самостоятельно. - person GSerg; 16.05.2017
comment
Поставьте себя на место следующего парня, который погуглиет what is faster != or HasValue. Он отвечает на этот вопрос, просматривает ваш ответ, оценивает ваш тест и говорит: «Боже, я никогда не буду использовать !=, потому что он явно намного медленнее!» Это очень неправильный вывод, который он потом будет распространять. Вот почему я считаю ваш ответ вредным - он отвечает на неправильный вопрос и, таким образом, приводит к неверному выводу у ничего не подозревающего читателя. Подумайте, что произойдет, если вы измените свой CheckNullableImpl на также быть return o != null;. Вы получите тот же результат теста. - person GSerg; 16.05.2017
comment
Вы спорите не с моим ответом. Отвечаю для конкретной конкретной ситуации. Не скрываю, что это конкретная ситуация, опишите ситуацию с текстом (конвейеры сериализаторов), покажите код и интересные цифры. Люди не глупы, могут это понять, начать думать и наслаждаться своими идеями. Радостное мышление, а не охота за значками. - person Roman Pokrovskij; 16.05.2017
comment
Я спорю с вашим ответом. Ваш ответ обманчиво выглядит так, будто он показывает разницу между != и HasValue, хотя на самом деле он показывает разницу между object o и T? o. Если вы сделаете то, что я предложил, то есть перепишите CheckNullableImpl как public static bool CheckNullableImpl<T>(T? o) where T: struct { return o != null; }, вы получите тест, который ясно показывает, что != намного медленнее, чем !=. Это должно привести вас к выводу, что проблема, которую описывает ваш ответ, вовсе не касается != против HasValue. - person GSerg; 16.05.2017
comment
Осознав это, вы можете перефразировать свой вопрос, чтобы объяснить, что на самом деле разница между object o и T? o, независимо от того, используется ли != или HasValue, поэтому следует избегать упаковки и распаковки значений, допускающих значение NULL, при написании пользовательских сериализов. Тогда это был бы абсолютно правильный и ни в коем случае не вводящий в заблуждение ответ, но после того, как вы его перечитали, вы бы удалили его, потому что он не имел бы никакого отношения к тому, что спросил OP. - person GSerg; 16.05.2017