В чем разница между myObject == null и myObject is null?

Я обнаружил, что есть много людей, которые думают, что является, а не является(представленным в C# 9) для проверки нулей просто синтаксическим сахаром для оператора равенства или неравенства ( == и !=).

Примечание

До C# 9 не было никакой поддержки «is not», для достижения того же результата вам нужно будет сделать «!(myObject is null)», немного более подробный и сложнее читать

Но на самом деле это не только синтаксический сахар, и мы можем использовать IL и декомпилированный код C#, чтобы взглянуть на то, что происходит за кулисами.

Будет использоваться sharplab.io.

SharpLab.io — это невероятный инструмент, потому что он предоставляет нам простой способ декомпилировать и проверять, как компилятор преобразует наш код, в режиме онлайн без необходимости использования каких-либо дополнительных инструментов или IDE, таких как Visual Studio.

Если вам нужен более полный инструмент для декомпиляции IL, чтобы упростить навигацию по сложным проектам, я рекомендую расширение Visual Studio ILSpy.

Итак, без дальнейших церемоний, давайте рассмотрим очень простой случай, «условие if»дляпроверки того, является ли экземпляр объекта нулевым или нет.

Но подождите, разве я не говорил, что is не просто синтаксический сахар? IL выглядит почти так же, за исключением инструкции dup , которая не имеет существенного значения для этого примера. Здесь инструкция dup дублирует верхний элемент СТЕКА из соображений производительности (дополнительную информацию можно найти здесь).

Так в чем же на самом деле разница между is и ==?

Что ж, как вы знаете (или не знаете), в C# мы можем переопределить поведение оператора по умолчанию.

Например, в этом тестовом классе выше я могу сделать что-то вроде этого.

public class Test {
    
    public static bool operator ==(Test x, Test y) => false;
    
    public static bool operator !=(Test x, Test y) => true;
}

При этом я фактически переопределяю поведение операторов равенства и неравенства по умолчанию.

В этом примере, полностью искажая рабочий поток этих операторов, говоря, что == всегда будет возвращать false, а != всегда будет вернуть true.

Итак, теперь, когда я перезаписал поведение этих операторов, как выглядит IL?

Итак, теперь мы можем начать видеть разницу.

На изображении выше вы можете видеть, что оператор равенства на самом деле вызывает перезаписанный оператор, где остается «есть» одинаковый.

Давайте посмотрим на скомпилированный код C#, чтобы лучше понять, почему это происходит.

На этом этапе становится совершенно очевидно, почему оператор равенства вызывает оператор перезаписи, а оператор «есть» — нет.

Вызов равенства должен гарантировать, что сигнатура вашего класса соблюдается, это причина приведения к классу Test и вызывает наш метод равенства, где оператор «is» приводится к простой объект, игнорирующий любую пользовательскую реализацию оператора равенства.

Примечание.

На самом деле приведение к объекту для выполнения проверки на значение null также является подходом для предлагаемой проверки значения параметра на значение null в C# 11, о котором я не буду говорить в этом посте.

Таким образом, результат запуска будет

Поскольку == всегда возвращает false, «if (test1 == null)» никогда не будет истинным.

Ситуация становится немного более опасной, когда мы хотим гарантировать, что что-то не является нулевым, чтобы избежать печально известного NullReferenceException.

Итак, давайте теперь посмотрим на оператор неравенства.

Результат.

Я считаю, что на данный момент довольно очевидна проблема, которую это могло бы вызвать, верно?

Если бы вы проверяли, не является ли объект нулевым, чтобы безопасно получить доступ к его свойствам, у вас были бы NullReferences повсюду.

В этот момент вы можете задаться вопросом Но зачем кому-то делать что-то подобное?

Ну, мы очень привыкли включать в наш проект сторонние библиотеки из всевозможных источников, пакеты других компаний, отдельных разработчиков, какой-то случайный пакет в репозитории NuGet и т. д.

И, возможно, какой-то новый выпуск этих пакетов может привести к ошибке, подобной этой, преднамеренно или непреднамеренно, может быть очень трудно определить, где именно ваш код не гарантирует, что объект не является нулевым.

Так должен ли я пойти и везде изменить все свои нулевые проверки, чтобы следовать этому шаблону? Ответом будет старое доброе клише «Это зависит».

В вашем проекте есть одна или несколько сторонних библиотек? Они из надежного источника?

Итак, вот также несколько советов, касающихся изменения нулевых проверок с == на is, которым следует следовать, чтобы избежать больших головных болей.

  1. Всегда покрывайте логические ветки вашего кода в модульных тестах, проверка нулевой проверки на самом деле выполняет проверку правильно
  2. Избегайте добавления сторонних библиотек в производственный код из источников, которым вы не доверяете или не знаете. Обычно я рекомендую использовать библиотеки, хорошо признанные сообществом или с большим количеством участников, такие как, например, MediatR, FluentValidation, AutoMapper, NewtonSoft, Dapper и т. д. Риски будут ниже.
  3. Всегда проверяйте примечания к выпуску и изменения перед обновлением версии библиотеки с открытым исходным кодом.

Надеюсь, это поможет вам понять нюансы использования «is» или «==» в проверках на null.

Дайте мне знать, что вы думаете в комментариях.