Properties

Properties

The Crash Course

  • Properties provide a quick and effective approach to creating getters and setters for instance variables.
  • You can create a property with code like the following:
public int Score
{
    get
    {
        return score;
    }
    set
    {
        score = value;
        if (score < 0)
        {
            score = 0;
        }
    }
}
  • Not all properties need both a setter and a getter.
  • Getters and setters can have different access modifiers.
  • Auto-implemented properties can be created that allow you to quickly define a simple property with default behavior (public int Score { get; set; }).

Introduction

In the previous tutorial, we talked about how it is generally considered a good idea to make your fields private and make methods that allow access to fields as needed. But we also saw how having a bunch of GetWhatever() and SetWhatever(…) methods are not as pretty as direct field access, like thing.whatever.

We're going to address that problem here by discussing a rather unique feature of C# called properties.

The intent of properties is to give us the best of both worlds: the ability to protect our data from arbitrary changes from the outside world without sacrificing simple field-like access.

Consider this class definition:

class Rectangle
{
    private float _width;
    private float _height;
 
    public float GetWidth() { return _width; }
    public float SetWidth(float width) { _width = width; }
 
    public float GetHeight() { return _height; }
    public float SetHeight(float height) { _height = height; }
 
    public float GetArea() { return _width * _height; }
 
    public Rectangle(float width, float height)
    {
        _width = width;
        _height = height;
    }
}

This class has made its fields private to ensure it can enforce any rules it may have about them, but it comes with a cost. Consider this code that makes a rectangle and then tries to add 1 to the original width.

Rectangle rectangle = new Rectangle(5, 6);
rectangle.SetWidth(rectangle.GetWidth() + 1);

That second line is especially annoying compared to its counterpart if we just made our fields public, which would otherwise look more like this:

Rectangle rectangle = new Rectangle(5, 6);
rectangle._width++;

Rather than having to pick between the two, we can use a special C# language feature called a property, which allows us to define getter and setter methods in a compact way that supports field-like access without sacrificing the protection methods offer.

Adding Basic Properties

A property is easier to show than describe, so let's just jump into it and replace our GetWidth() and SetWidth(float) methods with a property:

class Rectangle
{
    private float _width;
    private float _height;
 
    public float Width
    {
        get { return _width; }
        set { _width = value; }
    }
 
    public float GetHeight() { return _height; }
    public float SetHeight(float height) { _height = height; }
 
    public float GetArea() { return _width * _height; }
 
    public Rectangle(float width, float height)
    {
        _width = width;
        _height = height;
    }
}

A property has an accessibility modifier, which is often (but not always) public, followed by a type (float in this case), then the name of the property. Inside of curly braces is a definition for a getter, using the get keyword, and a setter, using the set keyword. The getter is expected to return something with a type matching the property's type. The setter must not return anything but has access to the special value value, which is the value the outside world intended to set.

For all practical purposes, this property is identical to the GetWidth and SetWidth methods that it replaced, but does have a couple of advantages.

First, it directly ties the getter and setter together. With two random methods, you could spread them out across the class, making it hard to recognize they belong together, and you could give them mismatched names like GetWidth() and SetLength(float). With a property, the two share a name.

Second, on the outside, a property is accessed the same way you'd access a field:

Rectangle rectangle = new Rectangle(5, 6);
rectangle.Width++;
 
// or...
rectangle.Width = 20;
Console.WriteLine(rectangle.Width);

Convenient, isn't it?

Behind the scenes, the property still compiles down to two methods, so we still get all of the benefits we were hoping for by making our fields private and accessing them through public methods.

Variations on the Basic Concept

Properties have been in the C# language since the start, but it feels like every new version of C# adds a new extra thing you can do with properties. That leaves you with a ton of nuance and possibilities with properties. We won't cover them all here, but we'll cover a few common examples.

Get- or Set-Only Properties

Properties do not need to have both a getter and a setter. If you want one without the other, you just leave the one you don't need off. We can us that to make a property for the rectangle's area:

public float Area { get { return _width * _height; } }

Different Accessibility Levels

If you want a property getter to be public but the setter to be private, you can do that by applying the public keyword to the property but the private keyword to the part you want restricted:

public float SomeProperty { get { return _someValue; } private set { _someValue = value; } }

Auto-Properties

Perhaps the single most popular variation on the idea of a property is that of an auto-property.

Our rectangle width is extremely simple. If we gather all of the elements that support the rectangle's width, it is these:

private float _width;
public float Width { get { return _width; } set { _width = value; } }

Notably, there are no actual rules to enforce here! There are no side effects and no validation. It is just a simple "stick the value into the related field" and "retrieve the value currently in the related field."

This is not uncommon, especially when a class is relatively new. (Classes tend to grow extra validation and complexities after they've been around for a while.) To reduce the amount of boilerplate code you have to write, the following is an auto-property that is precisely equal to the code before:

public float Width { get; set; }

The compiler will automatically generate a related field called the backing field and wire it all up to get and set that field as expected. This allows you to reserve the option to turn it into a full property later on without affecting most of the program. You will see a ton of auto-properties when you look at others' code. They're everywhere.

Object Initializer Syntax

Another cool thing about properties is that there's special syntax to set a bunch of properties on a newly created object. While the constructor will typically demand you supply values for anything mandatory, this syntax makes it possible to set a bunch of optional properties while the object is still being created:

Player player1 = new Player("CatBot 2000") { Score = 2000, LivesLeft = 9 };

The alternative would have been to assign values to those properties on subsequent lines. This version is more compact and confines all initialization code to a single line.

What's Next?

That covers all of the important things about properties.

The next thing we want to go over is something very similar in appearance (but actually quite different in implementation) to a class: a struct.