C# has been through a lot of change in the last decade. From open-sourcing the language to a new cross-platform framework to yearly releases instead of 3-year iterations. Meanwhile, new languages, new frameworks, and new paradigms have taken off in the software industry, including the rise in popularity of Node.js , TypeScript, Kotlin, Rust, Go, and Python. Through it all, C# and .NET stayed relevant and popular . In this blog post, we’ll glimpse the day-to-day activity and challenges the C# language design team faces. You’ll see what they’re working on in the upcoming C# 12 version and beyond that, in future versions.

Staying true to open source culture, the C# design team moved its entire activity to a public GitHub repo . Inside, you can see language specs, proposals for new features, issues, suggestions, questions, and the valuable language design meeting summaries (LDMs). It’s fascinating to dive a little into the content of these meetings and see the kind of discussions they are having. These guys have to consider things like compiler limitations, memory and performance impact, syntax challenges, and backward compatibility with existing C# code. But I’ll leave that to them, and we’ll talk just about the result. Here are a few of the most interesting upcoming proposals for C# 12 and beyond.

1. Primary constructors

Primary constructors is the main feature of C# 12. It’s almost certainly going to get into the released version, and it looks very good in a demo. The main idea is to have thinner syntax for the use case of receiving arguments in a constructor and saving them as private members. Here’s an example of the new syntax:

public class Person (string firstName, string lastName)
{
    string FullName => `{firstName} {lastName}`; 
}

This will be the same as writing this:

public class Person
{
	private string firstName;
	private string lastName;
	
	public Person(string firstName, string lastName)
	{
		this.firstName = firstName;
		this.lastName = lastName;
	}
    
    string  FullName => `{firstName} {lastName}`;
}

If the syntax looks familiar, you’re right, it is very similar to records that were introduced in C# 9. But there are differences:

  • Records are intended to be immutable, whereas classes with a primary constructor will have the same conceptual usage as regular classes (with a bit less code).
  • In records, constructor arguments are saved as public immutable properties, whereas in classes, they will be saved as private fields.
  • You can use the with keyword in records to create mutated copies, but not in classes with primary constructors.

That’s the gist of it. Although, there are subtleties and open questions. Like what happens when you want multiple constructors? How does it work with base classes? How does this work for property initializers? And on and on. Language design isn’t as easy as it sounds. You can read all about it in the official proposal and see the language design meeting (LDMs) summaries LDM-2022-10-17 LDM-2023-01-18 . And you can even try it yourself. Yes, it’s already implemented, and a branch with the implementation is available in SharpLab to play with.

If you aren’t familiar with SharpLab , it’s a C# interactive online environment, just like JSFiddle is for JavaScript. It means you can write C# code in the browser, run it, see the IL code it produces, and more fun stuff.

2. Semi-auto-properties (and the field keyword)

There was a lot of language development around properties, but there seems to be no end to features you can add to them. The proposal for semi-properties saves you the trouble of creating a backing field. Instead, you’ll be able to use a new keyword (field is being proposed) to access that member. Here’s an example with the syntax:

public class Point
{
    public int X { get { return field; } set { field = value; } }
    public int Y { get { return field; } set { field = value; } }
}

This is equivalent to regular auto-properties

public class Point
{
    public int X { get; set; }
    public int Y { get; set; }
}

And this code is generated under the hood for both:

public class Point
{
    private int __x;
    private int __y;
    public int X { get { return __x; } set { __x = value; } }
    public int Y { get { return __y; } set { __y = value; } }
}

With semi auto properties, I can write something like this:

public class LazyInit
{
    public string Value => field ??= ComputeValue();
    private static string ComputeValue() { /*...*/ }
}

Check out the proposal . And here is a SharpLab branch to play with.

3. List-pattern-matching extended to IEnumerables

The advances for list pattern matching in C# 12 aren’t as exciting on their own, but this is a lesser-known feature that’s interesting to show regardless. The idea is powerful pattern-matching capabilities for lists and arrays, including wildcards and discards. In C# 12, it’s extended from lists and arrays (introduced in C# 11 ) to any IEnumerable collection. Here are a few examples:

var arr = new[] {1,2,3}; // works with arrays and lists (e.g new List<int>() {1,2,3})
Console.WriteLine(arr is [1,2,3] ); // true (list pattern)
Console.WriteLine(arr is [1,_,3] ); // true (with discard)
Console.WriteLine(arr is [1,2] ); // false
Console.WriteLine(arr is [1, ..] ); // true (slice pattern)

