C# 10 new features

C# 10 New Features
C# 10 New Features

In this post, we’ll discuss some new C# 10 features that will help you to write cleaner, more expressive, and faster code.
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.

1. C# 10 Global using directives

The  global  using directive was introduced in C# 10, allowing you to create common namespaces that will automatically be available for use within the entire project. It helps developers to avoid declaring these common namespaces on the top of each C# file.
Adding the global modifier to the using directive applies using to all files in the compilation.
It is advised that you keep your global namespaces in a separate file. We’ve 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;

2. Implicit Global Usings

C# 10 ImplicitUsings: Implicit usings, which are available in .NET 6/C# 10, add common global using directives for the specific types of projects. Developers must set the ImplicitUsings attribute in the .csproj file to allow implicit usings.
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 ImplicitUsings

When you create a new .NET 6 project with Visual Studio 2022, this new property is enabled by default:
The implicit usings 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, the console application was created in 64-bit Visual Studio 2022. Here, 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.

3. C# 10 – Record structs

In C# 9,  record  keyword was introduced as a convenient mechanism to define a reference type with value equality semantics.
One annoying limitation was that records were always reference types, even though value types would have been preferable in several cases. C# 10 now allows you to declare  record struct , which is a huge improvement.
In C# 10, record struct types are value types, just like other struct types. They 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.

    • Performace: To prevent performance issue of reflection, record structs include a special implementation of IEquatable <T>, as well as a ToString() override method.
    • 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 record struct are mutable or changable (get/set) , whereas the properties of 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 primary 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

Records are designed to work with immutable data models, although they are not mandated to be immutable.
Immutability can be useful in situations where you don’t want to make any changes to a record variable/object once it’s been created.
Although non-destructive mutation is allowed, an immutable record type is thread-safe and cannot be mutated or changed after it is created. Only constructors can be used to initialize record types.

record type with positional parameters

public record Person(string Name, int Age);

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

4. C# 10 – File-scoped namespace declaration

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 {}.

// File-scoped namespace declaration
   namespace MyConsoleApp;

There can only be one file-scoped namespace declaration, and it must come before any type. This 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.

5. Extended property patterns

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

6. Improvements of structure types

In C# 9.0, a left-hand operand of a with expression must be of a record type. Starting with C# 10, a left-hand operand of a with expression can also be of a structure type or an anonymous type.

with expressions on structs: Any record type has the copy constructor. That’s a constructor with only one parameter of the containing record type. It creates a new record instance with the state of its argument. When a with expression is evaluated, the copy constructor is called to create a new record instance based on the original record.

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

7. Sealed modifier on ToString() in record types

Starting with C# 10, you can add a sealed modifier when overriding ToString() in a record type, which prevents the compiler from synthesizing a ToString() implementation for any derived record types. The sealed ToString() method ensures that all the derived record types use the ToString() method defined in the common base record type.

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

8. Null Parameter Checks in C# 10

Null Parameter Checking, a new feature in C# 10, can benefit developers by making code more flexible and avoiding errors like “Object Reference Not Set to an Instance of an Object.”

To check for a Null Reference, we currently have to write C# code that looks like this:

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.

Articles to Check Out

Leave a Reply