Fields and Properties
A field stores a value, a property guards access to it, and the C# 14 field keyword adds a check without writing the field by hand.
public int Count { get; set; } // same as: private int _count; public int Count { get { return _count; } set { _count = value; } }A field is a variable that lives inside an object. It stores a value and does nothing else. A property looks like a field from outside but is really a pair of tiny methods called accessors: a get that produces a value and a set that receives one. The accessor is where a rule goes, so a nonsense value never reaches storage.
The short form and the long form
Writing public int FanSpeed { get; set; } declares an auto-implemented property. The compiler generates a hidden private field, called the backing field, plus a get that returns it and a set that assigns it. The long form writes that field and both bodies by hand. They compile to the same thing, so use the short form until you need a rule.
Putting a rule in an accessor
Inside a set body, the incoming value arrives in an implicit parameter named value. You never declare it. C# 14 adds the contextual keyword field, which refers to the generated backing field from inside an accessor. That lets you write one accessor with a body and leave the other generated, instead of declaring a field by hand just to add a range check.
What trips people up
Make fields private and expose a property instead. A public field cannot later grow a check, and it cannot be used where an interface demands a property. If your class already has something named field, write this.field or @field inside accessors to mean that member rather than the keyword.
Example
Thermostat t = new Thermostat();
t.TargetCelsius = 21;
Console.WriteLine($"target: {t.TargetCelsius}");
// Out of range, so the set accessor ignores it and the old value stands.
t.TargetCelsius = 99;
Console.WriteLine($"target: {t.TargetCelsius}");
class Thermostat
{
// Longhand: a private field, plus a property with both bodies written out.
private int _targetCelsius = 18;
public int TargetCelsius
{
get
{
return _targetCelsius;
}
set
{
// `value` is the incoming value. It is declared for you.
if (value >= 5 && value <= 30)
{
_targetCelsius = value;
}
}
}
// Shorthand. The compiler writes the hidden field and both bodies,
// which is the longhand above minus the range check.
public int FanSpeed { get; set; }
}
// Output:
// target: 21
// target: 21When to use it
- Reject a negative price or an out-of-range percentage at the moment it is assigned rather than later.
- Expose a value for reading while keeping the only write inside the class.
- Add validation to an existing auto-implemented property without changing any calling code.
More examples
The field keyword, new in C# 14
Reach for this when an auto-implemented property needs validation added later. Before C# 14 that meant declaring a backing field, writing both accessors, and touching every line that used the field. The field keyword lets the change stay inside the one accessor that needs it. Code written for .NET 9 or earlier cannot use it, so you will still meet the hand-written form everywhere.
Dial dial = new Dial();
dial.Level = 7;
Console.WriteLine($"level: {dial.Level}");
// 99 is pulled back to the top of the allowed range.
dial.Level = 99;
Console.WriteLine($"level: {dial.Level}");
class Dial
{
// `field` is the backing field the compiler generated for Level.
// Only the set accessor has a body; get is still generated.
// No private int _level is declared anywhere.
public int Level
{
get;
set => field = Math.Clamp(value, 0, 10);
}
}
// Output:
// level: 7
// level: 10Short form and long form side by side
Both properties behave the same to every caller, so this is purely about which you read in someone else's code. Note the initialisers: with nullable reference types enabled, a non-nullable string property left unset would warn, so each one is given a starting value. The alternative is to set it in a constructor or mark it required.
class Product
{
// Short form. The compiler generates a hidden private field
// and both trivial accessors. The = "unknown" is a field initialiser
// on that hidden field.
public string Sku { get; set; } = "unknown";
// Long form. Identical behaviour, written out.
private string _name = "unnamed";
public string Name
{
get
{
return _name;
}
set
{
_name = value;
}
}
}Read-only storage and computed values
This is the shape to reach for when a value is decided once and derived values must stay consistent. Storing a TotalCents field as well would let the three numbers fall out of step the moment one of them changed. A computed property costs a little arithmetic per read and removes that whole class of bug.
Receipt receipt = new Receipt(1200, 240);
Console.WriteLine($"net: {receipt.NetCents}");
Console.WriteLine($"total: {receipt.TotalCents}");
class Receipt
{
// readonly: assignable only here or inside a constructor.
// Nothing can change it afterwards, not even other methods of Receipt.
private readonly int _netCents;
private readonly int _taxCents;
public Receipt(int netCents, int taxCents)
{
_netCents = netCents;
_taxCents = taxCents;
}
// A get-only property. No set accessor exists, so no caller can assign it.
public int NetCents => _netCents;
// A computed property. It stores nothing and works the answer out
// on every read, so it can never disagree with the two fields.
public int TotalCents => _netCents + _taxCents;
}
// Output:
// net: 1200
// total: 1440
Discussion