May 05 2020
I’ve been frustrated to see how much useless repetition I had in my C# conditionals and switches, but I knew programmers could do better. In this post, I’ll show you how to make your code more concise with Unity C# Pattern Matching (C# 7.0)
Are you doing type casting in your if or switch statements?
If so, you can profit from pattern matching.
Have you ever written this type of code?
var collider = FindObjectOfType<Collider>();
if (collider is SphereCollider)
{
SphereCollider sphereCollider = ((SphereCollider)collider);
sphereCollider.radius *= 2f;
}
else if (collider is BoxCollider)
{
BoxCollider boxCollider = ((BoxCollider)collider);
boxCollider.size *= 2f;
}
else ...
If so, you probably found it sucky to repeat the lines you have in mind.
Check, cast, repeat.
Good news: you don’t need that bothersome repetition anymore.
We can do better with Unity C# Pattern Matching.
Pattern matching is a C# 7.0 language feature that helps you combine testing conditions and extracting values at the same time.
This probably means little to you, but the next examples will make it clear.
Let’s rewrite that code snippet with pattern matching in mind:
if (collider is SphereCollider sphereCollider)
{
sphereCollider.radius *= 2f;
}
else if (collider is BoxCollider boxCollider)
{
boxCollider.size *= 2f;
}
What we’ve done is to test if the variable is of a certain type and to extract the value at the same time.
In other words, you can save other programmers from reading a highly redundant line of code.
And that’s elegant.
More than elegant, it’s sexy.
Can you do it with switches as well?
You bet you can.
We can rewrite that code to use a switch format like below:
switch (collider)
{
case SphereCollider sphereCollider:
sphereCollider.radius *= 2f;
break;
case BoxCollider boxCollider:
boxCollider.size *= 2f;
break;
case MeshCollider _:
break;
default:
Debug.LogError("I'm a weirdo");
break;
}
By the way, have you seen what we did in line 9?
We used a C# discard, another of the neat new C# features to say “I don’t care about the variable”.
I wonder… what happens if the collider’s radius/size is 0?
Multiplying it by 2 won’t have a noticeable effect, other than slowing down your game performance.
In that case, we can go for an extended switch syntax that lets you test for conditions as well.
Here’s how you can use the when statement:
switch (collider)
{
case SphereCollider sphereCollider when Mathf.Approximately(sphereCollider.radius, 0):
case BoxCollider boxCollider when Mathf.Approximately(boxCollider.size.magnitude, 0):
Debug.LogError("Not gonna work");
break;
case SphereCollider sphereCollider:
sphereCollider.radius *= 2f;
break;
case BoxCollider boxCollider:
boxCollider.size *= 2f;
break;
case MeshCollider _:
break;
default:
Debug.LogError("I'm a weirdo");
break;
}
You can use the when keyword to add conditions to your pattern matching structure.
With pattern matching and conditionals, be aware that the switch case order matters. C# will evaluate your switch sequentially (you know what that means).
When it comes to the performance difference on the if statement, it doesn’t differ noticeably.
Let me elaborate on that.
In my tests, I found the generated codes (IL and C++) to be slightly different.
With pattern matching, the IL code does the cast first and then checks if it worked before entering the branch.
Without pattern matching, the IL code checks if the object is of that type, and only if it is, then enters the branch and does the cast.
These are the timings I got with IL2CPP and Mono by doing 50 million of these operations:
Times in milliseconds | Pattern Matching | No Pattern Matching + Cast |
---|---|---|
IL2CPP — Debug | 3965 | 2654 |
IL2CPP — Release | 186 | 189 |
IL2CPP — Master | 191 | 204 |
Mono | 236 | 233 |
Considering how often you’ll run these instructions per frame, it won’t make a difference at all in your project.
That is, unless you’re doing hundreds/thousands of them per frame (and you shouldn’t do that).
Regarding the switch statement… well, the conclusions are similar. The switch complexity increases as you add conditionals with the ‘when’ keyword, as you’d normally expect.
It’s an excellent practice to avoid if and switch statements in your update functions.
If you can do that, you’ll be fine.
Unity C# Pattern Matching makes you a more concise programmer.
Say more, in fewer lines.
And the performance difference isn’t significant enough to even consider performance a factor
So, green light to go crazy with pattern matching in your project.
If you want more Unity Performance Tips, get my Unity Performance Checklist.
~Ruben