What’s new in C# 10
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);
}