Sitemap

Behind the scenes of the new field keyword in C# 14

7 min readApr 22, 2025

C# continues to evolve, and with version 14, we get a small but powerful addition to the language: the field keyword. While it might look like simple syntax sugar at first glance, this new keyword can help improve the clarity and maintainability of class properties.

Photo by Angélie Page on Unsplash

Types of property definitions

I started my career writing C# properties like this:

private string _name;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
}
}

I hated it and was happy when C# got auto-implemented properties.

public string Name { get; set; }

But the evolution didn’t end. C# got the property with an initializer, making the property slightly more expressive without losing conciseness.

public string Name { get; set; } = "Unknown";

Expression-bodied properties allow the combination of old-style and the bodied expressions:

private string _name;
public string Name
{
get => _name;
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("Name cannot be empty");
_name = value;
}
}

C# 9 made it possible to create init-only properties:

public string Name { get; init; }

Now, with C# 14, the new field keyword brings a subtle yet impactful improvement. It lets you access the compiler-generated backing field directly within the setter. It enables cleaner logic without declaring a separate variable.

// Examples with the field keyword

// Utilize the field keyword with traditional property syntax.
public string Name
{
get
{
return field;
}
set
{
field = value?.Trim();
}
}

// Expressions
public string Name
{
get => field;
set => field = value?.Trim();
}

// Combination of expressions and auto-implemented properties
public string Name
{
get;
set => field = value?.Trim();
}

These use cases seem perfectly valid, but with the introduction of the new field keyword, there now appear to be seven different ways to define properties — at least, that’s what I counted. Weren’t the previous six options sufficient?

Do we need the new keyword?

Interestingly, the introduction of the field keyword didn’t come out of nowhere — it’s a feature the C# community has been requesting for quite some time. It looks like the first mention of this need was in 2015. Many developers saw this feature as a natural evolution of the language, combining the simplicity of auto-properties with the flexibility of custom logic.

Reduce boilerplate

The primary purpose of using this keyword is to reduce boilerplate code. The field keyword enables simple logic in property setters without requiring a backing field.

// adjusting value in the setter
public string? Name
{
get;
set => field = value?.Trim();
}

// default value
public string? Country
{
get;
set => field = value ?? "Unknown";
}

These kinds of lightweight transformations used to require a private backing field and a full property definition. Now, with the field keyword, the logic is concise and remains compatible with tools that rely on auto-properties, such as serializers or Object-Relational Mappers (ORMs).

Isolation

The new field keyword also introduces a powerful mechanism for achieving isolation. By marking a field with the field keyword, you explicitly indicate that it is a true backing field — isolated from interception, overriding, or any other behavior modification.

The value in the field behaves like a variable that is scoped to the property. It is not visible outside the property, and it cannot be accessed from other methods or properties within the class. This encapsulation prevents unintended side effects or misuse.

Impact is huge

C# and .NET consistently introduce new features with each version. Some of these features are essential, while others are merely desirable. The new field keyword falls somewhere in between.

On one hand, the new field keyword is a relatively minor addition, almost like syntactic sugar. On the other hand, it can significantly impact our codebase. For example, adding a new lock type may seem like a valuable feature, but locking is something we need to do only rarely. In contrast, we add or update properties almost every day.

What’s the trade-off?

Complexity

Introducing new features and syntax to a programming language can increase its complexity, making it more challenging for newcomers to learn and for experienced developers to keep up with changes. This complexity may also lead to fragmented coding styles within teams, as different developers might prefer different subsets of the language. We’ll circle back to this topic later in the post.

Backward compatibility

Maintaining backward compatibility can be tricky and may occasionally lead to awkward code. For example, in the case of the field keyword, if your codebase already has a field named "field", you'll need to adjust the code to restore compatibility and ensure it compiles successfully.

private string @field;

public string Name
{
get => @field;
set => @field = value;
}

However, I don’t see it as a major issue in this case, since the common convention is to use underscores for field names, and “field” itself is a pretty uncommon choice for a class field name.

Nullability

