avatarAtakan Korez


Every new feature coming with C# 12

The evolution of C# is a continuous process that drives the language to new heights of expressiveness, productivity, and performance. With the release of C# 12, developers are set to experience a range of new features and enhancements that will shape the future of C# programming. This blog post will delve into the key features of C# 12, highlighting their significance and providing insights into their practical applications. Let’s start!

Primary Constructors

To grasp the essence of primary constructors, let’s first revisit the existing feature introduced in C# 9 → records.

In C# 9, developers gained the ability to create records, essentially classes with default implementations for common scenarios. For instance, declaring a record ‘user’ with a ‘name’ property allowed for easy instantiation and utilization:

public record User(string Name);

// Usage
var user = new User("Atakan");

This record feature streamlined the process of creating classes with properties. However, in C# 12, primary constructors extend this convenience to traditional classes. Consider the following example, where a class User is declared with name and age parameters:

public class User(string name, int age)
    public string Name// Name used directly in function members
        get => name;
        set => name= value ?? throw new ArgumentNullException(nameof(name));

    public int Age {get; set;} = age; // name used for initialization

In the above example, the constructor parameters name and age can serve different purposes for class.

The primary constructor feature in C# 12 offers flexibility by allowing its usage in various contexts. The primary constructor streamlines code and enhances code maintainability, whether applied to properties, methods, or constructors. For instance, the primary constructor parameter name can be employed within a method:

// Primary Constructor
public class User(string name)
    public void Method1()
      /* Utilizing the 'name' parameter as a private field - not readonly 
        So, you can assign a value to it */ 
        name = "Atakan";      

Primary constructors in C# 12 mark a significant step towards code simplification and improved developer experience. It is important for developers to pay attention to the following points when using them:

  • Primary constructor parameters may not be stored if they aren’t needed
  • Primary constructor parameters aren’t members of the class. For example, a primary constructor parameter named param can’t be accessed as this.param
  • Primary constructor parameters can be assigned to
  • Primary constructor parameters don’t become properties, except in record types

Collection Expressions

C# 12 release introduces a groundbreaking feature known as Collection Expressions. This enhancement revolutionizes the initialization of arrays and other data structures, providing developers with a more concise and expressive syntax.

Traditionally, declaring and initializing an array in C# involved verbose syntax, as exemplified below:

int[] numbers = new int[] { 1, 2, 3, 4, 5, 6, 7, 8};

The collection Expressions feature enables developers to achieve the same result with a more concise syntax:

int[] numbers = [1, 2, 3, 4, 5, 6, 7, 8];

This syntax not only enhances readability but also brings uniformity to array initialization. However, it’s crucial to note that explicit type declaration is now mandatory which means you can’t use var keyword with this new feature!

The power of Collection Expressions extends beyond arrays, encompassing various data structures. For instance, initializing a span or a jagged 2D array becomes more intuitive:

Span<int> span = ['a', 'b', 'c'];
int[][] jaggedArray = [[1, 2, 3], [4, 5, 6]];

Bonus → Empty Dictionary Initialization

While not officially part of the planned C# 12 features, developers gain the ability to initialize an empty dictionary using angle brackets.

Dictionary<string, string> dic = [];

Ref Readonly Parameters

The RefReadonly parameters are a new feature in C# 12 that allows you to pass references to data that should not be modified. This can improve performance and make your code more readable. When you pass a reference to a variable as a ref readonly parameter, the compiler ensures that the variable’s value cannot be changed inside the method. This means that you can safely pass the reference to a method without worrying about the method modifying the variable’s value.

public void Swap(ref readonly int x, ref readonly int y)
    int temp = x;
    x = y;
    y = temp;

In blow example, the Swap method takes two ref readonly parameters: x and y. The ref readonly modifier indicates that the method cannot modify the values of x and y. Inside the method, the values of x and y are swapped using a temporary variable. This ensures that the original values of x and y remain unchanged.

Default Lambda Parameters

The upcoming C# 12 release introduces a highly anticipated quality-of-life feature known as Default Lambda Parameters. This feature enhances the flexibility of lambda expressions by allowing developers to set default values for parameters, streamlining code, and simplifying invocations.

Consider a scenario where a lambda expression defines a parameter, such as in the example below:

var ageLambda = (string name, int age) => $"{name} is {age} years old";

Traditionally, when invoking this lambda, both parameters must be explicitly provided:

string result = ageLambda("Atakan", 40);

Default Lambda Parameters enable developers to set default values for parameters within the lambda expression:

var ageLambdaWithDefault = (name = "Atakan" , age = 40) => $"{name} is {age} years old";

string result = ageLambdaWithDefault(); 
// I can also specify any value, for e.g. --> ageLambdaWithDefault("Korez", 35); 

Alias Any Type

This feature challenges traditional constraints by relaxing rules on the usage of the using alias directive, particularly in the context of pointer types, array types, and more.

