C# 10 New Features: A Comprehensive Guide with Code Examples

As a programming language, C# has gone through several iterations, and with each new version, we see new features and enhancements that make programming with it even better.
C# 10, the latest language version, is no exception.

In this article, we will explore the new features of C# 10 and provide examples of how to use them in your code.

C# 10 New Features
C# 10 New Features

C# 10 is supported by .NET 6 and newer framework versions.
You can download Visual Studio 2022, which comes with the .NET 6 SDK.

C# 10 New features

The following is a list of some of the new features in C# 10.

Global using directives

C# 10 introduces a new feature called global using directives. This feature allows you to specify using directives applied to all files in a project. It can be useful if you have a set of commonly used namespaces you want to include in all your files without adding them manually on the top of each C# file.

For example, if you want to use the System namespace in all your files, you can add a global using directive in your project file like this:

It is advised that you keep your global namespaces in a separate file. We have created a GlobalUsing.cs file, which looks like this:

// GlobalUsings.cs
global using System;
global using System.Collections.Generic;
global using System.Linq;
global using System.Threading.Tasks;
global using Microsoft.AspNetCore.Mvc;

Implicit Global Usings

Implicit usings available in .NET 6/ C# 10 are used to add common global using directives for the specific types of projects.

Developers must set the ImplicitUsings attribute in the .csproj file to allow implicit using.
When you enable the ImplicitUsings property, it imports a number of common namespaces from the .NET class library and makes them available for usage in C# files.

<ImplicitUsings>enable</ImplicitUsings>
C# 10 new ImplicitUsings feature
C# 10 Implicit Usings

When you create a new .NET 6 project with Visual Studio 2022, this new property is enabled by default:

The implicit using is a hidden auto-generated file that declares global using statements behind the scenes in your obj/Debug/net6.0 folder. The name of the file is “ProjectName.GlobalUsings.g.cs“

In my case, when I open this file, I find the following content inside:

// <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;

In the following example, we created the console application in 64-bit Visual Studio 2022. You can see that it allows printing the message without importing any (using) directives.

Here’s an example of a Program.cs file created in C# 10

C# 10 New Console application without main method
C# 10 New Console application without main method

Note: Starting with C# 9, you don’t have to explicitly include the Main method in a console application project. Instead, the compiler produces a class and the Main method as an entry point for the application.

Record structs

Record structs are a new feature introduced in C# 10 that allows you to create value types that behave like reference types.

Record structs are immutable by default, meaning you cannot modify their properties once they are created. They also have a built-in GetHashCode and Equals method, making them ideal for data structure use.

Records are implicitly inherited from the System.ValueType .

Structs already have value equality, so you can compare them based on their value. Record structs now have the == operator and the IEquatable<T> interface.

  • Performance: Record Structs include a special implementation of IEquatable <T> and a ToString() override method to prevent performance issues of reflection.
  • Limitation: Record struct parameters cannot use ref, out or this modifiers (but in and params are allowed).
  • Differences: The only major difference between a regular record and a record struct in C# 10 is that a regular record passes by reference from function to function, whereas a record struct is copied by its values.
  • Mutable: By default, the properties of the record struct are mutable or changeable (get/set), whereas the properties of the record class are immutable. Even so, a readonly record struct in C# 10 can be declared that satisfies the semantics of the record class and is immutable.

You can define record struct types in C# 10 and later by using either positional parameters or standard property syntax:

The record structs can be Positional with a default constructor that declares public members implicitly:

// Positional syntax for record struct
    public record struct Person(string FirstName, string LastName);
// Person person = new Person { FirstName = "Shekh", LastName = "Ali"};

You may also use the readonly modifier to make a record struct immutable.

public readonly record struct Person(string name, int age);

Mutable property of record type in C# 10

In previous versions of C#, record types were immutable by default, which meant that you could not change their properties once they were created. C# 10 introduces a new feature that allows you to mark properties in a record as mutable.


To mark a property as mutable, you can use the init keyword like this:

public record Person(string FirstName, string LastName)
{
   public string Nickname { get; init; }
}

Record type with positional parameters

public record struct Point(int X, int Y);

Example: record struct in C# 10

An example of a record struct in C# 10 can be seen below.

C# 10 record struct example
C# 10 record struct example

File-scoped namespace declaration

C# 10 introduces a new feature called file-scoped namespace declarations. This feature allows you to declare a namespace limited to a single file.

In C # 10, a new form of namespace declaration is available. You can now include the file-scoped namespace as a statement, followed by a semicolon “;” without the curly braces {}.

For example, you can declare a file-scoped namespace like this:

// File-scoped namespace declaration
   namespace MyConsoleApp;

There can only be one file-scoped namespace declaration, which must come before any type. It reduces the code’s complexity and eliminates a level of nesting.
The new syntax for declaring File-scoped namespaces saves both horizontal and vertical space.

