New features in C#12

C# 12 which was released as part of .NET 8.0 on 14th November 2023 has some improvements as well as new features. This article will list down the various features along with examples.

1. Primary constructors :

Primary constructors can be created for any struct or class. It is basically a new syntax that allows class initialization by allowing parameters to be directly defined in the class declaration.

Class Initialization : The example below shows a Movie class along with primary constructor.

public class Movie(string name, DateTime releaseDate, int ratings, string director)
{
    public string Name { get; } = name;
    public DateTime ReleaseDate { get; } = releaseDate;
    public int Ratings { get; } = ratings;
    public string Director { get; } = director;

    public string GetDescription()
    {
        return $"{Name} is directed by {Director} and was released on {ReleaseDate.ToShortDateString()}. It has a rating of {Ratings}.";
    }
}

public class Program
{
    public static void Main()
    {
        var movie = new Movie("Inception", new DateTime(2014, 11, 17), 9, "Christopher Nolan");
        Console.WriteLine(movie.GetDescription());
    }
}

Dependency injection in primary constructors : We can take advantage of Primary constructors while implementing dependency injection in a typical ASP.NET core 8 web api application service. The existing way to implement dependency injection in a typical service let’s say MovieService was done in the following manner :

public class MovieService
{
    private readonly IMovieRepository _movieRepository;
    public MovieService(IMovieRepository movieRepository)
    {
        _movieRepository = movieRepository;
    }
    public async Task<IEnumerable<Movie>> GetAll()
    {
        return await _movieRepository.GetAll();
    }
}
public class Movie
{
    public string? Name { get; set; }
    public DateTime ReleaseDate { get; set; }
    public int Ratings { get; set; }
    public string? Director { get; set; }

}

Now rewriting the code using the new syntax of primary constructors, the code becomes cleaner.

public class MovieService(IMovieRepository movieRepository)
{
    public async Task<IEnumerable<Movie>> GetAll()
    {
        return await movieRepository.GetAll();
    }
}

public class Movie
{
    public string? Name { get; set; }
    public DateTime ReleaseDate { get; set; }
    public int Ratings { get; set; }
    public string? Director { get; set; }

}

2. Collection expressions

Collection expressions introduces a new syntax to create common collection values. Also appending other collections into these values is possible using a spread operator .... Perhaps Javascript spread operator has inspired C# community to add this. 🙂

Several collection-like types can be created in new way. These types are:

The following examples show uses of collection expressions:

// Create an array:
int[] arrOne = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// Create a span
Span<char> spVowels = ['a', 'e', 'i', 'o', 'U'];

// Create a list:
List<string> lstMovieParts = ["one", "two", "three"];

// Create a jagged 2D array:
int[][] arrTwoD = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];

// Create a jagged 2D array from variables:
int[] arrRowA = [1, 2, 3];
int[] arrRowB = [4, 5, 6];
int[] arrRowC = [7, 8, 9];
int[][] arrJaggedTwoDfromVariables = [arrRowA, arrRowB, arrRowC];

The use of  spread operator.. in a collection expression is shown below :

int[] fibSeriesOne = [0, 1, 1];
int[] fibSeriesTwo = [2, 3, 5];
int[] fibSeriesTwoThree = [8, 13, 21];
int[] fibSeriesTwoFour = [34, 55, 89];
int[] fibSeries = [.. fibSeriesOne, .. fibSeriesTwo, .. fibSeriesTwoThree, .. fibSeriesTwoFour];
foreach (var fibSeriesNo in fibSeries)
{
    Console.Write($"{fibSeriesNo}, ");
}
// output:
// 0, 1, 1,2, 3, 5,8, 13, 21,34, 55, 89

3. Inline Arrays:

The runtime team and library authors utilize inline arrays to enhance the performance of applications. Inline arrays allow developers to create fixed-size arrays within a struct type, offering performance characteristics comparable to unsafe fixed-size buffers. While you may not directly create inline arrays, you can leverage them when they are exposed as System.Span<T> or System.ReadOnlySpan<T> objects through runtime APIs. The example below shows how to create an inline array :

