C# 14 introduces the field contextual keyword, which lets you write logic inside a property accessor without declaring a separate private backing field. The syntax is cleaner, but the more important benefit is a safety guarantee: the backing storage becomes inaccessible outside the property itself.
The problem
Consider a Thermostat class that exposes a TargetTemperature property. The set accessor validates that the value stays within a safe operating range:
public class Thermostat
{
private double _targetTemperature;
public double TargetTemperature
{
get => _targetTemperature;
set => _targetTemperature = value is >= 10 and <= 30
? value
: throw new ArgumentOutOfRangeException(nameof(value), "Temperature must be between 10°C and 30°C.");
}
public void FactoryReset() => _targetTemperature = 0;
}
The set accessor enforces the range. But FactoryReset reaches past it and writes 0 directly to the backing field - a value the accessor would have rejected. In other words, the validation has been silently bypassed.
var thermostat = new Thermostat { TargetTemperature = 21 };
Console.WriteLine(thermostat.TargetTemperature); // 21
thermostat.FactoryReset();
Console.WriteLine(thermostat.TargetTemperature); // 0 - out of range, no exception thrown
This is the core issue: private fields are still accessible from anywhere within the same type. The backing field and the accessor that validates it are two separate things, and code inside the class can always choose which one to use.
The C# 14 fix
Replace the backing field and its usages with the field keyword:
public class Thermostat
{
public double TargetTemperature
{
get => field;
set => field = value is >= 10 and <= 30
? value
: throw new ArgumentOutOfRangeException(nameof(value), "Temperature must be between 10°C and 30°C.");
}
}
The compiler generates the backing field automatically. Most importantly, field only exists within the property’s accessor body - no other method in the class can access it. Any method that wants to change TargetTemperature now has to go through the property, which means guaranteed validation.
You can also omit field from the get accessor when you just want to return it as-is, which is the case in this example:
public double TargetTemperature
{
get;
set => field = value is >= 10 and <= 30
? value
: throw new ArgumentOutOfRangeException(nameof(value), "Temperature must be between 10°C and 30°C.");
}
Both forms compile to the same result.
One naming caveat
If you already have a member named field in your type, the compiler raises a warning due to the naming conflict. In practice this is very uncommon, but still worth knowing if you’re adding this pattern to an existing legacy codebase (which might still have confusing code with confusing naming).
The takeaway
The field keyword isn’t just about removing boilerplate - it actually changes who can reach the backing field. When nothing else in the type can touch it directly, only then can you guarantee that your validation logic is genuinely enforced instead of being respected by convention.