The field keyword in C# 14 is a contextual keyword designed to eliminate boilerplate when a property needs custom logic (like validation, trimming, or lazy loading) but doesn’t genuinely need a manually declared private backing field.
Before C# 14, adding a single check to a setter meant declaring a private variable, writing an explicit getter, and writing an explicit setter. In C# 14, the compiler automatically synthesizes the backing field and lets you refer to it directly as field inside property accessors.
The Old Way (C# 13 and earlier)
private decimal _price;
public decimal Price
{
get => _price;
set => _price = value >= 0 ? value : throw new ArgumentOutOfRangeException(nameof(value));
}
The C# 14 Way
public decimal Price
{
get; // auto-implemented
set => field = value >= 0 ? value : throw new ArgumentOutOfRangeException(nameof(value));
}
- No
_pricedeclaration: The compiler emits the hidden backing field. - Mixed accessors: You can keep
get;automatic while providing a body only forset(or vice versa). - Encapsulation: Other methods in your class cannot bypass your setter validation by accidentally modifying a private variable, because no named backing field is exposed to the class.
Common Use Cases & Examples
1. Input Validation and Normalization
A very common pattern is sanitizing or validating arguments in set or init accessors:
public class UserProfile
{
// Normalization: trim whitespace before saving
public string Username
{
get;
set => field = string.IsNullOrWhiteSpace(value)
? throw new ArgumentException("Username cannot be empty.", nameof(value))
: value.Trim();
}
// Default initializer works seamlessly with field-backed properties
public int Age
{
get;
set => field = value >= 0 ? value : throw new ArgumentOutOfRangeException(nameof(value));
} = 18;
}
2. Lazy Initialization in a Getter
You can use field inside an expression-bodied property or explicit getter to cache expensive computations:
public class ReportGenerator
{
// The backing field starts null, then caches the result on first read
public string SummaryReport => field ??= BuildExpensiveReport();
private string BuildExpensiveReport()
{
// ... perform expensive formatting / calculation ...
return "Aggregated Report Data";
}
}
3. Change Notification (INotifyPropertyChanged)
For UI or MVVM architectures, emitting change notifications usually requires repetitive private field declarations:
public class SettingsViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
public bool DarkModeEnabled
{
get;
set
{
if (field == value) return;
field = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(DarkModeEnabled)));
}
}
}
Rules and Nuances to Keep in Mind
| Rule | Detail |
| Scope | field is only a keyword inside property accessor bodies (get, set, init). It is not recognized in regular methods, constructors, or indexers. |
| Initializers vs Setters | Property initializers (= "value";) assign directly to the compiler-generated field, skipping your set accessor logic. Assignments inside constructors call your set accessor as usual. |
| Disambiguation | If your class has a member named field, accessing it inside an accessor is ambiguous. Use this.field to access the class member, or @field if referring to a parameter/local named field. |
nameof() Behavior | You cannot write nameof(field) because the field has no user-defined name; pass nameof(value) or nameof(PropertyName) to exception arguments. |