Are you using these features in C#?

As of today, C# 11 is in the starting blocks and can already be tested using the preview features in Visual Studio 2022. But here, I want to review some of the features that the language versions since 8.0 brought us. There is quite a lot that we as developers can do differently today than we would have done with previous versions of C#.

Over the past years, my fellow developers and I tried to stay compatible with the latest language and .NET Core versions. So, all those new features, especially with C# 8.0 were at least available to us. And some of them did make it into our daily habits, some did not.

Let’s have a look into a few of those language features. And also, let’s do some polls in between to see whether we like or don’t like any of these.

Default interface implementations

Starting with C# 8.0 and .NET Core 3.0 you are able to provide default implementations already in interfaces:

public interface IMakeCoffee
{
    void MakeCoffee()
    {
        Console.WriteLine("I am the default implementation.");
    }
}

public class Coworker : IMakeCoffee
{
}

public class FrenchPress : IMakeCoffee
{
    public void MakeCoffee()
    {
        Console.WriteLine("I am the french press implementation.");
    }
}

Looking at this code, you’ll see that the interface features a method definition. Both classes implement IMakeCoffee technically, but only one provides a separate definition of the method body. Note that, compared to class inheritance in C#, the override keyword is not needed in order to provide a new implementation.

Obviously, this example isn’t really good at demonstrating possible benefits regarding this feature, but you probably get the idea.

Default interface implementations - do you like and use it?
×
Click here to see my (very personal) opinion

I cannot say that I like this feature. Thinking about having to refactor a bunch of code that both uses interfaces with default implementations, class inheritance, and method overrides I imagine myself going crazy over determining where the exact call might come from and which code is unneeded and which is not. Also, I think that providing default implementation in an interface clearly violates interpreting it as a contract, an API.

Advanced pattern matching and switch expressions

Syntactic sugar-like stuff has also been introduced with C# 8.0, trying to give us alternatives to writing switch expressions for example:

public enum CoffeeType
{
    Americano,
    Decaf,
    Latte
}

public class FrenchPress
{
    public bool IsMilkInTheFridge { get; private set; }

    public bool CanMakeCoffee(CoffeeType type)
    {
        return type switch
        {
            CoffeeType.Americano => true,
            CoffeeType.Decaf => false,
            CoffeeType.Latte when this.IsMilkInTheFridge => true,
            _ => throw new CoffeeTypeException();
        };
    }

    public void MakeCoffee(Jar jar, CoffeeType type)
    {
        if (Jar is not EspressoCup)
        {
            ...
        }
    }
}

So, for one, switch expressions can become very concise using this syntax, being deprived of all break statements, colons, newlines.

As for the advanced pattern matching, it can provide you with an early exit using this type of safe casting. Often, it is possible to reduce nesting in your code this way.

Advanced pattern matching and concise switch statements - do you like and use it?
×
Click here to see my (very personal) opinion

Oh yes, I love it. Using the simplified switch expressions is quite a benefit to the readability of code. And as for the pattern matching stuff: The not is a great extra. However, adding property matching to it, e.g., if (jar is EspressoCup {Size: 100}) can make your code less readable, but that depends…

Asynchronous disposable

Since C# 8.0, asynchronous disposing is available to us developers. However, I find that it has not yet trickled through to many people or at least not many are talking about it. Part of the problem might be, that in order for it to work correctly, you might have to implement both IAsyncDisposable and IDisposable. This example is taken from the official documentation by Microsoft:

public class ExampleAsyncDisposable : IAsyncDisposable, IDisposable
{
    public void Dispose()
    {
        Dispose(disposing: true);
        GC.SuppressFinalize(this);
    }

    public async ValueTask DisposeAsync()
    {
        await DisposeAsyncCore().ConfigureAwait(false);

        Dispose(disposing: false);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            // call synchronous dispose methods here!
        }
    }

    protected virtual async ValueTask DisposeAsyncCore()
    {
        // call asynchronous dispose methods here!
    }
}

