In this post, we're going to look at the latest features of C#, C#10. Remember that in order to use this version, you need to have .NET 6 installed.
It will be officially released in a couple of weeks, but the "release candidate" is already available.
Table of Contents
1 - Global usings in c#
In C#9, we saw how top-level statements were introduced, which greatly reduced the amount of code needed to run a console application.
The goal of global usings is essentially the same, to reduce the amount of code we need. It's worth mentioning that we already (more or less) had this functionality available in Blazor since .NET 5 (c# 9).
This consists of having our usings
(in other words, the namespaces
we're going to use) in a common place.
To do this, we have two options:
1.1 - Global usings file
We can create a file in our project to include these global usings
, simply referencing them, and thus we can use them from anywhere in that project.
global using System;global using System.Collections.Generic;global using System.Linq;
1.2 - Update the project's csproj file
Optionally, we can include them in the application's .csproj
file by enabling the <ImplicitUsings>enable</ImplicitUsings>
annotation and including them inside an ItemGroup
.
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net6.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> </PropertyGroup> <ItemGroup> <Using Include="System.Threading" /> </ItemGroup> </Project>
Personally, I’m not sure which option I prefer. What I do know is that I don't want a combination of both and possibly, to avoid having global usings
specified all over the code, it might be a good idea to centralize them in the .csproj
.
2 - Namespace declaration per file
By now we all know what a namespace
is or the .NET namespace, it's basically the logical space where our classes, methods, etc. are located.
Until now (c# 9 and earlier) we would write it like this:
namespace csharp10{ class NamespacePerFile { }}
But with C# 10, and with the same goal of reducing code, we can declare the namespace in a single line without using brackets, which saves both code and indentation.
namespace csharp10;class NamespacePerFile{}
3 - Minimal apis
One of the major changes in this version of C# are minimal APIs, which I covered in an in-depth post about them.
In summary, Microsoft now makes it possible to create APIs with very few lines of code, which is great.
using System;using Microsoft.AspNetCore.Builder;using Microsoft.Extensions.Hosting;var builder = WebApplication.CreateBuilder(args);var app = builder.Build();app.MapGet("/", () => "Hola NetMentor!");app.Run();
4 - DateOnly and TimeOnly
In C#, and really in every other programming language, there are always problems with dates and times, like what time a date corresponds to, and so on.
Some time ago I made a short YouTube video explaining why it’s important to use UTC
when storing times, but sometimes you only need the date or the time.
That's why we now have the DateOnly
and TimeOnly
types.
4.1 - DateOnly in C#
Using and understanding DateOnly
is very easy, as it stores only the date. This can be really useful, for example, for indicating which days someone is on vacation in HR software. We’ve all had scenarios at work where we've stored the time as 00:00:00.
Now you just need to use DateOnly, and it's really simple; in fact, you can use DateTime.Now as a reference:
DateOnly date = DateOnly.FromDateTime(DateTime.Now);
4.2 - TimeOnly in C#
Similar to the previous case, you can start from a DateTime
object to get the time, parse the time from a string
, or, probably the most sensible option, get it from a TimeSpan
.
TimeOnly time = TimeOnly.FromTimeSpan(TimeSpan.FromHours(13));
5 - Lambda improvements
This functionality is related to delegates as well as lambda expressions. We know that you can declare a delegate in a single line, but if you use var
instead of specifying the delegate type, I personally prefer to specify the type, but I also understand that many developers prefer to use var
.
Func<string> lambdaExpression = () => "hola Netmentor.";var example = () => "hola Netmentor."; // In C#9, this doesn’t compile.
- Note: In addition, C# 10 allows us to specify the return type of the lambda expression, which wasn’t possible in previous versions.
Func<string?> withReturn = string? () => null;
6 - PriorityQueue Collection
A new collection type, which is a queue that allows you to specify the priority of items you enqueue.
- You can find more details in this post.
PriorityQueue<string, int> priorityQueue = new PriorityQueue<string, int>();priorityQueue.Enqueue("Opel", 2);priorityQueue.Enqueue("Audi", 1);priorityQueue.Enqueue("BMW", 3);
7 - LINQ improvements
7.1 - Max/MaxBy and Min/MinBy
After a long time, LINQ now includes the ability to retrieve the maximum or minimum value from a list, based on an attribute (if it’s an object).
What used to require .OrderBy(x=>x.Priority).First();
can now be done with MaxBy()
/MinBy()
.
The difference between using .Max()
/.Min()
and .MaxBy()
/.MinBy()
is in the result: with .Max()
you get the maximum value of the compared element, whereas .MaxBy()
returns the entire object:
List<MaxMinExample> exampleList = new(){ new(3, "priority 3"), new(2, "priority 2"), new(10, "priority 10"), new(8, "priority 8")};int max = exampleList.Max(x=>x.Priority); // Returns 10 (as an integer)MaxMinExample? maxBy = exampleList.MaxBy(x => x.Priority); // Returns the object with priority 10
7.2 - IEnumerable Chunk
Finally, there's an option that allows you to take a list (or any type that implements IEnumerable) and create chunks
from it. That means if you have a list of 1000 items, you can group them in blocks of 10, 100, 500, or however many you specify.
Personally, I'm glad to see this feature because I've had to implement it manually more than once at different companies.
Its functionality is pretty straightforward: you have a list and use the .chunk()
method, passing in the size of each block you want.
IEnumerable<int> listInts = Enumerable.Range(0, 1000);//list of 1000 elementsint chunkSize = 50;foreach (var chunk in listInts.Chunk(chunkSize)) //returns a list of the indicated size.{ //Parallel calls Parallel.ForEach(chunk, (item) => { Console.WriteLine($"Simulating get with id {item}"); });}
7.3 - Take method improvement
When you want to retrieve part of a list and print it, we’ve always had to skip a certain number of items and then get the ones we want.
In C# 10 you can now specify a range directly:
List<int> listInts = Enumerable.Range(0, 1000).ToList();//list of 1000 elementsvar resultCsharp9 = listInts.Skip(10).Take(15);var resultCsharp10 = listInts.Take(10..25);
8 - Property patterns
In the latest versions of C#, Microsoft has provided what is called "pattern matching" and related features. In this case the improvement is that you can access the child element's properties through a dot, whereas in C#9 it was much longer and more complex, especially if the element was several levels deep inside the main object.
public record Subtype(int Age);public record Person(string Name, Subtype subtype);public void Example(){ Person person = new Person("Ivan", new(29)); string result = ExpressionCsharp10(person);}private static string ExpressionCsharp10(Person person) => person switch { { subtype.Age: >= 18 } => "is an adult", { subtype.Age: < 18 } => "is a minor", _ => throw new NotSupportedException() };//This example is from c#9, very verboseprivate static string ExpressionCsharp9(Person person) => person switch { { subtype: { Age: >= 18 } } => "is an adult", { subtype: { Age: < 18 } } => "is a minor", _ => throw new NotSupportedException() };
9 - Records improvements
Throughout my posts I've always recommended using records when possible, because I'm a huge fan of immutability, and I'm glad to see the enhancements Microsoft has brought to records in the language.
9.1 - Simplifying record definitions in C# 10
The first improvement is that you probably already noticed from the post: you no longer need to declare the properties; just include the constructor in the method definition in a single line and the compiler will understand that they're properties.
public record Person(string Name, int Age);public void Example(){ Person person = new("Ivan", 28); Console.WriteLine($"The name is: {person.Name}");}
As you can see, it's easy to access the properties.
9.2 - Record structs in C# 10
By default, when you define a record
, it becomes a class after compiling, but from this new C# 10 version you can define record structs
by simply indicating it:
public record struct Grades(int Math, int English);
9.3 - Sealed ToString in records
By default, all objects have a .ToString()
method, which, for example, is used when doing string interpolation
. In the real world, overriding this method is not uncommon:
public record Person(string Name, int Age){ public override string ToString() { return $"The name is: {Name} and the age is: {Age}"; }}public void Example2(){ Person person = new("Ivan", 28); Console.WriteLine(person);}
Now you can mark this method as sealed (see sealed), so if you have a type that implements your record
, you won't be able to override the .ToString()
method:
public record Person(string Name, int Age){ public sealed override string ToString() { return $"The name is: {Name} and the age is: {Age}"; }}public record Student : Person{ private Grades Grades; public Student(string name, int age, Grades grades) : base(name, age) { Grades = grades; } //gives an error and doesn't compile public override string ToString() { return $"The name is: {Name} " + $"and the grade in math is {Grades.Math}"; }}
Since the Person
type has the .ToString()
method sealed, the code won't compile because the Student type is trying to override .ToString()
.
10 - String interpolation in constants
Not much to say here — the name says it all. Since C# 10 you can now do string interpolation
with constants, which wasn't possible before, so now you can define the following:
public const string Name = "Netmentor";public const string Greeting = $"Hola {Name}!";//This is how it was done in c# 9public static readonly string GreetingCsharp9 = $"Hola {Name}!";
Conclusion:
In this post we’ve covered the improvements in C# 10 compared to C# 9.
If there is any problem you can add a comment bellow or contact me in the website's contact form