What is the difference between "x is null" and "x == null"?
In C# 7, we can use
if (x is null) return;
instead of
if (x == null) return;
Are there any advantages to using the new way (former example) over the old way?
Are the semantics any different?
Is it just a matter of taste? If not, when should I use one over the other?
Reference: What’s New in C# 7.0.
Update: The Roslyn compiler has been updated to make the behavior of the two operators the same when there is no overloaded equality operator. Please see the code in the current compiler results (M1
and M2
in the code) that shows what happens when there is no overloaded equality comparer. They both now have the better-performing ==
behavior. If there is an overloaded equality comparer, the code still differs.
See for older versions of the Roslyn compiler the below analysis.
For null
there isn't a difference with what we are used to with C# 6. However, things become interesting when you change null
to another constant.
Take this for example:
Test(1);
public void Test(object o)
{
if (o is 1) Console.WriteLine("a");
else Console.WriteLine("b");
}
The test yields a
. If you compare that to o == (object)1
what you would have written normally, it does make a hell of a difference. is
takes in consideration the type on the other side of the comparison. That is cool!
I think the == null
vs. is null
constant pattern is just something that is very familiar 'by accident', where the syntax of the is
operator and the equals operator yield the same result.
As svick commented, is null
calls System.Object::Equals(object, object)
where ==
calls ceq
.
IL for is
:
IL_0000: ldarg.1 // Load argument 1 onto the stack
IL_0001: ldnull // Push a null reference on the stack
IL_0002: call bool [mscorlib]System.Object::Equals(object, object) // Call method indicated on the stack with arguments
IL_0007: ret // Return from method, possibly with a value
IL for ==
:
IL_0000: ldarg.1 // Load argument 1 onto the stack
IL_0001: ldnull // Push a null reference on the stack
IL_0002: ceq // Push 1 (of type int32) if value1 equals value2, else push 0
IL_0004: ret // Return from method, possibly with a value
Since we are talking about null
, there is no difference since this only makes a difference on instances. This could change when you have overloaded the equality operator.
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.
There is also a difference when you try to compare a non-null variable to a null value. When using ==
, the compiler will issue a Warning, while when using is
, the compiler will issue an Error. Most likely, 99% of the time, you want the compiler to shout at you for such a basic mistake. +1 for is null
.
P.S. Tested on https://dotnetfiddle.net/ with NetCore3.1