Dotnet logo

.NET Tools

Essential productivity kit for .NET and game developers

How-To's

Switch Expressions and Pattern-Based Usings – A Look at New Language Features in C# 8

ReSharper and Rider support for C# 8In 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 usings and using declarations.

In this series, we are looking at:

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:
Typing Assist in Switch Expressions

Also ReSharper’s and Rider’s structural navigation has been updated to work nicely with switch expressions:
Structural navigation in 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 tryfinally statement on IL level) around all of its following statements:
Convert to using declaration

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!

image description