C# 10 Updates

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.

 

 

 

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();

minimal api result

 

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.

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.

 

This post was translated from Spanish. You can see the original one here.
If there is any problem you can add a comment bellow or contact me in the website's contact form

Uso del bloqueador de anuncios adblock

Hola!

Primero de todo bienvenido a la web de NetMentor donde podrás aprender programación en C# y .NET desde un nivel de principiante hasta más avanzado.


Yo entiendo que utilices un bloqueador de anuncios como AdBlock, Ublock o el propio navegador Brave. Pero te tengo que pedir por favor que desactives el bloqueador para esta web.


Intento personalmente no poner mucha publicidad, la justa para pagar el servidor y por supuesto que no sea intrusiva; Si pese a ello piensas que es intrusiva siempre me puedes escribir por privado o por Twitter a @NetMentorTW.


Si ya lo has desactivado, por favor recarga la página.


Un saludo y muchas gracias por tu colaboración

© copyright 2025 NetMentor | Todos los derechos reservados | RSS Feed

Buy me a coffee Invitame a un café