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?
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
- There is no appreciable technical difference.
- Yeah the
is
version is a bit neater and a little more succinct I guess. - 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
- If you like it and your team likes it, go with it.
- It's not really my cup of tea
I found the compiler is very intelligent.
There are several variants of translations for the is
expression:
if(SomeMethod() is MyType a) {...}
-
SomeMethod
returnsMyType
-
MyType
has no override operator ==, and variablea
is not usedif (SomeMethod() != null) {...}
-
MyType
has override operator ==, but variablea
is not usedif ((object)(SomeMethod()) != null) {...}
-
MyType
has no override operator ==, and variablea
is usedMyType a; if ((a = SomeMethod()) != null) {...}
-
MyType
has override operator ==, and variablea
is usedMyType a; if ((object)(a = SomeMethod()) != null) {...}
-
-
SomeMethod
returns other type likeobject
-
Variable
a
is not usedif (SomeMethod() is MyType) {...}
-
MyType
has no override operator ==, and variablea
is usedMyType a; if ((a = (SomeMethod() as MyType)) != null) {...}
-
MyType
has override operator ==, and variablea
is usedMyType a; if ((object)(a = (SomeMethod() as MyType)) != null) {...}
-
BTW you can check all these variants by ILSpy or something similar.