562

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.

Pang
  • 9,564
  • 146
  • 81
  • 122
Maniero
  • 10,311
  • 6
  • 40
  • 85

3 Answers3

438

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 lot of difference. is takes into 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.

Glenn Slayden
  • 17,543
  • 3
  • 114
  • 108
Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
  • Yes, that is what i also understand of this new feature. I think it would be mainly useful in `switch` structures, that where a bit limited in c#. But as of `null` is refered, i think it would be just the same using one way or the other.. – Pikoh Nov 18 '16 at 12:07
  • I guess so. I don't know the implications on the generated CIL, but I guess it wouldn't make any difference. – Patrick Hofman Nov 18 '16 at 12:08
  • 22
    @PatrickHofman [It looks like `is` calls `object.Equals(x, null)`, while `==` compiles as `ceq`.](http://tryroslyn.azurewebsites.net/#b:master/f:%3Eilr/K4Zwlgdg5gBAygTxAFwKYFsDcAoADsAIwBswBjGUogQxBBgGEYBvbGNmAge06JgFkAjAApOBAFapSyGAA8AlDAC8APlkwwdCMCJEcASC49+AJhHjJ0+UtUylimFp04AvkA==) But the result should be the same, as you said. – svick Nov 18 '16 at 16:04
  • As written in the docs: 1) If expr and constant are integral types, the C# equality operator determines whether the expression returns true (that is, whether expr == constant) 2) Otherwise, the value of the expression is determined by a call to the static Object.Equals(expr, constant) method. https://learn.microsoft.com/en-us/dotnet/articles/csharp/language-reference/keywords/is#pattern-matching-with-is – kapsiR Apr 14 '17 at 07:32
  • @svick it will call `op_Equality` if that's defined on the type in question, which can make a difference in some cases, though a case where behaviour is different with null would be pretty specialised (though not unheard of). This can also make one more performant than the other, though in such cases using `==` along with a cast as per `(object)x == null` is the one that would be lightest in cases where the others had the same behaviour but more work getting there. – Jon Hanna Aug 31 '17 at 12:25
  • 52
    Always beware in mind that `==` is an overloadable operator. You can have any behaviour you want with it. For e.g. this [weirdly implemented `==`](https://dotnetfiddle.net/QTpdpT) wont tell you if your instance is truly null. `is null` on other hand will always return true for true null references :) Also, if you have `ReferenceEquals` in your code, VS 2017 light bulbs will suggest to change to `is null`, not `== null` (correctly). – nawfal Apr 19 '18 at 13:21
  • 5
    @PatrickHofman should not IL's be the other way around? == calls System.Object::Equals(object, object) and is null calls ceq – Zbigniew Ledwoń Apr 23 '19 at 06:36
  • Downvoted for complicating the sample code by boxing a value type. Once you box a value, reference comparisons are rarely useful. – Frank Hileman Oct 30 '19 at 17:31
  • 4
    As of C# 9.0 "is not" has been introduced and you can use if (obj is not null) instead of if (!(obj is null)) which ignores the use of != – Majid Shahabfar Feb 14 '21 at 07:33
143

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.

Thorkil Holm-Jacobsen
  • 7,287
  • 5
  • 30
  • 43
  • 4
    In addition, note (x is null) requires a class constraint if x is a generic type, while (x == null) and object.ReferenceEquals(x, null) do not. – David Augusto Villa Nov 05 '19 at 23:58
  • 9
    And it should also be noted that null coalescing operator (??) and null coalescing assignment operator (??=) like "is" ignores overloaded equals operator (==) too. – Majid Shahabfar Feb 14 '21 at 07:42
  • That's very useful answer, that explains why my unit test keep failing. – Taher Dec 10 '22 at 08:08
  • The last point doesn't seem to be valid anymore... it fails if -specifically- setting a value type on the constraint (`bool IsNull(T item) where T : struct => item is null; `), but `bool IsNull(T item) => item is null; ` seems to be fine currently – Jcl Aug 09 '23 at 12:05
40

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.

enter image description here

enter image description here

P.S. Tested on https://dotnetfiddle.net/ with NetCore3.1

Frederic
  • 1,580
  • 15
  • 15
  • 1
    This just comes down to your visual studio settings. You can easily tell the compiler to make those warnings into compile errors. – Benjamin Sutas Sep 02 '21 at 03:04
  • 10
    @BenjaminSutas Indeed you can configure VS to change de default behavior. But the default behavior is generally the one that is expected by most users. – Frederic Sep 03 '21 at 00:15