Sinatr
Sinatr

Reputation: 22007

The "is" type pattern expression for null check

I can refactor this code (popular as/null check pattern)

var a = b as MyType;
if(a != null) { ... }

..into a nice "is" type pattern expression:

if(b is MyType a) { ... }

..which is cool... I think... Is it?


But now I am also thinking to refactor

var a = SomeMethod();
if(a != null) { ... }

..into:

if(SomMethod() is MyType a) { ... }

Note: there is no as and SomeMethod() already returns MyType. It looks like (pseudocode) if(A is A) and may easily confuse, no?

The first refactoring is legal, but what about the latter one? I am not an IL expert to check myself and C# 7.0 features are still new to me. Perhaps there are problems which I didn't discover yet?

Upvotes: 9

Views: 744

Answers (4)

M.Hassan
M.Hassan

Reputation: 11092

In c# 8, a sugar expression for testing not null using pattern matching expression:

if (name is {})  // name !=null
   Console.WriteLine("name is not null")

Upvotes: 0

TheGeneral
TheGeneral

Reputation: 81593

Obviously the 2 implementations are very similar, the difference would be negligible in memory, allocations, and cycles.

The compiler basically treats them as follows (for reference types)

First

MyType myType = SomeMethod();
if (myType != null)
{
   Console.WriteLine(myType.ToString());
}

Second

MyType myType2;
if ((object)(myType2 = SomeMethod()) != null)
{
   Console.WriteLine(myType2.ToString());
}

Probably better seen with the IL

First

IL_0000: ldarg.0
IL_0001: call instance class C/MyType C::SomeMethod()
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: brfalse.s IL_0015

IL_000a: ldloc.0
IL_000b: callvirt instance string[mscorlib] System.Object::ToString()
IL_0010: call void[mscorlib] System.Console::WriteLine(string)

Second

IL_0015: ldarg.0
IL_0016: call instance class C/MyType C::SomeMethod()
IL_001b: dup
IL_001c: stloc.1
IL_001d: brfalse.s IL_002a

IL_001f: ldloc.1
IL_0020: callvirt instance string[mscorlib] System.Object::ToString()
IL_0025: call void[mscorlib] System.Console::WriteLine(string)

Note : You can check out the disassembly, IL and jit-asm here

The IL difference is basically 2 opcodes:

  • dup : Copies the current topmost value on the evaluation stack, and then pushes the copy onto the evaluation stack.
  • Ldloc : Loads the local variable at a specific index onto the evaluation stack.

When Jitted, it would most likely optimize into the same instructions anyway


Summary

  1. There is no appreciable technical difference.
  2. Yeah the is version is a bit neater and a little more succinct I guess.
  3. It's probably more printable characters, so if you have printable character OCD or suffer brutal code reviews, it might not be a good thing
  4. If you like it and your team likes it, go with it.
  5. It's not really my cup of tea

Upvotes: 6

Ben Voigt
Ben Voigt

Reputation: 283951

I wouldn't use it to perform an identity cast on a reference type, as the null check is much more intuitive to the future reader.

For nullable types it's an entirely different story. Given struct S, then

void foo(S? p)
{
    if (p is S s) {
        bar(s);
    }
}

is equivalent to

void foo(S? p)
{
    if (p.HasValue) {
        bar(p.GetValueOrDefault());
    }
}

and avoiding the GetValueOrDefault() call (or worse, read of the Value property which does another null-check) is IMO very useful and significantly helps readability.

Upvotes: 0

shingo
shingo

Reputation: 27432

I found the compiler is very intelligent. There are several variants of translations for the is expression:

if(SomeMethod() is MyType a) {...}

  1. SomeMethod returns MyType

    • MyType has no override operator ==, and variable a is not used

      if (SomeMethod() != null) {...}
      
    • MyType has override operator ==, but variable a is not used

      if ((object)(SomeMethod()) != null) {...}
      
    • MyType has no override operator ==, and variable a is used

      MyType a;
      if ((a = SomeMethod()) != null) {...}
      
    • MyType has override operator ==, and variable a is used

      MyType a;
      if ((object)(a = SomeMethod()) != null) {...}
      
  2. SomeMethod returns other type like object

    • Variable a is not used

      if (SomeMethod() is MyType) {...}
      
    • MyType has no override operator ==, and variable a is used

      MyType a;
      if ((a = (SomeMethod() as MyType)) != null) {...}
      
    • MyType has override operator ==, and variable a is used

      MyType a;
      if ((object)(a = (SomeMethod() as MyType)) != null) {...}
      

BTW you can check all these variants by ILSpy or something similar.

Upvotes: 2

Related Questions