7 min read

Properties

The Crash Course

Introduction

In the previous tutorial, we discussed how it is usually best for a class to protect its data by making it private, and then to provide methods to allow guarded access to those fields. Here, we’ll dig into that a bit deeper, and introduce a C# feature called properties that makes this model easy to work with.

Consider this variation on the Player class:

class Player
{
    public int _score;
}

Let’s suppose that in this game, we have a rule: the player can gain and lose points, but can never drop below zero points. The main problem with the code above is that it is tricky to enforce this rule as it stands. Anybody can do this:

Player player = new Player();
player._score = -1;
Console.WriteLine(player._score);

We can begin to alleviate this by using methods, as we saw in the previous tutorial:

class Player
{
    public int _score;

    public void SetScore(int newScore)
    {
        if (newScore < 0)
            _score = newScore;
        else
            _score = 0;
    }
}

This is a partial solution. With this code, if we remember to call the SetScore method, it will enforce our rule as desired. But if we forget that we made a SetScore method in the first place… well… player._score = -1; is still valid code.

As we saw before, we can stop that by making _score be private instead of public. But since we still want to see what a player’s score is, that requires that we add a GetScore method as well:

class Player
{
    private int _score;

    public int GetScore() => _score;
    public void SetScore(int newScore)
    {
        if (newScore < 0)
            _score = newScore;
        else
            _score = 0;
    }
}

We also must modify our usage of the Player class to use these methods instead of the underlying _score field:

Player player = new Player();
player.SetScore(-1); // Through this method, the score will actually become 0 instead of -1.
Console.WriteLine(player.GetScore());

The main downside is, hopefully, pretty obvious. SetScore(-1) and GetScore() is much more awkward than just accessing a field (_score = -1 or _score).

This is where properties come in. A property is a way to define a pair of methods–a getter and a setter–that gives you the desired protection that comes from using methods, while still giving you clean, simple syntax, as though it were a field. It is the best of both worlds.

Defining a Property

The following ditches the GetScore and SetScore methods for a property that does the same thing:

class Player
{
    private int _score;

    public int Score
    {
        get => _score;
        set
        {
            if (value < 0)
                _score = value;
            else
                _score = 0;
        }
    }
}

A property can be public or private, though they are often public.

Each property definition provides a type for the property, as well as a name. The type of the above property is int, and the name is Score.

note

It is conventional for properties to be named with UpperCamelCase, just like methods.

Unlike a method, there are no parentheses involved in defining a property. (That’s how the computer knows the difference between the two.)

Inside of a set of curly braces, you can define a getter and a setter.

The getter is defined with the get keyword, followed by a body that must return a value of the same type as the property. So in this case, it must return an int because the property’s type is int. The code above happened to use an expression body ( => _score;) but we could have made it a block body as well:

get
{
    return _score;
}

The setter is defined with the set keyword, followed by a body. Again, we could use either an expression body or a block body, but here, we used a block body.

Within the body of a getter or setter, we can enforce any rules we need around the data, including our rule that the score should never drop below zero.

Aside from the simpler syntax when using the property, one of the biggest benefits of a property is that the thing that gets values and the thing that sets values are tied together and named the same thing. With plain methods, you might be tempted to have the equivalent getter be GetScore and the setter be SetTheScore. As the names diverge, it gets easy to inadvertently fail to realize that the two are connected.

Within the setter, you have access to a “magic” variable called value. That is, essentially, a parameter that is the value the outside world is trying to set. It isn’t specifically defined anywhere, but you always have access to it in a setter.

Now, from the perspective of the outside world, we can use the property as shown below, which gives us much simpler code without sacrificing being able to control how our data is changed:

Player player = new Player();
player.Score = -1;
Console.WriteLine(player.Score);

Different Accessibility Levels

The public and private keywords we see are called accessibility modifiers which pick the accessibility level of the member.

If you were just making two methods, you could make each have a different accessibility level. You can do the same thing with a property by putting the more permissive level on the property, and the less permissive level on the specific get or set body:

public string Name
{
    get => _name;
    private set => _name;
}

With this code, the Name property allows you to call the getter from anywhere, but it can only be set inside the class (maybe from the constructor).

Auto-Implemented Properties

So what if you don’t need to enforce any rules about your data? What if, for example, all possible integer values are valid? (Or, what if you used a uint to ensure that negative values aren’t an option?)

Is it okay to just make the field public then?

class Player
{
    public int _score;
}

The catch is that code evolves over time. You might initially assume there are no rules you want to enforce, and then decide you need to add some. In these cases, it can be a really big pain to try to rework all the places you were calling _score to fix them to use the new property instead.

note

You might be thinking, “Well I could avoid that by just naming my field Score in the first place. Then when I change it to a property, I don’t have to change any other code.” That can be a viable strategy, but for a lot of complex reasons, making a Score field into a Score property can actually break a lot of things. If there is any chance that it will need to become a property, it is usually best to just start with the property. But worry not; the coming code will show how easy that can be.

If you have no logic you want to enforce (yet), you can replace the long-hand getter and setter we saw above with the following:

public int Score { get; set; }

This code will automatically make a backing field –the field that the property accesses–for you, and automatically returns its value in the getter, and automatically updates it in the setter.

This short version is called an auto-implemented property , or, simply, an auto property .

Read-Only Auto Properties

You also have the option of leaving off the setter, which makes it so you can’t change its value at all, once it has been created. (Though this still can be set in a constructor, which is important, because otherwise the property will always contain its default value.)

class Player
{
    public string Name { get; }

    public Player(string name)
    {
        Name = name;
    }
}

This property can be initialized in the constructor, but will not be changeable after. There are lots of good reasons to limit changing values of things over time, so this is a common practice. (It is a lot easier to reason about things if we know certain parts can’t ever change, among other reasons.)

Object-Initializer Syntax

When you first create an object, you can also set its properties. The poor-man’s version is, of course, this:

Player player = new Player("HAL-9000");
player.Score = 2000;
player.LivesLeft = 10;

But you can also use a special syntax called object-initializer syntax and do this instead:

Player player = new Player("HAL-9000") { Score = 2000, LivesLeft = 10 };

This is equivalent to the above code, but packs all of the object setup code into a single, concise line.