.NET Tools
Essential productivity kit for .NET and game developers
Switch Expressions and Pattern-Based Usings – A Look at New Language Features in C# 8
In this post, we will continue our journey through C# 8 language features. Previously, we’ve shown how ReSharper and Rider can help you work with the new Index
and Range
types as well as null-coalescing assignments (or compound assignments in general). Today, we will cover two other very helpful additions, which are switch
expressions and pattern-based using
s and using
declarations.
In this series, we are looking at:
- Indices, Ranges, and Null-coalescing Assignments
- Switch Expressions and Pattern-Based Usings
- Recursive Pattern Matching
- Async Streams
- Nullable Reference Types: Migrating a Codebase
- Nullable Reference Types: Contexts and Attributes
Switch Expressions
As with most of the C# 8 language features, switch expressions also aim for reduced ceremony and reduction of boilerplate code. In most cases – pun intended – switch
statements don’t do heavy computations but return simple values. Fortunately, we can now write such code as a single expression:
// Using switch statement switch (policy) { case FileExistsPolicy.Fail: throw new Exception($"File '{targetFile}' already exists."); case FileExistsPolicy.Skip: return false; case FileExistsPolicy.Overwrite: return true; case FileExistsPolicy.OverwriteIfNewer: return File.GetLastWriteTimeUtc(targetFile) < File.GetLastWriteTimeUtc(sourceFile); default: throw new ArgumentOutOfRangeException(nameof(policy), policy, message: null); } // Using switch expression return policy switch { FileExistsPolicy.Fail => throw new Exception($"File '{targetFile}' already exists."), FileExistsPolicy.Skip => false, FileExistsPolicy.Overwrite => true, FileExistsPolicy.OverwriteIfNewer => File.GetLastWriteTimeUtc(targetFile) < File.GetLastWriteTimeUtc(sourceFile), // _ => throw new ArgumentOutOfRangeException(nameof(policy), policy, message: null) };
While the switch
expression is obviously much more concise, let’s decompose what actually happens. First, the switch
keyword is now written infix to be better distinguishable from the switch
statement, as described in the official documentation.
Second, the case
keywords and colons :
have made way for the lambda arrow =>
. Meanwhile, different cases are solely separated by a comma.
Third, we’ve been able to get rid of the default
case, because the runtime will automatically throw a SwitchExpressionException
if the switch expression is non-exhaustive. If this happens, the unmatched value will be included in the exception message. If we still want to define a custom default case, we can use the _
discard pattern. Note that switch expressions also work nicely with throw
expressions and expression-bodied members.
ReSharper and Rider will help us making the switch from switch
statements to switch
expressions, by automatically replacing the case
and default
keyword:
Also ReSharper’s and Rider’s structural navigation has been updated to work nicely with switch expressions:
Switch expressions also integrate smoothly with recursive pattern matching, which we will cover in the next post.
Pattern-Based Usings and Using Declarations
With the introduction of pattern-based usings and using declarations, we are able to get rid of tedious code. First, let’s focus on the pattern-based usings, which basically allows to use a type as if it would implement IDisposable
, although it doesn’t:
static void M() { using (new RefStruct()) // no error with C# 8 { } } ref struct RefStruct { public void Dispose() { } }
In the current implementation, pattern-based usings are restricted to ref struct
types, but especially for them it’s very helpful to use pattern-based usings, since they are stack-only and can’t implement any interfaces.
Now we’re getting to the more interesting part, which are using declarations. Quite often, a using
statement fills up the body of a method and has the actual meaningful code nested inside it. Objects of IDisposable
also often come in series, which can lead to further indented code if we were not to add an exception to our code style settings. A using
declaration is a single statement, which implicitly spans a using
statement (or try
–finally
statement on IL level) around all of its following statements:
One limitation of the new using
declarations is that they need a variable initialization to be done, unlike the using statement, which works with any expression that is of the IDisposable
type. Another is the variable itself is read-only and cannot be re-assigned.
Download ReSharper Ultimate 2019.1 or check out Rider 2019.1 to start taking advantage of ReSharper’s language support updates and C# 8. We’d love to hear your thoughts!