switch (arr) {
    case [..,3]:
        ...
        break;
    case [_,2,_]:
        ...
        break;
    default:
        ...
        break;
}

var y = arr switch {
    [1]=> "foo",
    [1,2,_] => "bar",
    _ => "zing" // default   
};

// Following doesn't work in C# 11, but will (hopefully) work in C# 12
var rng = Enumerable.Range(1,3);// 1,2,3
Console.WriteLine(rng is [1,2,3] ); // true
Console.WriteLine(rng is [1,_,3] ); // true
Console.WriteLine(rng is [1,2] ); // false
Console.WriteLine(rng is [1, ..] ); // true

This proposal is probably going to be released in the upcoming C# 12 version. And it also has a working branch on SharpLab.

4. Pure Unions and Discriminating Unions

You might know union types from other languages like TypeScript. A union is a way to represent a variable that can be one of multiple types. For example, in TypeScript, you can write the following:

type BoolOrNumber = boolean | number;
const a: BoolOrNumber = false; // ok
const b: BoolOrNumber = 5; // ok
const c: BoolOrNumber = 'asdf'; // error

function Foo(x: BoolOrNumber) {
	console.log(typof x); // will be either "boolean" or "number"
}

In C#, there’s a proposal to do just that. The proposed syntax is similar to the one in TypeScript:

A | B myUnion1 = new A(); // ok
A | B myUnion2 = new B(); // ok

public void Foo(A | B union) // also OK
{
    if(union is A a)
    {
        ...
    }
    if (union is B b)
    {
        ...
    }
}

This kind of union is called a “Pure Union” or a “Type Union”. The proposal, however, doesn’t include a syntax to alias a union type as a new type. e.g something like type BoolOrNumber = boolean | number isn’t proposed, but this is compensated for in the other proposed union type. The other union type is called a “Discriminating Union” or a “Tagged Union”. The idea here is that each union option will have a specific identifier (aka tag) and you could pattern-match and cast with this identifier. There’s an active proposal using the syntax enum class. Here’s an example code:

enum class Shape
{
    Rectangle(double Width, double Length),
    Circle(double Radius),
    CircleWithFixedRadiusFive, // single instance
}

With the above declaration of Shape, you could write the following code:

Shape s = new Shape.Rectangle(10, 3.5);
Shape.Circle c = new Shape.Circle(12.3);
Shape f = Shape.CircleWithFixedRadiusFive;

switch (Shape shape) {
	case shape is Rectangle rect:
		...
		break;
    case shape is Circle circle:
    	...
    	break;
    case shape is CircleWithFixedRadiusFive:
    	...
    	break;
}

This is just a proposal, of course, so the syntax might be entirely different when it’s eventually released. For example, here’s another proposal I found that suggests an entirely different syntax using enum struct:

enum struct S
{
    Case1(A),
    Case2(A, B)
}

In this proposal, Case1 and Case2 are tags that represent the Id of the type, whereas the actual types (A and B) are declared outside of the union type. With this proposal, you could write something like this:

if (x is S.Case1(A a))
{
	...
}
if (x is S.Case2(A a, B b))
{
	...
}

var y = x switch {
    S.Case1(A a) => ...
    S.Case2(A a, B b) => ...
}

Unions is the one language feature in this list that’s probably not going to be released in version 12. But I’m looking forward to them in the future. They come in pretty handy in TypeScript.

If you’d like to get into a rabbit hole, interesting though it might be, you can read the proposals and some of the language design meeting (LDM) summaries. If you’ve survived and understood them, consider applying to be a C# language designer at Microsoft.

Finishing up

Many other smaller features are going to be released in C# 12. If you want to find out which ones, you can look at the list of branches in SharpLab that are marked as “C# Next” (disclaimer: I don’t know how this list came together, but it looks authentic).

C# Next branches on SharpLab

If you’re interested to find out more about upcoming C# features, you can start by looking at the active language proposals . There are just 32 of them, though it’s not exactly easy to read. Besides the proposals, you can look at the weekly meeting summaries and notes ,. Finally, there’s a mountain of discussion threads and language ideas that you can check out. Since it’s open-sourced, you can even contribute yourself. Maybe you have a killer language feature to suggest or point out an issue in an existing proposal.

Oh, and there’s a way to install the upcoming C# version on your own computer and play with it. It involves downloading a preview Visual Studio version and some other steps, but it’s not too hard. Here’s a video with the instructions .

That’s it for this one, happy coding.