Properties
In the previous lesson the fields were closed, and the work with them went through methods. For a house's floors that would be a pair of methods: GetFloors() and SetFloors(3). It is long to write, but SetFloors can check the number. An open field is shorter, a.Floors = 3, but it cannot check anything: a.Floors = -1 goes through silently.
Picture a concierge in an apartment building. Visitors do not walk around the apartments themselves: they hand a parcel to the concierge, who inspects it first. To a visitor it feels like leaving the parcel on a desk, but in fact it was accepted by the rules.
In C# such a concierge is a property: a class member that is read and written like a field, but every read and every write runs code of the class.
A property with get and set
Let's make the floors a property that never lets a house have fewer than one floor:
var a = new House();
a.Floors = 3;
Console.WriteLine(a.Floors); // 3
a.Floors = -1;
Console.WriteLine(a.Floors); // 1
class House
{
private int _floors = 1;
public int Floors
{
get => _floors;
set => _floors = value < 1 ? 1 : value;
}
}Let's go through the class line by line:
private int _floors = 1;is a private field that holds the number of floors.public int Floors { … }is the property: a type, a name and a block withgetandset. There are no parentheses after the name.get => _floors;runs on every read ofa.Floorsand hands out the field's value.set => …runs on every write. The wordvalueinsidesetis the value on the right of=.a.Floors = -1runssetwithvalueequal to-1. The conditionvalue < 1is true, so1goes into the field.
From outside, a.Floors looks like a field, but there is no way around the check: outside code has no other path to _floors. In larger programs a wrong value is usually answered with an error rather than fixed silently. But the place for the check is the same: set.
Here the concierge and the property differ. A concierge can keep a parcel at the desk, while a property stores nothing itself. The number sits in the field _floors, and the property has only the code of get and set.
The auto-property
Often no check is needed, but you still want the field closed. Then you write an auto-property: public string Owner { get; set; } = "";. The compiler creates a hidden field itself, and get and set read and write it. The value after = is the starting one, as with a field.
Why use such a property instead of an open field? A check can be added to set later, and the code that writes a.Owner = "Anna" does not have to change. Also, a property can limit who writes it.
{ get; set; }
Any code can read and write the property, as with an open field.
{ get; private set; }
Any code can read it, but only the code of the class itself can write it:
var a = new House();
a.Paint("red");
Console.WriteLine(a.Color); // red
class House
{
public string Color { get; private set; } = "white";
public void Paint(string color) => Color = color;
}The method Paint is code of the class, so it changes Color. But the line a.Color = "red"; outside does not compile: error CS0272, writing is closed.
{ get; init; }
A value can be written only when the object is created, and after that the property is read-only. For this, the values are listed in braces after new:
var a = new House { Street = "Oak", Number = 7 };
Console.WriteLine(a.Street + " " + a.Number); // Oak 7
class House
{
public string Street { get; init; } = "";
public int Number { get; init; }
}The { Street = "Oak", Number = 7 } part is called an object initializer. The constructor runs first, then the properties get the values from the braces. The empty parentheses after new House can be left out.
An initializer can set properties with an ordinary set too. The point of init is that writing is closed once the object is created: the line a.Number = 8; then does not compile, error CS8852.
{ get; }
A property with only get can be written only in its declaration, with =, or in the constructor. It is a value the object gets when it is built and never changes again.
Property or field
Interviewers often ask how a property differs from a field. The answer has two parts:
- from outside a property looks like a field, but reading and writing run its
getandsetcode; - so a property can check a value or close writing, and a field cannot.
Common mistake
The property's name was written in set instead of the field's:
var a = new House();
a.Floors = 3;
class House
{
private int _floors = 1;
public int Floors
{
get => _floors;
set => Floors = value; // ✗
}
}Stack overflow.The program compiles without warnings, but crashes when it runs. Floors = value inside set calls the set of the same property again, which calls it once more, and so on until the call stack overflows. It is the same recursion without a base case. The property must write to the field: set => _floors = value;.
Key points
- A property is read and written like a field while its code runs:
geton reading,seton writing; insidesetthe new value is calledvalue. - An auto-property
{ get; set; }keeps its value in a hidden field the compiler creates. private setlets only the class's code write,initonly the creation of the object, and a property withgetalone is set in its declaration or in the constructor.- An object initializer
new House { Street = "Oak" }assigns values to properties right after the constructor.