What’s new in C# 14
With .NET 10 around the corner, we’re also getting the C# 14 language alongside it. Ah yes, .NET 10 and C# 14, because nothing says “we’ve got this under control” more like completely unrelated version numbers. How well, here we are!
In this blog post, I’ll highlight some of the new features coming with C# 14 and share my thoughts on whether they’re actually useful or just nice-to-haves. 🧠
The big news: Extension members
Let’s start big with extension members. C# 14 introduces not only a new way to define extension methods, but also a new extension type: extension properties.
For a very long time now, we have been able to write extension methods, like this one for example:
public static class EnumerableExtensionsOld
{
public static IEnumerable<TSource> TakeFiveFirstItems<TSource>(this IEnumerable<TSource> source)
{
return source.Take(5);
}
}
The problem with this syntax was that it didn’t provide an easy way to also define extension properties, since these extensions are written in a method-like structure.
That’s the main reason they needed to come up with a new syntax. Basically what they did is, they lifted up the type, in this case IEnumerable<TSource>
, to its own block definition. Which makes the extension method definition already a whole lot simpler like you can see here:
public static class EnumerableExtensions
{
extension<TSource>(IEnumerable<TSource> source)
{
public IEnumerable<TSource> TakeFiveFirstItems()
{
return source.Take(5);
}
}
}
There will also be a new reserved keyword: extension 💡
But now with this syntax, we can easily add extension properties:
public static class EnumerableExtensions
{
extension<TSource>(IEnumerable<TSource> source)
{
public bool IsEmpty => !source.Any();
}
}
This is purely compile-time syntactic sugar. You call it like a property in C#, but at the IL level, it’s just static methods, named
get_PropertyName
andset_PropertyName
, with the instance passed as the first parameter.
That’s pretty cool! 🤘🏻 For the first time, we will be able to write C# like this:
var myCollection = Enumerable.Empty<int>();
Console.WriteLine($"My collection is empty: {myCollection.IsEmpty}");
The new syntax also provides an easy way to add static extension members. Here we don’t provide the instance of the type, but only the type itself:
public static class DateOnlyExtensions
{
extension(DateOnly) {
public static DateOnly Birthday => new(1998, 06, 08);
}
}
A usage of this could look as follows:
Console.WriteLine($"My birthday is on {DateOnly.Birthday}");
This is already a pretty cool feature! It’s really an extension–pun intended–on a concept that has already seen widespread use. Even if you’re not planning to make use of the new extension properties or static definitions, you’ll probably want to get used to the new syntax.
Nice-to-haves
The rest of the features that were announced, were nice-to-haves in my opinion. These are features that refine the C# language, rather than fundamentally changing it. I’ll rank them on how useful I think they will be on a daily basis, starting with the most useful one first.
Conditional Assignments
Let’s say we have a Person
class with an Age
property. Setting the age is pretty easy, but what if the Person is null
? Maybe it came from a database or something. In that case, it would be good that we check for this potential null
before assigning it. So we would have something like this:
if (person is not null)
{
person.Age = 27
}
Now with C# 14, we have something called Conditional Assignments, and it looks pretty elegant if you ask me:
person?.Age = 27
This basically means that if the person is null
, it will not assign anything. We already had this syntax in the past when accessing properties where the accessor member might be null
, so it’s nice to see we now get the same syntax for assignment.
The field keyword
If you have ever written a full property before, then you also know that it comes with some boilerplate. Let’s take that same person class as before, but now when setting the age, we want to check if it’s higher than or equal to zero:
private int _age;
public int Age
{
get => _age;
set => _age = value < 1
? throw new AggregateException("Age must be higher or equal to 0") :
value;
}
C# 14 introduces a new keyword called field
that represents the backing field of your property, in this case _age
. When using the field
keyword, we now get something like this:
public int Age
{
get;
set => field = value < 1
? throw new AggregateException("Age must be higher or equal to 0") :
value;
}
So yeah, a pretty neat little thing. However, there’s a potential breaking change 💣: The existence of the field contextual keyword within property accessor bodies is a potentially breaking change, since it’s becoming a reserved keyword. You can use @field
or this.field
to disambiguate between the field keyword and the identifier, or you can rename the current field symbol to provide better distinction.
Unbound generics for nameof
It was not possible in the C# language to have something like this:
Console.WriteLine("Until C# 13, we could not do a 'nameof' of a generic type like" +
$" {nameof(List<>)} without giving a concrete type like 'nameof(List<int>)'");
I already spoiled the surprise… 🎁 but yep, now you can!
Some honorable mentions
There were also some very small improvements that are not worth mentioning in full, but do deserve a shout-out:
- Partial constructors and events
- Simple lambda definitions and modifiers
- User defined compound assignment (not really small, but still in the proposal-phase at the time of writing)
That’s it 🎉. I’ll definitely be using the new extension members. It’s also nice to see they are putting in the effort to refine and clean up the C# language as well.