Alias Any Type essentially redefines the possibilities of the using alias directive in C# 12. Previously restricted in certain contexts, such as pointer types and array types, Alias Any Type breaks these barriers, ushering in a new era of flexibility in type definitions.

One illustrative example of Alias Any Type’s transformative power is the creation of a 2D point type. With the following declaration:

using Point2D = int X, int Y;

Developers can now use Point2D as a concise, tuple-like structure, reminiscent of a struct:

var point = new Point2D(3, 7);

This opens up possibilities for implicit conversions, allowing developers to seamlessly integrate Point2D into their codebase.

Inline Arrays

In the latest strides towards optimizing performance, C# 12 introduces a feature known as Inline Arrays. Originally utilized by the runtime team at Microsoft and library authors, this feature is now being made public to benefit a broader spectrum of developers.

The primary aim is to enhance the performance of applications leveraging .NET or associated libraries. So, Inline Arrays provide a performant solution through fixed-size stack-allocated buffers. An inline array is declared similar to the following struct:

public struct Buffer
    private int _element0;

You use them like any other array:

var buffer = new Buffer();
for (int i = 0; i < 10; i++)
    buffer[i] = i;

foreach (var i in buffer)

Inline Arrays address performance concerns by offering developers a fixed-size stack-allocated buffer. The key objective is to align the performance characteristics of a stack-allocated buffer with those of an unsaved fixed-size buffer. While the inner workings may delve into intricate details of performance optimization, developers can leverage Inline Arrays to enhance the speed and efficiency of their code.

Experimental Attribute

This attribute provides a mechanism for developers to label features as experimental or risky, signaling potential changes or removal in future updates. It serves as a marker for features that are considered experimental or risky. By applying this attribute to a specific feature or code block, developers convey that the associated functionality is subject to evaluation and may undergo changes or removal in future updates.

To designate a feature as experimental, developers can utilize the Experimental Attribute by providing a diagnostic ID. Here’s a simple illustration:

public class ExperimentalFeature
    // Feature implementation

When attempting to use the marked feature, developers receive a compilation error, emphasizing the experimental nature of the code:

// Compilation error due to experimental feature
var instance = new ExperimentalFeature(); 

To proceed with using an experimental feature, developers must explicitly suppress the compilation error. This intentional step signals an awareness of the risks associated with the feature:

#pragma warning disable RiskyId
var instance = new ExperimentalFeature();
#pragma warning restore RiskyId


C# 12 introduces a revolutionary feature called Interceptors, offering developers the ability to intercept and modify method invocations during compile time. Although in preview, this feature opens new doors for code manipulation without directly altering the source.

Interceptors allow developers to intercept method invocations and replace them with custom code during compile time. This powerful capability introduces a paradigm shift in code manipulation, enabling developers to enhance or modify behavior without directly modifying the original source code.

Consider the following example class with two methods:

public class Example
    public void MethodOne() => Console.WriteLine("Hello from method one");
    public void MethodTwo(string name) => Console.WriteLine($"Hello from method two, {name}");

Now, let’s create an Interceptor to modify the behavior of MethodOne:

[InterceptsLocation("C:\\Path\\To\\Your\\File\\Example.cs", 6, 9)]
public static class Interception
    public static void InterceptMethodOne(this Example example)
        Console.WriteLine("Hello from Interceptor");

In this example, the InterceptMethodOne extension method intercepts the original MethodOne at the specified location in the source code.

To use the Interceptor, no modifications are needed in the original class. Simply instantiate the Example class and call the methods as usual:

class Program
    static void Main()
        Example example = new Example();
        example.MethodOne(); // Outputs: Hello from Interceptor
        example.MethodTwo("Atakan"); // Outputs: Hello from method two, Atakan

Interceptors can also handle method parameters. For instance, modifying MethodTwo using an Interceptor:

[InterceptsLocation("C:\\Path\\To\\Your\\File\\Example.cs", 7, 9)]
public static class Interception
    public static void InterceptMethodTwo(this Example example, string name)
        Console.WriteLine($"Hello from Interceptor, {name}");

Now, invoking MethodTwo triggers the Interceptor:

class Program
    static void Main()
        Example example = new Example();
        example.MethodTwo("Atakan"); // Outputs: Hello from Interceptor, Atakan


C# 12 marks a significant milestone in the evolution of the C# language, bringing a plethora of new features and enhancements that empower developers to write more expressive, maintainable, and performant code. From the streamlined object initialization with primary constructors to the concise array creation with inline arrays, C# 12 introduces a range of features that enhance productivity and code quality.

I encourage you to delve into the exciting world of C# 12 and explore the boundless opportunities it presents for crafting exceptional code. As you embark on this journey of discovery, remember that the C# community is always here to support and guide you. Together, let’s embrace the power of C# 12 and shape the future of software development.

If you found this blog post helpful, please give it a clap. Thanks for reading…

C Sharp Programming
C Sharp Language
Csharp 12
C Sharp Development
Dot Net Framework
Recommended from ReadMedium