Properties are also utilized for a lazy loading pattern. The common structure typically looks as follows:

private User? _user;
public User User
{
get
{
if (_user is null)
{
_user = FetchUserFromDB();
}
return _user;
}
}

If you want to use the new field to express this pattern, you’ll need to add an attribute([field: MaybeNull]) to appease the compiler and prevent it from throwing an error: “Expression is always false according to nullable reference types’ annotations”.

[field: MaybeNull]
public User User
{
get
{
if (field is null)
{
field = FetchUserFromDB();
}

return field;
}
}

Or you can transform it to a more concise version:

// without the field 
private User _user;
public User User => _user ??= FetchUserFromDB();

// with the field
[field: MaybeNull]
public User User => field ??= FetchUserFromDB();

Personally, I find using an attribute here a bit clunky. The attribute [field: MaybeNull] isn’t commonly known or used by most developers. Moreover, while using attributes can be useful for metaprogramming (ideally for the separation of concerns), [field: MaybeNull] only serves as a hint for the compiler.

I would recommend sticking with the traditional backing field in this case. Alternatively, if you need thread-safe lazy loading and don’t care much about performance, using Lazy<T> is the best solution.

// Thread safe but with additional allocation
private readonly Lazy<User> _lazyUser = new(() => FetchUserFromDB());
public User User => _lazyUser.Value;

Fatigue

Is it time we start talking about .NET fatigue? Ten years ago,
Eric Clemmons published a famous and funny article about JavaScript fatigue. With the growing number of features and frameworks in .NET, are we now approaching a similar tipping point?

With so many features and syntax choices in C#, it’s getting harder to know what to use and when. The new field keyword is just the latest addition. Just think about primary constructors, minimal APIs vs. controllers, or whether to use the new HybridCache or stick with a proven third-party library. Just in terms of keywords alone, Go has 25 keywords, but C# has over 80.

All of these things add to the idiomatic tension, which basically means: “We already have too many ways to do this, and now there’s one more.”

As I showed you, there are now at least seven distinct ways to express a property. While each of these has a valid purpose, the sheer variety might be overwhelming, especially for newcomers or teams trying to establish consistent coding guidelines. The more options are available, the more developers are prone to the bike-shedding effect — spending time discussing the syntax rather than the actual problem at hand.

Potentially other additional

Oh, and we’re not finished. Looking ahead, there are numerous ways the C# language could further develop its syntax. If you take a moment to review the discussion about potential additions to properties, you’ll find various ideas like:

The property body without explicitly declaring a getter

//basically a method without brackets
public string FullName
{
if (string.IsNullOrEmpty(FirstName)) return LastName;
if (string.IsNullOrEmpty(LastName)) return FirstName;
return FirstName + " " + LastName;
}

Scoped fields

public string MyProperty
{
string myField; // this would be visible only in MyProperty

get { return myField; }
set
{
myField = value;
NotifyOfPropertyChange(nameof(MyProperty));
}
}

An inspiration from Typescript:

public SolidColorBrush get Brush 
{
// ...logic...
return _brush;
}

However, what seems most interesting is Extension Properties, which the C# team is already looking into.

public class Player
{
public Dictionary<int,int> Stats {get;}
}

public extension PlayerExtensions of Player {
public int Health {
get => this.Stats[StatTypes.Health];
set => this.stats[StatTypes.Health] = value;
}
}

While many of these features are still speculative, they reflect a broader trend in modern programming languages toward concise code and reduced boilerplate. Unfortunately, they also increase idiomatic tension and add complexity to the language syntax.

Conclusion

When I started working on this blog post, I didn’t expect to write more than 1,500 words about something as simple as a new keyword for a bit less boilerplate properties, but here we are.

C# 14’s new field keyword might be a small addition, but it makes a noticeable difference when dealing with properties. It’s especially handy in models and data transfer objects (DTOs) where you need lightweight logic, such as sanitization or default values. And judging by recent trends, property syntax in C# is far from finalized — future iterations may bring even more exotic syntax and functionality.

--

--

Marek Sirkovský
Marek Sirkovský

Responses (1)