Introduction
Every C# team eventually writes the same property twice: once as a clean auto-property, once as a bloated version with a private field bolted on just to add one line of validation. C# 14 finally kills that tax. The field keyword lets you keep the auto-property syntax and still add logic in the accessor. No backing field to declare. No name to keep in sync. It's the kind of change that makes old code look dated the moment you see it.
Most articles stop there. Few mention what happens when your codebase depends on the backing field's name—not just its behavior. If you use EF Core with explicit field mapping, or reflection-based mapping code, this refactor can compile cleanly and still break at runtime.
This article covers that gap.
What field Removes
Before C# 14, a validated property needed a manual backing field:
public class Product
{
private decimal _price;
public decimal Price
{
get => _price;
set => _price = value >= 0
? value
: throw new ArgumentOutOfRangeException(nameof(value));
}
}
With the field keyword, the compiler generates the backing field for you:
public class Product
{
public decimal Price
{
get;
set => field = value >= 0
? value
: throw new ArgumentOutOfRangeException(nameof(value));
}
}
Same behavior, fewer lines. Nothing new here.
Where It Actually Bites: Reflection-Based Field Access
The compiler-generated field isn't named _price. It follows the auto-property naming pattern:
// Decompiled IL field name
private decimal '<Price>k__BackingField';
The angle brackets make that name illegal to type in C# source. That's on purpose. It stops naming collisions. But it also means old code that looked up your field by its string name, "_price", won't find it anymore.
Case 1: EF Core HasField / [BackingField] Mapping
Some teams map EF Core properties straight to private fields. This bypasses setter validation during materialization. It means naming the field explicitly:
public class OrderLineItem
{
private int _quantity;
public int Quantity
{
get => _quantity;
set => _quantity = value;
}
}
modelBuilder.Entity<OrderLineItem>()
.Property(o => o.Quantity)
.HasField("_quantity");
Refactor Quantity to use field, and _quantity no longer exists:
public class OrderLineItem
{
public int Quantity { get; set; }
}
// Throws InvalidOperationException at model-build time:
// "_quantity" could not be found
modelBuilder.Entity<OrderLineItem>()
.Property(o => o.Quantity)
.HasField("_quantity");
Fix
Drop the explicit HasField call. Let EF Core's default convention find the compiler-generated field on its own. Remove any UsePropertyAccessMode overrides tied to a specific field name too.
modelBuilder.Entity<OrderLineItem>()
.Property(o => o.Quantity); // EF Core infers the backing field on its own
| Scenario | Before field Keyword | After Refactor | Result |
|---|---|---|---|
| No explicit HasField | EF infers _quantity by convention | EF infers <Quantity>k__BackingField | Works, no change needed |
| Explicit HasField("_quantity") | Field exists, mapping works | Field renamed, mapping breaks | Runtime InvalidOperationException |
| [BackingField(nameof(_quantity))] attribute | Compiles and works | _quantity no longer exists | Compile error, easy to catch |

Join the conversation! Your thoughts help the community grow.