[System.Runtime.CompilerServices.InlineArray(15)]
public struct InlineArrayExample
{
    private int _element;
}

The usage of this inline array declared above is as follows:

var inlineArr = new InlineArrayExample();

for (int i = 0; i < 15; i++)
{
    inlineArr[i] = i;
}

foreach (var individualItem in inlineArr)
{
    Console.Write($"{individualItem} ");
}

4. Optional parameters in lambda expressions

From C# 12 onwards we can provide default values for parameters on lambda expressions. The first example shows how to declare a lambda expression with a default parameter, then call it once using the default and once with two explicit parameters:

var defaultLambdaDemo = (int paramOne, int paramTwo = 1) => paramOne + paramTwo;

Console.WriteLine(defaultLambdaDemo(18)); 
Console.WriteLine(defaultLambdaDemo(18, 3)); 

The output for this will be :

19
21

The second example shows another lambda expression with multiple default parameter:

var defaultLambdaDemoAlt = (string paramOne, string paramTwo = "James",string paramThree ="Moriarty") => paramOne + " " + 
paramTwo + " " + paramThree;

Console.WriteLine(defaultLambdaDemoAlt("Prof"));
Console.WriteLine(defaultLambdaDemoAlt("Prof","James"));
Console.WriteLine(defaultLambdaDemoAlt("Sir", "Leigh","Teabing"));

The output for this will be :

Prof James Moriarty
Prof James Moriarty
Sir Leigh Teabing

5.ref readonly parameters

This is one of the most underrated feature introduced in C# 12.In older version of C#(version 7.2 onwards) we already had in parameter to implement the same feature. But from this version a new keyword ref readonly has been introduced. The addition of ref readonly parameters enables more clarity for APIs that might be using ref parameters or in parameters:

  • APIs created before in was introduced might be using ref even though the parameter/argument isn’t modified in them. Those APIs can be updated with ref readonly. It won’t be a breaking change for functions which call the API. If however we change the ref parameter to in parameter, then it would be a breaking change and all the caller functions have to be modified.
  • APIs that have an in parameter, but logically require a variable. A value expression doesn’t work. 
  • APIs that have ref because they require a variable, but there is no change in the value of the variable.
  • It is a very useful feature when you an object which has many important master data and it is used in almost every sub routines or operations and also you don’t want somebody even by mistake to update it. In such scenario when a developer even by mistake tries to change the value in any sub routine, the code will throw a compilation error.

6. Alias any type

Earlier in C# we had the ability to alias namespaces and named types but now we can create semantic aliases for tuple types, array types, Generic list type, pointer types, or other unsafe types. But we must be also very careful that although most types can support aliasing in C# 12, including nullable value types. However, nullable reference types can’t have aliases. Some examples are shown for demonstration purposes.

Aliasing tuple type :

//alias Tuple type
using Container = (int length, int breadth, int height);

Container smallCargoContainer = (20,4,6);

Console.WriteLine($"Container Dimensions are Length : {smallCargoContainer.length}, Breadth : {smallCargoContainer.breadth}, Height : {smallCargoContainer.height}");

Aliasing Generic list- List<T> :