Extended property patterns

In C# 10, you can now use extended property patterns to match objects based on their properties and their values. It means that you can use more complex expressions to match objects.

For example, you can use the following code to match an object that has a property called Name and a property called Age that is greater than 18:

Example 1:

public record Person(string FirstName, string LastName, int Age);

var person = new Person("Shekh", "Ali", 25);

if (person is { FirstName: "Shekh", Age: > 18 })
{
   Console.WriteLine("This person is over 18 years old");
}

Extended property patterns are included in C# 10 to make it much simple and easier to access nested property values in patterns.
If we add an Address to the Person record, we may pattern match in both of the following ways:

Example: Extended property patterns

namespace MyApp;

class Address
{
    public string City { get; set; }
}

internal class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public Address Address { get; set; }
}

public class Program
{
    public static void Main()
    {
        var obj = new Person
        {
            Name = "Shekh",
            Age = 29,
            Address = new Address { City = "Delhi" }
        };

        Console.Write($" Name : {obj.Name} Age: {obj.Age}");

        if (obj is Person { Address: { City: "Mumbai" } })
            Console.Write($"City: { obj.Address.City}");

        if (obj is Person { Address.City: "Delhi" }) // Extended property pattern
            Console.Write($" City: { obj.Address.City}");
        Console.ReadKey();
    }
}
C# 10 Extended property patterns
C# 10 Extended property patterns

Improvements of structure types

C# 10 introduces several improvements to structure types. One of the main improvements is that you can now use the ‘with’ keyword to create a new instance of a structure with updated values.

The following example shows the result of a with expression returns a new instance with the updated value.

C# 10 with expressions on structs and anonymous types
C# 10 with expressions on struct

Sealed modifier on ToString() in record types

In previous versions of C#, record types automatically generated a ToString() method that provided a string representation of the object. In C# 10, you can now mark this method as sealed, which prevents derived classes from overriding it.

The following example demonstrates a record called Person that implements an override of the ToString() method with the sealed keyword.

The sealed keyword prevents the compiler from synthesizing a ToString() method for any derived record types.
The Employee record inherits from Person and tries to override the ToString() method, resulting in the following compilation error:

Example:

public record Person
{
    public string FirstName { get; init; }

    public string LastName { get; init; }

    public sealed override string ToString()
    {
        return $"FirstName: {FirstName} LastName{LastName}";
    }
}

public record Employee : Person
{
   // Trying to overide ToString() in the derived record type.
  // Error CS0239 'Employee.ToString()': cannot override inherited member 
  // 'Person.ToString()' because it is sealed.

    public override string ToString() 
    {
        return $"FirstName: {FirstName} LastName{LastName}";
    }
}
Sealed modifier on ToString in record types
C# 10 Sealed modifier on ToString in record types

Null Parameter Checks in C# 10

C# 10 introduces a new feature that allows you to perform null checks on method parameters. This feature can help you avoid null reference exceptions in your code.
For example, you can use the following code to check if a parameter is null:

void ValidateMail(string email)
{
    if (email == null)
    {
        throw new ArgumentNullException("email");
    }
}

In the code above, we have a function with only one parameter, “email.” If the parameter email is null, we must throw an ArgumentNullException.

In C# 10, to simplify the above problem  ArgumentNullException.ThrowIfNull  and  !!  two exclamation marks next to the parameter was introduced.

Example 1: C# 10 Null checks parameter ( ThrowIfNull )

string? email =null;
ValidateMail (email);

void ValidateMail(string email)
{
    //if(email == null)
    //{
    //    throw new ArgumentNullException ("email");
    //}

    ArgumentNullException.ThrowIfNull(email);
    Console.WriteLine($"Email : {email}");
}
C# 10 Null checks parameter ThrowIfNull
C# 10 Null checks parameter ThrowIfNull

Example 2: Null checks with two exclamation marks

void PersonDetail(Person personObject !!)
{ 
    // Code here
}

The personObject is automatically checked for null in the above code, and if it is null, the AgrumentNullException is thrown.

References: devblogs- Welcome to C# 10, MSDN- What’s new in C# 10

Thank you for taking the time to read the blog, if you find it interesting, please like, comment, and share it with others. Thanks.

FAQs:

Q: Is it possible to use record types with mutable properties?

Yes, in C# 10, you can mark properties in a record as mutable using the init keyword.

Q: What is the benefit of using file-scoped namespace declarations?

File-scoped namespace declarations can help you organize your code and make it easier to understand.

Q: Can you explain how to use extended property patterns in C# 10?

Extended property patterns allow you to match objects based on their properties and their values. You can use complex expressions to match objects.

You might also like:

Let others know about this post by sharing it and leaving your thoughts in the comments section.

Shekh Ali
4 1 vote
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments