3 minute read

Let’s look at some of the most interesting and exciting features introduced in C# 10.0.

What’s new in C# 10

Global usings & implicit usings

Actually, I’m not sure I like this two things. I like things to be mostly explicit, without much of an implicit magic.

But, we shall see how it comes - may be it will be great to use them.

It seems that this features enabled Minimal APIs templates and further simplified Top-level programs introduced in C#9.

Global usings

We have a new way of declaring usings - once for entire project:

global using MySuperLongCompanyName.MyNotSoLongFancyApplicationName.AndSomeMoreSomething.Common.BecauseEveryoneLovesCommon;

Of course we still can use using static XXX(anyone uses it?) and namespace aliases.

Another way to add namespace for entire project is to add to a .csproj file:

<Using Include="OmgThisIsAwesome.HideYourUsingsFromEveryoneEyesInCsProjFiles">

Implicit usings

New projects created from .NET 6 templates will have this feature enabled by default and it will provide your project with a list of global usings depending on your project type.

Feature is controlled by this flag in .csproj

<ImplicitUsings>enable</ImplicitUsings>

and we can disable it like

<ImplicitUsings>disable</ImplicitUsings>

and it creates auto generated file in obj\Debug\net6.0\ProjectName.GlobalUsings.g.cs (for console app)

// <auto-generated/>
global using global::System;
global using global::System.Collections.Generic;
global using global::System.IO;
global using global::System.Linq;
global using global::System.Net.Http;
global using global::System.Threading;
global using global::System.Threading.Tasks;

File-scoped namespaces

This is a cool and simple one! Gives -1 to Nesting Level.

Write this:

namespace MyCoolNamespace;

class XXX
{
    ...
}

instead of this:

namespace MyCoolNamespace
{
    class XXX
    {
        ...
    }

Type-inference for lambdas

With every new version C# is becoming more and more similar to JavaScript (for good or evil).

Now we can do this

var writeSomething = (string something) => Console.WriteLine(something);

instead of this

Action<string> writeSomething = (string something) => Console.WriteLine(something);

or even specify return types for some strange cases

var someStrangeLambda = object (int someParam) => someParam>10 ? "YES!" : 42;

Record structs

Note: for record classes see this article.

Now we can declare record structs!

public record XXX {...} // record class
public record class XXX {...} // also class

public record struct Student // this will be a struct
{
    public int Id {get;init;}
    public string Name {get;init;}="John"; // property initializer in struct!
}

// and we can even do this (positional records)
public record struct Teacher(int Id, string Name); // brevity is the sister of talent (c)

This will be a good replacement for Tuples.

WARNING! Unlike record classes record structs are mutable (which is strange and doesn’t look consistent to me) - so we shall use readonly keyword to stop this:

public readonly record struct Teacher(int Id, string Name);
// will give compile-time error if we will try to mutate properties

and use this to mutate records:

var mutatedTeacher = teacher with { Name = "Teacher-man!" };

Extenteded Property patterns

Property patterns were around since C# 8.0 (and they are awesome), but now we can write more clean code by referencing nested properties with simple dot-patterns:

var car1 = new Car
{
    Id = 1,
    OwnerName = "John",
    Model = new ModelInfo
    {
        Model = "Mustang",
        Manufacturer = "Ford",
        ModelYear = 2016
    }
};

var car2 = new Car
{
    Id = 1,
    OwnerName = "John",
    Model = new ModelInfo
    {
        Model = "Explorer",
        Manufacturer = "Ford",
        ModelYear = 2021
    }
};

var cars = new[] { car1, car2 };

foreach (var car in cars)
{
    // BEFORE
    if (car is Car { Model: { ModelYear: 2016 } })
    {
        Console.WriteLine($"Found 2016MY: {car.Model.Model}");
    }

    // NOW
    if (car is Car { Model.ModelYear: 2016 })
    {
        Console.WriteLine($"Found 2016MY: {car.Model.Model}");
    }
}

Additional info can be found here and here.

ArgumentNullException

Thanks to Caller expression attribute we can now simplify null checks:

public void ReplaceTires(Tire tires)
    {
        // BEFORE (Notice that we need to use argument's name twice, which can lead 
        // to an error)
        if (tires == null)
        {
            throw new ArgumentNullException(nameof(tires));
        }

        // NOW
        ArgumentNullException.ThrowIfNull(tires);
    }