Nigel
Nigel

Reputation: 3311

How should I interpret the "null check pattern" in C#

I have some code that effectively does this:

    private void DoStuff(int? a)
    {
        int c = 0;
        if (a is int b)
        {
            c = b;
        }
    }

But a is int b gives me a warning:

Use not null pattern instead of a type check succeeding on any not-null value

Using Resharper's suggestion "Use null check pattern" autocorrects this code as follows, causing the warning message to disappear:

   private void DoStuff(int? a)
    {
        int c = 0;
        if (a is { } b)
        {
            c = b;
        }
    }

That's great and all but now I don't understand the code I'm writing. How should I interpret if(a is {} b) in the english language?

Is it saying "if a is not null set b to a's non-null value"?

Or is {} a shorthand for "the underlying type of a" (i.e. int)?

Is there anything I can put inside the braces, or do the braces alone have their own meaning?

Anything to help me understand what this code really means would be appreciated. Thank you.

Upvotes: 21

Views: 8292

Answers (3)

Hans Kesting
Hans Kesting

Reputation: 39358

Would that a have been of type object, then the if (a is int b) check would be fine and I don't think Resharper would have complained. However, now a has type int? (or Nullable<int>) so the check "is it really an int?" is the same as "is it not null?".

This can be expressed more clearly as

private void DoStuff(int? a)
{
    int c = 0;
    if (a.HasValue)
    {
        c = a.Value;
    }
}

or shorter

private void DoStuff(int? a)
{
    int c = a.GetValueOrDefault();
}

to assign the default value (0) when a is null, or use the real value when not.

Upvotes: 0

Stan
Stan

Reputation: 6295

TL;DR resharper is incorrect, official language reference suggests if (a is int b). Anyway if (a is int b) and if (a is { } b) are compiled into exactly the same CIL.


The Resharper suggestion seems to be incorrect, even the official language reference for nullable value types (link) literally shows an example with if (a is int valueOfA):

int? a = 42;
if (a is int valueOfA)
{
    Console.WriteLine($"a is {valueOfA}");
}
else
{
    Console.WriteLine("a does not have a value");
}
// Output:
// a is 42

Even the official language reference for is (link) operator shows:

int i = 34;
object iBoxed = i;
int? jNullable = 42;
if (iBoxed is int a && jNullable is int b)
{
    Console.WriteLine(a + b);  // output 76
}

Also if you inspect CIL, it's compiled in .NET 7 into the very same thing. See for yourself here.

if (a is { } b):

        IL_0000: nop
        IL_0001: ldc.i4.s 0
        IL_0003: stloc.0
        IL_0004: ldarga.s a
        IL_0006: call instance bool valuetype [System.Runtime]System.Nullable`1<int32>::get_HasValue()
        IL_000b: brfalse.s IL_0018

        IL_000d: ldarga.s a
        IL_000f: call instance !0 valuetype [System.Runtime]System.Nullable`1<int32>::GetValueOrDefault()
        IL_0014: stloc.1
        IL_0015: ldc.i4.1
        IL_0016: br.s IL_0019

vs. if (a is int b):

        IL_0000: nop
        IL_0001: ldc.i4.s 0
        IL_0003: stloc.0
        IL_0004: ldarga.s a
        IL_0006: call instance bool valuetype [System.Runtime]System.Nullable`1<int32>::get_HasValue()
        IL_000b: brfalse.s IL_0018

        IL_000d: ldarga.s a
        IL_000f: call instance !0 valuetype [System.Runtime]System.Nullable`1<int32>::GetValueOrDefault()
        IL_0014: stloc.1
        IL_0015: ldc.i4.1
        IL_0016: br.s IL_0019

Upvotes: 15

Zze
Zze

Reputation: 18865

The a is { } is an example of using is to match an expression against a pattern... reference

The is operator checks if the result of an expression is compatible with a given type. For information about the type-testing is operator, see the is operator section of the Type-testing and cast operators article.

Beginning with C# 7.0, you can also use the is operator to match an expression against a pattern, as the following example shows:

static bool IsFirstFridayOfOctober(DateTime date) =>
    date is { Month: 10, Day: <=7, DayOfWeek: DayOfWeek.Friday }

The a is { } b is an example of using a declaration pattern to declare a new local variable, scoped to the if block. reference

You use declaration and type patterns to check if the run-time type of an expression is compatible with a given type. With a declaration pattern, you can also declare a new local variable. When a declaration pattern matches an expression, that variable is assigned a converted expression result


I highly recommend Sharplab.io to investigate the optimisations that take place during C# compilation. In your provided example:

private void DoStuff(int? a)
{
    int c = 0;
    if (a is { } b)
    {
        c = b;
    }
}

Equates to:

private void DoStuff(Nullable<int> a)
{
    int num = 0;
    int valueOrDefault = default(int);
    int num2;
    if (a.HasValue)
    {
        valueOrDefault = a.GetValueOrDefault();
        num2 = 1;
    }
    else
    {
        num2 = 0;
    }
    if (num2 != 0)
    {
        num = valueOrDefault;
    }
}

So basically a is { } b is saying; does a match any pattern (i.e. not null) if yes, assign it to a local variable b.


Personally I think that whatever code you write should be as readable as possible for everyone. If you are confused by the code you have written then don't write it, write something that the person taking over from you will understand.

Upvotes: 18

Related Questions