Skip to content
Theory · Classes and objects · lesson 6 of 11

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:

C#
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:

  1. private int _floors = 1; is a private field that holds the number of floors.
  2. public int Floors { … } is the property: a type, a name and a block with get and set. There are no parentheses after the name.
  3. get => _floors; runs on every read of a.Floors and hands out the field's value.
  4. set => … runs on every write. The word value inside set is the value on the right of =.
  5. a.Floors = -1 runs set with value equal to -1. The condition value < 1 is true, so 1 goes 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:

C#
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:

C#
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 get and set code;
  • 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:

C#
var a = new House();
a.Floors = 3;

class House
{
    private int _floors = 1;

    public int Floors
    {
        get => _floors;
        set => Floors = value; // ✗
    }
}
TEXT
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: get on reading, set on writing; inside set the new value is called value.
  • An auto-property { get; set; } keeps its value in a hidden field the compiler creates.
  • private set lets only the class's code write, init only the creation of the object, and a property with get alone is set in its declaration or in the constructor.
  • An object initializer new House { Street = "Oak" } assigns values to properties right after the constructor.