C# Property Accessors: Encapsulation and Validation with Get and Set

Object-oriented principles restrict direct access to a class's internal state. C# employs properties—defined via get and set accessors—to mediate interactions with private fields. This mechanism acts as a controlled gateway, preserving encapsulation while exposing necessary data.

Consider a class maintaining internal state:

class Worker
{
    private string fullName;
    private int yearsExperience;

    public string FullName
    {
        get { return fullName; }
        set { fullName = value; }
    }

    public int YearsExperience
    {
        get { return yearsExperience; }
        set { yearsExperience = value; }
    }
}

The general structure for declaring a property requires matching the return type to the backing field, utilizing a distinct name:

public ReturnType PropertyName
{
    get { return BackingField; }
    set { BackingField = value; }
}

Retrieving a property invokes the get accessor, while assigning a value triggers the set accessor. Omitting the set block renders the property read-only, and excluding get makes it write-only.

Relying on properties instead of public fields permits runtime validation. If an invalid value is assigned, the set accessor can reject it:

class Worker
{
    private string fullName;
    private int yearsExperience;

    public string FullName
    {
        get { return fullName; }
        set { fullName = value; }
    }

    public int YearsExperience
    {
        get { return yearsExperience; }
        set
        {
            if (value >= 0 && value <= 50)
            {
                yearsExperience = value;
            }
        }
    }
}

Properties also facilitate transparent data transformation without altering the external interface. A heat sensor might internally store Kelvin but externally report Celsius:

class HeatSensor
{
    private double kelvinValue;

    public HeatSensor(double initialKelvin)
    {
        kelvinValue = initialKelvin;
    }

    public double CurrentTemp
    {
        get { return kelvinValue - 273.15; }
        set { kelvinValue = value + 273.15; }
    }
}

Invoking new HeatSensor(313.15).CurrentTemp yields 40. If the underlying storage shifts, the public contract remains intact.

Exposing a field directly as a public member circumvents encapsulation:

public class User1
{
    public string identifier;
}

Using auto-implemented properties enforces encapsulation even without custom logic:

public class User2
{
    public string Identifier { get; set; }
}

The compiler generates a hidden backing field for Identifier, ensuring the architecture supports future validation additions without breaking consuming code. Manually expanding an auto-property looks like this:

private string identifier;

public string Identifier
{
    get { return identifier; }
    set { identifier = value; }
}

Custom logic within accessors handles null checks or formatting:

private string identifier;
public string Identifier
{
    get { return identifier; }
    set { identifier = string.IsNullOrEmpty(value) ? "Unknown" : value; }
}

Validation ensures state integrity:

class Profile
{
    private int userAge;

    public int UserAge
    {
        get { return userAge; }
        set
        {
            if (value < 0 || value > 150)
            {
                userAge = 0;
            }
            else
            {
                userAge = value;
            }
        }
    }
}

Accessors provide three core capabilities: concealing internal representation, enforcing business rules, and triggering side effects upon state mutation.

For scenarios requiring no custom logic, C# offers concise syntax. Traditional implementations:

class Configuration
{
    private int timeout;
    public int Timeout
    {
        get { return timeout; }
        set { timeout = value; }
    }
}

Expression-bodied members:

class Configuration
{
    private int timeout;
    public int Timeout
    {
        get => timeout;
        set => timeout = value;
    }
}

Auto-implemented properties eliminate the need for explicit backing fields:

class Configuration
{
    public int Timeout { get; set; }
}

Tags: C# properties encapsulation OOP

Posted on Fri, 12 Jun 2026 16:29:23 +0000 by Mindwreck