In .NET, there are multiple ways to check if a value is null, but they are not all equivalent. Let's look at the different ways to check if an object is null:
C#
object obj = ...;
obj == null;
object.Equals(obj, null);
object.ReferenceEquals(obj, null);
obj is null; // Equivalent to ReferenceEquals(obj, null)
(object)obj == null; // Equivalent to ReferenceEquals(obj, null)
obj is not object; // Equivalent to !ReferenceEquals(obj, null)
obj is not {}; // Equivalent to !ReferenceEquals(obj, null)
obj.Equals(null); // doesn't work as it would throw a NullReferenceException
There are three main ways to perform a null check:
Object.ReferenceEquals(obj, null)
ReferenceEquals returns true when the object instances are the same instance. In this case, it returns true when obj is null.
object.Equals(obj, null)
Equals behaves like ReferenceEquals when one argument is null. However, its logic is more complex and it is slightly slower, as the method call is not always inlined.
obj == null
The == operator behaves like ReferenceEquals unless the class defines a custom equality operator. When a custom operator is defined, the compiler uses it instead of the default one, so the comparison logic may not perform an actual null check. You can force the compiler to use the == operator defined on object by explicitly casting the value to object.
C#
var obj = new Sample();
obj == null; // return true as it calls Sample.op_Equality which always return true
(object)obj == null; // return false as it calls object.op_Equality (ReferenceEquals)
obj is null; // return false as it calls object.op_Equality (ReferenceEquals)
class Sample
{
public static bool operator ==(Sample? a, Sample? b) => true;
public static bool operator !=(Sample? a, Sample? b) => false;
}
Note that is null does not work with structs, except for Nullable<T>:
C#
MyStruct a = default;
_ = a is null; // Compilation error
_= a == null; // Valid only if MyStruct implements a compatible == operator
Span<char> span = default;
_ = span is null; // Compilation error
_ = span == null; // ok as Span has a compatible == operator
int? b = null;
_ = b is null; // Ok, rewritten as !b.HasValue
Although is not null, is object, and is {} all perform the same null check, they differ in how pattern variables can be captured:
C#
if (obj is not null x) // Not valid
if (obj is object x) // Ok, x is of type object
if (obj is {} x) // Ok, x is of the same type as obj
#Additional resources
Do you have a question or a suggestion about this post? Contact me!