That is a lot of boilerplate code, especially while the full dispose pattern is already a lot to digest. By the way, if you are interested in more on asynchronous code, visit my article on that topic: 10 Best Practices in Async-Await Code in C# in [2022].

Asynchronous dispose pattern - do you like and use it?
×
Click here to see my (very personal) opinion

I haven’t used it a lot. But there is obviously a need for this functionality, and I will make sure to use it correctly. Even though C# and .NET do have garbage collection, there is still a lot of room for error to be made by the guy in front of the IDE…

Indices and ranges

This one is for all those loving Python and other data-driven languages. (Again) C# 8.0 brought indices and range operators to us.

private string[] labels = new[]
{
    "Americano",
    "Decaffinated",
    "Latte",
    "Espresso"
};

public void GetItems()
{
    var firstItem = this.labels[0];
    var lastItem = this.labels[^1];

    var firstTwoItems = this.labels[0..2];
    var alsoFirstTwoItems = this.labels[0..2];

    var lastTwoItems = this.labels[^2..^0];
    var alsoLastTwoItems = this.labels[^2..];

    var range = 1..^1;
    var middleItems = this.labels[range];
}

This one is worth spending a few seconds on. You can index elements of an array (or better, a sequence) starting at the beginning, counting backward from the last item, store ranges in variables, and use them later.

Index and range operators - do you like and use it?
×
Click here to see my (very personal) opinion

I have a data science background myself, that is, I am used to fluently indexing arrays and matrices. First, it was MATLAB (oh yeah, indices starting with 1…), then Python. So, naturally, I should be drawn to features like this. And I am, but what I find genuinely confusing is the syntax for backward-indexing elements. If I want the last element, I’d write [^1], but if I want the last two elements, I’d write [^2..^0]. Seriously? Why not introduce a new and telling keyword for this, like end in other languages…

Omitting type in new()

While C# 9.0 was all about records and related features, there is one little thing that can help you remove noise from your code:

private EspressoCup cup = new();
private (int a, int b) tuple = new();

Since then, omitting explicit types during object instantiation is no longer necessary. We can trust the compiler on determining the right type, even for “complex” stuff like tuples and actions.

Target-typed 'new' expressions - do you like and use it?
×
Click here to see my (very personal) opinion

Yep, I love it. Even with code completion tools, I always found all that duplication unnecessary.

Global and implicit usings

C# 10.0 did quite a lot (as did the previous language updates), but I only want to focus on the most obvious changes:

  1. You can add a global modifier to any using directive. The compiler will then add this using to all files of your project implicitly.
  2. When running .NET 6, any newly generated project will have implicit using turned on. That means you don’t see any of the using statements at the top of your file anymore. You can disable this feature by adding a line to your project file:
<PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>disable</ImplicitUsings>
</PropertyGroup>

Global and implicit usings - do you like and use it?
×
Click here to see my (very personal) opinion

To be honest: I hate this.
I like to see the references and dependencies my code has. Especially during refactoring tasks, glancing at the top of your file easily reveals what needs to be removed. I disable this feature in all of my new projects.

File-scoped namespaces

Also introduced with C# 10, namespace declarations can be file-scoped, thus making a few parentheses unnecessary, like so:

namespace MyNamespace;

public class FrenchPress
{
  ...
}

File-scoped namespaces - do you like and use it?
×
Click here to see my (very personal) opinion

Although looking weird at first, I think this is a good, albeit small improvement in readability. Especially those like me, favoring one-class-per-file rules, shouldn’t see any problem with this.


C# 8.0 has been introduced in 2019 and since then, all language versions have given us more features than I could cover in this blog. Overall, I do see continuous improvement in the general developer experience in C#. After all, we can still decide to not use any of those new features.

I hope you found something new and participated in our polls!

Please leave a comment, share on social media, and subscribe to our post newsletter!

Leave a Reply

Your email address will not be published.