OOP Pillars – Encapsulation in C#

Reading Time: 3 minutes

In this article I talk about one of the first of the OOP Pillars – Encapsulation in C#. To fully understand encapsulation, you must have a basic knowledge of Classes in C#. Therefore, if you’re new to C# or need a refresher on Classes, please see my previous post on classes.

Encapsulation is the concept where an object’s data is not directly accessible via an object’s instance. Rather, an object’s data is declared private and access to it is done via public properties.

To illustrate this concept, I will use a Car class.

public class Car
{
    public int NumberOfDoors;
}

The problem with public data members is that the member is unable to validate values assigned to it. While the assigned value can be a valid integer value, but that does not mean that it is a valid number of doors for a car.

To resolve this problem, Encapsulation provides a way to ensure the integrity of an object’s state data by using Accessor and Mutator methods. An Accessor (or get) method is a public method that returns the value of a private member. A Mutator (or set) mothod is a public method that sets the value of private member. C# provides get and set functionality via Properties, which are a simplification of the Accessor and Mutator methods. Let’s update our class definition to use a property to provide access to the private data via a property.

public class Car
{
    private int _numberOfDoors;
    // Constructor
    public Car(int numberOfDoors)
    {
         _numberOfDoors = numberOfDoors;
    }
    public int NumberOfDoors
    {
        get => _numberOfDoors;
        set
        {
            if (value >= 2 && value <= 5)
            {
                _numberOfDoors = value;
            }
            else
            {
                throw new ArgumentOutOfRangeException(nameof(_numberOfDoors));
            }
        }
    }
}

In the code above, _numberOfDoors is only accessible via the public property NumberOfDoors. The public property ensures that the value assigned is within the valid range, in this case 2-5. Once a property is in place, it appears to the caller that they’re accessing the data directly. However, the appropriate Get and Set methods are called behind the scene, preserving Encapsulation.

Setting Private Properties Via a Constructor

A constructor has access to set private members. In the example above, the constructor is setting the value of the _numberOfDoors member. When you set the member directly, the constructor is not performing any business validation on the incoming value, this is a poor approach. A better approach is to validate the incoming data. Rather than duplicating the validation logic, the construction can assign the value to the NumberOfDoors property. Therefore, avoiding duplication of the validation logic.

    public Car(int numberOfDoors)
    {
        // Assignment of NumberOfDoors property
        NumberOfDoors = numberOfDoors;
    }

It is also good practice to use properties throughout your class implementation to ensure business rules are validate and to reduce duplication.

Read-Only and Write-Only Properties

When encapsulating private data, you can configure properties to be read-only or write-only. This is accomplished by omitting the set or get blocks, respectively. Alternatively you can mark the set block with the private keyword. This ensures that the property remains read-only, but allows the constructor to set the value via the property, to ensure business rules are validated.

    public Car(string make, int numberOfDoors)
    {
        Make = make;
        NumberOfDoors = numberOfDoors;
    }

    public string Make
    {
        get => _make;
        private set
        {
            if (value.Length <= 50)
            {
                _make = value;
            }
            else
            {
                throw new ArgumentOutOfRangeException(nameof(_numberOfDoors));
            }
        }
    }

Partial Classes

Lastly, I would like to briefly talk about partial classes. In C#, you can partition a class definition across multiple files. This allows you to isolate code that is more used or modified, from the standard, base code. This is a simple, yet powerful concept.

One example of Partial Classes is when using Entity Frameworks database-first. The generated code is placed into partial classes. Therefore it allows the user to, for example, add validation to the properties, in a separate file. In other words, the developer can add their modifications, unobtrusively and also maintain their changes if EF decides to re-generate the database code.

Leave a comment

Your email address will not be published. Required fields are marked *