C# has long supported two operators to check the type of an object: is
and as
. C# 7 adds a new way to use the is
operator that combines is
with basic patterns to provide an alternative for as
.
The new is
patterns provide a nicer syntax for safe casting than both the existing is
and as
operators and address the limitations of those operators as well.
TL;DR
C# 7 adds support for constant patterns, type patterns, and var patterns to the is
operator. Use them like this:
if(input is null)
return 0;
if(input is 5)
return 5;
if(input is int count)
return count;
if(input is string text)
return text.length;
if(input is var output)
return -1;
Unsafe Casting
You often need to cast an object to another type. You can do that directly using the cast operator, (string)input
, but what if input
is not a string
? Boom! You'll get an exception.
If you're absolutely certain of the type of an object, you can get a tiny performance boost by using an unsafe cast. But because we want to avoid exceptions, it's better to use a safe cast.
Safe Casting with is
One way to cast safely is to check the type using is
and then cast. The problem with this method is that input
is accessed twice.
if(input is string)
{
string text = (string)input;
}
Safe Casting with as
A better way to cast safely is to use the as
operator, which returns null when input
is not a string
. This also avoids the small performance hit of accessing input
twice.
string text = input as string;
if(text != null)
{
...
}
Problems with as
There are two limitations with the as
operator.
- It doesn't distinguish between a null value and the wrong type.
- It doesn't work with non-nullable types like
int
.
Update (14th April 2017): As Yves Goergen notes in the comments, null
has no type, so it is always the wrong type and in fact, is
treats null
in the same way, both with and without type patterns. Therefore, the first bullet point is not a limitation of as
; instead, it is the cause of a problem I've encountered with the usage of as
: the negation of as
, if(text == null)
, is used when if(text == null && input != null)
is intended. It seems a lot more common to use the negation of as
incorrectly, than the negation of is
, if(!(input is string))
.
Safe Casting with is
and type patterns
The new method of casting safely in C# 7 is to use is
with type patterns. Here is an example of how to use is
with type patterns to safely cast input
to a string
.
if(input is string text)
{
...
}
Not only is this the shortest and cleanest syntax, but it has none of the problems that plagued the previous methods:
input
is only accessed once.- The pattern will not match if
input
is null. - Non-nullable types like
int
are supported.
Type Patterns and Constant Patterns
The last example used is
to match on what is called a type pattern: string text
. Type patterns do not match null values, because null
is type-less. Therefore, in the previous example, text
will never be null.
If we want to match null
, we need to use a constant pattern. Constant patterns can be used with is
to match any constant value including null
. Here are three examples of constant patterns, followed by two examples of type patterns.
if(input is null)
return 0;
if(input is 3)
return 3;
if(input is "Hello")
return 5;
if(input is int count)
return count;
if(input is string text)
return text.length;
Scope of Pattern Variables
When a pattern variable like text
is introduced by a pattern match, it is introduced into the enclosing block's scope.
In if
statements and other statements that do not establish their own scope, the pattern variable is available to later code in the same scope. This means they behave as though they were declared immediately prior to where they are used, like text
in the earlier as
example. This enables their use with negations:
if(!(input is string text))
return;
Console.WriteLine(text.Length);
In while
statements and other statements that establish their own scope, the pattern variable is only available within the newly established scope, i.e. inside the while loop.
object input = "hello";
while(input is string output)
{
Console.WriteLine(output);
if(input == "world")
input = null;
else
input = "world";
}
// output is no longer in scope
Var Pattern
There is one final pattern available: the var pattern. The var pattern always matches, always returns true, and it merely puts the value into a new variable with the same type as the input.
Unlike type patterns, the var pattern also matches null
.
string text = null;
if(text is var temp)
Console.WriteLine("true");
Output:
true
Avoiding Multiple Evaluations with the Var Pattern
You might ask: when would I ever use the var pattern? Isn't it absolutely useless? Well, Alexander Shvedov figured out that you can use it to avoid multiple evaluations as demonstrated in this gist.
Often you find yourself walking some hierarchy, a list, a tree, the DOM, until some condition is met. For example, you might walk up the type hierarchy to the root type (yes, this is silly; it always ends up at Object
).
while(type.BaseType != null)
type = type.BaseType;
While succinct, this is not efficient. We're evaluating BaseType twice per iteration instead of once. Imagine if BaseType
was a really expensive method call, like a database call. We can make it more efficient by using a temporary variable, temp
, to avoid the duplicate evaluation.
Type temp;
while((temp = type.BaseType) != null)
type = temp;
The var pattern provides a different way to achieve the same thing.
while(type.BaseType is var temp && temp != null)
type = temp;
In this example, the inline assignment is quite readable. But in general, I detest inline assignments vehemently as they regularly become unwieldy with more complex method calls and conditions. It quickly becomes difficult to identify where the assignment ends and the conditions begin. Therefore, I think the var pattern is more readable.
Of course, in this particular example, a type pattern would be the most succinct and readable.
while(type.BaseType is Type temp)
type = temp;
Conclusion
C# 7 has added basic pattern matching to the is
operator. This means you won't need as
as often and your code will be slightly more readable.
When casting, it'll be easier to distinguish null values from type mismatches and it'll be easier to work with non-nullable types. You'll also be able to eliminate some nasty inline assignments by using the var pattern.