new features of C# 10

Best new features of C# 10

Recently C# 10 and .net 6 were released, with them came a whole host of features. In this post, I’m looking at the best new features of C# 10 and giving a brief explanation/demo of them. This post is largely looking at the highlights, rather than doing an in-depth review of all of the new features.

1. Global Namespaces

This is a killer feature, and one that I wish had always existed.  The concept is simple, take commonly used “imports” away from being added to every file, and import them once globally.

As an example, in our code, we can add the following to the “Program.cs” file:

global using DotNet6ExamplesOddNamespace;

We can then access that namespace anywhere in the project. This means that we can run the following code without having to carry out any more imports:

namespace DotNet6Examples
{
    public class GlobalNamespaceExample2 : IExampleInterface
    {
        //Despite being on a seperate namespace, 
        //we can access this as it's declared as a global 
        //namespace in "Program.cs"
        public void Run() => new GlobalNamespaceExample().Run();

    }
}

Even better, a lot of the “standard” imports are now global by default. You can get a full list of the default namespaces here. This is fantastic because it means that you can run code like the following without having to carry out a single import.

public void Run()
{
     /*Awesomely, all of this code works without importing a single namespace.
     This is because libraries like "linq"
     are now global namespace by default.*/
     var quickList = new List<String>() { "Value1", "Value2" };
     quickList.ForEach(q => Console.WriteLine(q));
}

2. Lambda Function upgrades

This is a small change, but something that makes code readability significantly nicer.

Previously to assign a function to a variable using lambda functions you’d do the following:

Func<String, int> parse = (String s) => int.Parse(s);
var oldVal = parse("10");

This is fine, but having to declare the operation type as a Function or Action, feels clunky and is hard to read. With the release of C# 10, you can now skip declaring the type, so the above code becomes:

var newParse = (string s) => int.Parse(s);
var newVal = newParse("10");

3. File-scoped namespaces

Another really simple change that makes a huge amount of difference. You no longer need to wrap the scope with the namespace. That means that:

namespace DotNet6Examples.FileScopedNamespace
{
    public class FileScopedNamespace : IExampleInterface
    {
        public void Run() => Console.WriteLine("With scoping");

    }
}

Becomes:

namespace DotNet6Examples.FileScopedNamespace;
public class FileScopedNamespaceUpdated : IExampleInterface
{
    public void Run() => Console.WriteLine("Without scoping");
}

This is so much cleaner and easier to read, it’s a fantastic and straightforward change. The only downside to this is that a lot of syntax highlighters are yet to catch up with the new format! Since the creation of namespaces, we’ve always had to nest our code in a set of brackets that felt largely redundant. The exception for this is if you’re looking to have multiple namespaces within a single file, which is almost always a bad idea anyway.

There’s a great guide to updating your projects to use File-scoped namespaces at meziantou.net.

4. Interpolated String Handler

C# 10 has given us the ability to add custom string interpolation. This means that instead of sticking to default string interpolation, we can customise it ourselves.

With the following code as a setup, we can start to see how you’d implement a solution.

public class InterpolatedStringHandlerExample : IExampleInterface
{
    public void Run()
    {
        var commaSeperate = new CommaSeperator();
        var time = DateTime.Now;
        var user = new User("John");

        commaSeperate.CommaSeperate($"{time}Comma seperated between values{user}");
        commaSeperate.CommaSeperate($"This should just hit the literal string and be printed");
        commaSeperate.CommaSeperate($"{time}This will be comma seperated");


    }
}

public class CommaSeperator
{
    public void CommaSeperate(string msg)
    {
        Console.WriteLine(msg);
    }
}


public class User
{
    public User(String name) => Name = name;
    string Name { get; set; } = "";
    public override string ToString() => Name;
}


In the above code, you can see we’ve done three things.

  1. Setup a Runner class
  2. Setup a method to print out a string (which will later to be comma seperated).
  3. Created a very basic POCO User.

The aim is to use Interpolated string handling to comma separate any values passed to the string interpolation.

This can be accomplished by setting up an additional structure like this:

[InterpolatedStringHandler]
public ref struct TestInterpolatedStringHandler
{
    // Storage for the built-up string
    StringBuilder builder;

    // Add the receiver argument:
    public TestInterpolatedStringHandler(int literalLength, int formattedCount)
    {
        builder = new StringBuilder(literalLength);
    }

    public void AppendLiteral(string s)
    {
        builder.Append(quoteString(s));
    }

    public void AppendFormatted(T s)
    {
        builder.Append(quoteString(s?.ToString()));
    }


    private String quoteString(String val) => "\"" + val + "\",";
    internal string GetFormattedText() => builder.Remove(builder.Length - 1, 1).ToString(); //Remove the final comma
}

The first aspect of this is to declare the struct with the ” InterpolatedStringHandler” attribute. This sets up the struct to be used as an Interpolated String Handler. We then set up four methods to facilitate the handling of the string.

  1. A constructor
  2. A method to deal with literal strings.
  3. A method to dead with generics (T).
  4. A method to return the finalised string.

In this case, you can see that in “1” we instantiate a StringBuilder, this is where we’ll piece together our string.

We then set up a method to deal with appending literal strings:

public void AppendLiteral(string s)
{
        builder.Append(quoteString(s));
}

This method is called for every literal string in our interpolated string. So if this string is used $”This {value} is an example”, the above method will be called twice. Once for “This ” and the second time for ” is an example”. In between, the method for running on generics will run. This is the following method:

public void AppendFormatted(T s)
{
      builder.Append(quoteString(s?.ToString()));
}

For both of the “Append” methods, we’ve used a quick “quoteString” method to wrap the text in quotes as a function of the custom interpolation. This is purely to demonstrate the functionality.

The next element of this approach is to return the newly formatted string with:

internal string GetFormattedText() => builder.Remove(builder.Length - 1, 1).ToString(); //Remove the final comma

This code finalises the string, whilst removing the last character as it’s always going to be a redundant comma.

Now that we’ve pieced together our string Interpolation handler, we can overwrite the ” CommaSeperate” method in our “CommaSeperator” class. Whenever an Interpolated string is passed to the method, the overridden method will be run:

public class CommaSeperator
{
	public void CommaSeperate(string msg)
    {
        Console.WriteLine(msg);
    }


    public void CommaSeperate(TestInterpolatedStringHandler builder)
    {
        Console.WriteLine(builder.GetFormattedText());
    }
}

Now when we run the code, we’ll have our custom Interpolation carried out giving us:

Best new features of C# 10

For those of you that are unfamiliar with Interpolated strings in C#, take a look at my post here and you can see Microsoft’s tutorial for custom interpolated strings here.

Conclusion of new features of C# 10

These are some of the best new features of C# 10, there’s a number of other features that you can explorer in the Microsoft dev blog here. It’s great seeing C# becoming more streamlined and easy to develop in.

I’ve uploaded all of these examples to Github here.

Let me know in the comments if you have a favorite new feature in C# 10!

Leave a Comment

Your email address will not be published. Required fields are marked *