Reputation: 22007
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
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
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
is
version is a bit neater and a little more succinct I guess.Upvotes: 6
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
Reputation: 27432
I found the compiler is very intelligent.
There are several variants of translations for the is
expression:
if(SomeMethod() is MyType a) {...}
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) {...}
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