Overloaded equals operator
There is in fact a difference in semantics between the two comparisons when you are comparing null
with a type that has overloaded the ==
operator. foo is null
will use direct reference comparison to determine the result, whereas foo == null
will of course run the overloaded ==
operator if it exists.
In this example I have introduced a "bug" in the overloaded ==
operator, causing it to always throw an exception if the second argument is null
:
void Main()
{
Foo foo = null;
if (foo is null) Console.WriteLine("foo is null"); // This condition is met
if (foo == null) Console.WriteLine("foo == null"); // This will throw an exception
}
public class Foo
{
public static bool operator ==(Foo foo1, Foo foo2)
{
if (object.Equals(foo2, null)) throw new Exception("oops");
return object.Equals(foo1, foo2);
}
// ...
}
The IL code for foo is null
uses the ceq
instruction to perform a direct reference comparison:
IL_0003: ldloc.0 // foo
IL_0004: ldnull
IL_0005: ceq
The IL code for foo == null
uses a call to the overloaded operator:
IL_0016: ldloc.0 // foo
IL_0017: ldnull
IL_0018: call UserQuery+Foo.op_Equality
So the difference is, that if you use ==
you risk running user code (which can potentially have unexpected behavior or performance problems).
Restriction on generics
Using the is null
construct restricts the type to a reference type. The compiler ensures this, which means you cannot use is null
on a value type. If you have a generic method, you will not be able to use is null
unless the generic type is constrained to be a reference type.
bool IsNull<T>(T item) => item is null; // Compile error: CS0403
bool IsNull<T>(T item) => item == null; // Works
bool IsNull<T>(T item) where T : class => item is null; // Works
Thanks to David Augusto Villa for pointing this out.