Property Accessors - Managing Access and Behavior in C#

Vaibhav • September 10, 2025

In the previous article, we explored encapsulation - the principle of hiding internal object state and exposing only what’s necessary through controlled interfaces. We saw how access modifiers and properties help enforce boundaries and protect data integrity. Now, we focus on a key feature that makes properties powerful and flexible in C#: property accessors.

Property accessors - get and set - allow you to define how values are retrieved and assigned. They are the foundation of encapsulated data access in C#. In this article, we’ll explore how accessors work, how to customize them, and how to design properties that are safe, expressive, and aligned with object-oriented principles.

Understanding Property Accessors

A property in C# is a member that provides a flexible mechanism to read, write, or compute the value of a private field. It typically includes two accessors:

public class Product
{
    private decimal price;

    public decimal Price
    {
        get { return price; }
        set { price = value; }
    }
}

The get accessor returns the value of the field, and the set accessor assigns a new value. This structure allows you to control how the field is accessed and modified.

When you use the property, it looks like a field:

Product p = new Product();
p.Price = 99.99m;             // Calls set accessor
Console.WriteLine(p.Price);   // Calls get accessor

Behind the scenes, the accessors run code that interacts with the private field. This is the essence of encapsulation - exposing behavior, not raw data.

Auto-Implemented Properties

For simple cases where no custom logic is needed, C# allows auto-implemented properties. These properties automatically create a hidden backing field.

public class Product
{
    public string Name { get; set; }
    public decimal Price { get; set; }
}

This syntax is concise and ideal for data models or DTOs. You can still add logic later by converting the property to a full form with explicit accessors.

Read-Only Properties

You can omit the set accessor to make a property read-only. This is useful for values that should not change after initialization.

public class User
{
    public string Username { get; }
    public DateTime CreatedAt { get; }

    public User(string username)
    {
        Username = username;
        CreatedAt = DateTime.Now;
    }
}

These properties are set in the constructor and cannot be modified afterwards. This enforces immutability for critical data.

Write-Only Properties

You can omit the get accessor to create a write-only property. This is rare but useful for sensitive data like passwords.

public class Account
{
    private string passwordHash;

    public string Password
    {
        set { passwordHash = Hash(value); }
    }

    private string Hash(string input)
    {
        return "hashed_" + input;
    }
}

This property allows setting a password but not reading it. The value is hashed before storage, protecting sensitive information.

Validation in Set Accessors

The set accessor is a natural place to validate input before accepting it. This helps enforce rules and prevent invalid state.

public class Product
{
    private decimal price;

    public decimal Price
    {
        get { return price; }
        set
        {
            if (value < 0)
                throw new ArgumentException("Price cannot be negative.");
            price = value;
        }
    }
}

Now, any attempt to set a negative price will result in an exception. This protects the object from invalid data and enforces business rules.

Computed Properties

Properties don’t have to store data - they can compute values based on other fields. These are called computed properties.

public class Temperature
{
    public double Celsius { get; set; }

    public double Fahrenheit
    {
        get { return Celsius * 9 / 5 + 32; }
    }
}

The Fahrenheit property is calculated from Celsius. It does not store a value, but provides a useful view of the data.

Backing Fields and Custom Logic

When you need custom logic, use a backing field - a private variable that stores the actual data. This gives you full control over how values are stored and retrieved.

public class Rectangle
{
    private int width;
    private int height;

    public int Width
    {
        get { return width; }
        set
        {
            if (value > 0)
                width = value;
        }
    }

    public int Height
    {
        get { return height; }
        set
        {
            if (value > 0)
                height = value;
        }
    }

    public int Area
    {
        get { return width * height; }
    }
}

This class uses backing fields to enforce that width and height are positive. The Area property is computed from those fields.

Expression-Bodied Properties

For simple properties, C# allows a concise syntax called expression-bodied properties. These use the => operator.

public class Circle
{
    public double Radius { get; set; }
    public double Area => Math.PI * Radius * Radius;
}

This syntax is clean and readable. It’s ideal for properties that return a single expression.

Access Modifiers on Accessors

You can apply different access modifiers to get and set accessors. This allows you to expose one accessor publicly and restrict the other.

public class Configuration
{
    public string AppName { get; private set; }

    public Configuration(string name)
    {
        AppName = name;
    }
}

Here, AppName can be read publicly but only set within the class. This is useful for values that should be initialized once and then remain unchanged.

Properties vs. Methods

Use properties for data access and methods for actions. If something feels like a value - use a property. If it performs a task - use a method.

user.Name = "Vaibhav";      // Property - setting a value
user.Login();              // Method - performing an action

Properties should be fast, predictable, and free of side effects. Avoid using properties for operations that take a long time or change state.

Designing Properties for Clarity

A well-designed property should:

  • Reflect the intent of the data.
  • Enforce rules through validation.
  • Expose only what’s necessary.
  • Use consistent naming and casing.
  • Be documented clearly.
/// <summary>Gets or sets the price of the product. Must be non-negative.</summary>
public decimal Price
{
    get { return price; }
    set
    {
        if (value < 0)
            throw new ArgumentException("Price cannot be negative.");
        price = value;
    }
}

This property is well-named, validated, and documented. It’s easy to understand and safe to use.

Summary

Property accessors are a powerful feature in C# that allow you to manage access to object state in a controlled and expressive way. The get and set accessors define how values are retrieved and assigned, enabling encapsulation, validation, and computed logic.

We explored auto-properties, read-only and write-only properties, backing fields, computed properties, expression-bodied syntax, and access modifiers on accessors. We also discussed how to design properties that are clear, safe, and aligned with object-oriented principles.

As you continue building classes in C#, use properties to expose meaningful data, enforce rules, and protect internal state. They are a key tool for designing robust and maintainable object-oriented systems.

In the next article, we’ll explore Auto-Properties - how to simplify property declarations and when to use them effectively.