using FeatureFilms = System.Collections.Generic.List<Movie>;
FeatureFilms GetGoodMovies()
{
List<Movie> movies = new List<Movie>
        {
            new Movie
            {
            Name = "American History X",
            ReleaseDate = new DateTime(1998, 10, 30),
            Ratings = 8,
            Director = "Tony Kaye"
            },
            new Movie
            {
                Name = "The Shawshank Redemption",
                ReleaseDate = new DateTime(1994, 9, 23),
                Ratings = 10,
                Director = "Frank Darabont"
            },
            new Movie
            {
                Name = "The Godfather",
                ReleaseDate = new DateTime(1972, 3, 24),
                Ratings = 10,
                Director = "Francis Ford Coppola"
            },
            new Movie
            {
                Name = "The Dark Knight",
                ReleaseDate = new DateTime(2008, 7, 18),
                Ratings = 9,
                Director = "Christopher Nolan"
            },
            new Movie
            {
                Name = "Hazaaron Khwaishein Aisi",
                ReleaseDate = new DateTime(2005, 4, 15),
                Ratings = 10,
                Director = "Sudhir Mishra"
            },
              new Movie
            {
                Name = "Yeh Jawaani Hai Deewani",
                ReleaseDate = new DateTime(2013, 5, 31),
                Ratings = 7,
                Director = "Ayan Mukerji"
            },
    };
return movies;
}
Console.WriteLine("Details of the movies are as follows: " + Environment.NewLine);

//displaying the list of movies from FeatureFilms alias
GetGoodMovies().ForEach(movie =>
Console.WriteLine(nameof(Movie.Name) + ": " + movie.Name + " "
+ nameof(Movie.Director) + ": " + movie.Director + " "
+ nameof(Movie.ReleaseDate) + ": " + movie.ReleaseDate.ToString("dd-MMMM-yyyy") + " "
+ nameof(Movie.Ratings) + ": " + movie.Ratings + " "
+ Environment.NewLine)
);

public class Movie
{
    public string? Name { get; set; }
    public DateTime ReleaseDate { get; set; }
    public int Ratings { get; set; }
    public string? Director { get; set; }

}

Output is as follows:

Details of the movies are as follows:

Name: American History X Director: Tony Kaye ReleaseDate: 30-October-1998 Ratings: 8 

Name: The Shawshank Redemption Director: Frank Darabont ReleaseDate: 23-September-1994 Ratings: 10 

Name: The Godfather Director: Francis Ford Coppola ReleaseDate: 24-March-1972 Ratings: 10 

Name: The Dark Knight Director: Christopher Nolan ReleaseDate: 18-July-2008 Ratings: 9 

Name: Hazaaron Khwaishein Aisi Director: Sudhir Mishra ReleaseDate: 15-April-2005 Ratings: 10 

Name: Yeh Jawaani Hai Deewani Director: Ayan Mukerji ReleaseDate: 31-May-2013 Ratings: 7 

7.Experimental attribute

Another strange feature(no offense to the person who developed this feature 🙂 ) which got introduced is ExperimentalAttribute. We can mark Types, methods, or assemblies with the attribute System.Diagnostics.CodeAnalysis.ExperimentalAttribute to denote an experimental feature. The compiler will issue a warning if someone accesses a method or type annotated with the ExperimentalAttribute. All types in an assembly marked with the Experimental attribute are experimental.

using System.Diagnostics.CodeAnalysis;
var newExpObj = new ExperimentalAttribDemo();

[Experimental(diagnosticId: "ExperimentalFeature001")]
public class ExperimentalAttribDemo
{ 
    public string? Name { get; set; }
    public int Age { get; set; }
}

In order to use this feature we have to include the namespace System.Diagnostics.CodeAnalysis in line no-1 as shown above. We have to annotate any class, method or type with [Experimental] attribute. In the example we can see that the class ExperimentalAttribDemo has an attribute [Experimental(diagnosticId: “ExperimentalFeature001”)] .If any developer attempts to use /instantiate the class ExperimentalAttribDemo, the compiler will show an error as shown below :

8. Interceptors

Interceptors are an experimental feature in C# 12. Microsoft has mentioned in their site :

Interceptors are an experimental feature, available in preview mode with C# 12. The feature may be subject to breaking changes or removal in a future release. Therefore, it is not recommended for production or released applications.

So I can think Microsoft can mark this feature/assembly with [Experimental] attribute. Although I haven’t checked it if they have done it 🙂 . But based on my experience it’s no use to discuss experimental feature which hasn’t been finalized.

Leave a comment

Create a free website or blog at WordPress.com.

Up ↑