Properties & Auto-Properties

Reviewed & published by Brayan K

Objects hold data — but exposing that data as raw public fields gives the outside world unchecked access to your object's insides. Properties are C#'s elegant solution: they look like fields to anyone using them, but secretly run get and set logic so you can validate, compute, or lock down a value. They're the polished public surface of every well-designed class.

Part of the free C# course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn

💡 Real-World Analogy

A property is like a bank teller standing in front of the vault. The money (the private field) is locked away; you don't reach in yourself. You ask the teller to deposit (the set) and they check the amount is valid first; you ask for the balance (the get) and they hand back the right figure. A raw public field is like leaving the vault door open — anyone can shove in any value, valid or not. The teller (property) keeps everything controlled and safe.

1. Auto-Properties

The everyday property is the auto-property: public string Name { get; set; }. The compiler quietly creates a hidden backing field and the trivial get/set logic. You can also write a computed read-only property with =>, which calculates its value from other data every time it's read — like IsAdult => Age >= 18.

using System;

class Person
{
    // AUTO-PROPERTIES: the compiler creates a hidden backing field
    // plus simple get/set logic for you. Clean and concise.
    public string Name { get; set; }
    public int    Age  { get; set; }

    // A read-only computed property: it has a getter only and is
    // calculated from other data each time you read it.
    public bool IsAdult => Age >= 18;
}

class Program
{
    static void Main()
    {
        Person p = new Person();
        p.Name = "Brenda";   // calls the SET accessor
        p.Age  = 34;

        Console.WriteLine($"{p.Name} is {p.Age}.");  // Brenda is 34.
        Console.WriteLine($"Adult? {p.IsAdult}");    // Adult? True  (computed)

        p.Age = 12;
        Console.WriteLine($"Adult? {p.IsAdult}");    // Adult? False (recomputed)
    }
}

// ✅ Expected output:
//    Brenda is 34.
//    Adult? True
//    Adult? False

2. Full Properties with Validation

When you need real logic, expand to a full property backed by a private field. Inside the set accessor, the keyword value is the incoming assignment — that's where you validate. This is the whole point of properties: a public field can't reject a bad value, but a property can throw or clamp before the data is ever stored.

using System.Globalization;
using System;

class BankAccount
{
    // A PRIVATE backing field stores the real value.
    private decimal _balance;

    // A FULL property wraps it with logic. The setter validates input.
    public decimal Balance
    {
        get => _balance;                 // expression-bodied getter
        set
        {
            if (value < 0)
                throw new ArgumentException("Balance can't be negative.");
            _balance = value;            // 'value' is the incoming assignment
        }
    }
}

class Program
{
    static void Main()
    {
        // Currency formatting follows the thread's culture: {value:C} prints
        // £ in London, $ in Boston and € in Paris. Pin it when the output has
        // to be the same everywhere — as it does on a page that shows you the
        // result.
        CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("en-GB");

        var acc = new BankAccount();
        acc.Balance = 250.00m;                  // passes validation
        Console.WriteLine($"Balance: {acc.Balance:C}"); // Balance: £250.00

        try
        {
            acc.Balance = -50;                  // setter rejects this
        }
        catch (ArgumentException ex)
        {
            Console.WriteLine($"Rejected: {ex.Message}");
            // Rejected: Balance can't be negative.
        }
    }
}

// ✅ Expected output:
//    Balance: £250.00
//    Rejected: Balance can't be negative.

Your turn. Give Product two auto-properties and one computed property. The hints in the comments show the exact syntax.

using System;

class Product
{
    // 🎯 YOUR TURN — give Product two auto-properties and one computed one.

    // 1) An auto-property string called Name
    public string Name { ___ }     // 👉 get; set;

    // 2) An auto-property decimal called Price
    public decimal Price { ___ }   // 👉 get; set;

    // 3) A computed read-only property PriceWithTax = Price * 1.2
    public decimal PriceWithTax => ___;   // 👉 Price * 1.2m
}

class Program
{
    static void Main()
    {
        var item = new Product { Name = "Mug", Price = 10.00m };
        Console.WriteLine($"{item.Name}: {item.PriceWithTax:C}");
        // ✅ Expected output:  Mug: £12.00
    }
}

3. Controlling Access

Properties let you tune who can read and write. A private set means outsiders can read the value but only the class itself can change it — perfect for a temperature that must go through validation. A computed property derived from it (Fahrenheit from Celsius) always stays in sync because it's recalculated on every read.

using System;

class Temperature
{
    // Public getter, PRIVATE setter: read freely, but only this class
    // can change it (here, via the SetCelsius method).
    public double Celsius { get; private set; }

    // Computed property derived from Celsius — always in sync.
    public double Fahrenheit => Celsius * 9 / 5 + 32;

    public void SetCelsius(double c)
    {
        if (c < -273.15)
            throw new ArgumentException("Below absolute zero!");
        Celsius = c;
    }
}

class Program
{
    static void Main()
    {
        var t = new Temperature();
        t.SetCelsius(25);
        Console.WriteLine($"{t.Celsius}°C = {t.Fahrenheit}°F");
        // 25°C = 77°F

        // t.Celsius = 100;  // ❌ won't compile: the setter is private
    }
}

// ✅ Expected output:
//    25°C = 77°F

🔎 Deep Dive: init-only properties

Modern C# adds the init accessor: a property you can set only during object creation (in an object initialiser or constructor), after which it's immutable. It gives you the best of both worlds — flexible construction with object initialisers, but no accidental mutation later. Ideal for IDs and other "set once" data.

using System;

class Order
{
    // 'init' lets you set the value ONLY during object creation,
    // then it's immutable — great for read-only-after-construction data.
    public int    Id      { get; init; }
    public string Item    { get; init; }
    public int    Quantity { get; set; }   // this one stays editable
}

class Program
{
    static void Main()
    {
        // Object initialiser sets init-only props at creation time.
        var order = new Order { Id = 1001, Item = "Notebook", Quantity = 3 };
        Console.WriteLine($"Order #{order.Id}: {order.Quantity}x {order.Item}");
        // Order #1001: 3x Notebook

        order.Quantity = 5;       // ✅ allowed — Quantity has a normal setter
        // order.Id = 9999;       // ❌ compile error — Id is init-only

        Console.WriteLine($"Updated quantity: {order.Quantity}"); // 5
    }
}

// ✅ Expected output:
//    Order #1001: 3x Notebook
//    Updated quantity: 5

A get-only auto-property (public int Id { get; }) is even stricter — it can only be assigned in the constructor. Reach for init when you want object-initialiser syntax, and get-only when the value always comes from constructor logic.

🧩 Reorder Challenge

These lines declare a Circle class with a computed Area and use it. Order them so the program prints an area of about 78.5 for radius 5.

        public double Area => 3.14159 * Radius * Radius;
class Circle
{
        public double Radius { get; set; }
}
// then in Main:
var c = new Circle { Radius = 5 };
Console.WriteLine($"Area: {c.Area:F1}");
class Circle
{
    public double Radius { get; set; }
    public double Area => 3.14159 * Radius * Radius;
}
// in Main:
var c = new Circle { Radius = 5 };
Console.WriteLine($"Area: {c.Area:F1}");

Why: the class opening brace comes first, then its members (Radius before Area, though members can appear in any order inside the class), then the closing brace. Radius must be set before Area is read — and because Area is computed, it uses whatever Radius holds at read time: 3.14159 × 25 ≈ 78.5.

🧠 Quick Recall

1. What does this print?

// class Box { public int W { get; set; }
//             public int Area => W * W; }
var b = new Box { W = 4 };
Console.WriteLine(b.Area);

16 — Area is computed as W * W, and 4 * 4 = 16. It's recalculated each time you read it.

2. Does this compile?

// class T { public int X { get; private set; } }
var t = new T();
t.X = 5;

No — the setter is private, so code outside the class can't assign X. Only members of T can set it.

3. What's the result?

// setter throws if value < 0
acc.Balance = 100;
acc.Balance = -1;  // inside a try/catch
Console.WriteLine(acc.Balance);

100 — the second assignment throws before _balance is overwritten, so the stored value stays at the last valid one, 100.

Pro Tips

Common Errors (and the fix)

Mini-Challenge: Validated Rectangle

Build a Rectangle with validated Width/Height and computed Area/Perimeter. The outline is in the comments.

using System;

class Rectangle
{
    // 🎯 MINI-CHALLENGE: a Rectangle with validation and computed values.
    // 1. Auto-properties Width and Height (double), but their SETTERS must
    //    reject values <= 0 (so make them full properties with a private
    //    backing field, or validate in a method — your choice).
    // 2. A computed read-only property Area => Width * Height.
    // 3. A computed read-only property Perimeter => 2 * (Width + Height).
    //
    // In Main: create a 4 x 3 rectangle and print:
    //    Area: 12, Perimeter: 14
    // Then try setting a negative width and catch the exception.

    // your code here
}

class Program
{
    static void Main()
    {
        // build and test your Rectangle here
    }
}

🎉 Lesson Complete

Practice quiz

What is the key difference between a field and a property?

  • A field runs get/set logic; a property stores data directly
  • They are identical
  • A property looks like a field but runs get/set methods that can validate or compute
  • A property cannot be public

Answer: A property looks like a field but runs get/set methods that can validate or compute. A property looks like a field from outside but is really a pair of get/set accessors that can run logic.

What does an auto-property 'public string Name { get; set; }' do?

  • Creates a hidden backing field and trivial get/set for you
  • Throws at runtime
  • Makes Name read-only
  • Requires you to write a backing field

Answer: Creates a hidden backing field and trivial get/set for you. An auto-property tells the compiler to generate a hidden backing field plus the simple get/set logic.

Inside a setter, what does the 'value' keyword represent?

  • The previous value
  • The backing field name
  • A default value
  • The incoming assignment being set

Answer: The incoming assignment being set. 'value' is the incoming assignment in a set accessor — that's where you validate before storing it.

What does a computed property like 'public bool IsAdult => Age >= 18;' do?

  • Stores a fixed value
  • Recalculates from other data each time it's read
  • Has a settable backing field
  • Runs only once

Answer: Recalculates from other data each time it's read. An expression-bodied computed property has a getter only and is recalculated from other data on each read.

What does 'public double Celsius { get; private set; }' allow?

  • Anyone can read it, but only the class can change it
  • Anyone can read and write it
  • Nobody can read it
  • It is immutable forever

Answer: Anyone can read it, but only the class can change it. A private set means outsiders can read the value but only the declaring class can assign it.

What does the 'init' accessor allow?

  • Setting the value anytime
  • Reading only
  • Setting the value only during object creation, then immutable
  • Calling a method

Answer: Setting the value only during object creation, then immutable. init lets you set the property in an object initialiser or constructor, after which it is immutable.

Why write 'set => _balance = value;' instead of 'set => Balance = value;'?

  • They behave the same
  • Assigning the property to itself recurses forever (StackOverflow)
  • The field version is read-only
  • value is invalid in a setter

Answer: Assigning the property to itself recurses forever (StackOverflow). Assigning the property inside its own setter calls the setter again forever; assign the private backing field instead.

Why prefer a property over a public field in most cases?

  • Fields are faster to type
  • Fields can validate; properties cannot
  • Properties cannot be public
  • A property gives a stable public surface you can later add validation to without breaking callers

Answer: A property gives a stable public surface you can later add validation to without breaking callers. Even a simple auto-property lets you later add validation, make the setter private, or compute the value without breaking calling code.

What can a get-only auto-property 'public int Id { get; }' be assigned in?

  • Anywhere
  • Only the constructor or its initialiser
  • Only a setter
  • Never

Answer: Only the constructor or its initialiser. A get-only auto-property can only be assigned in the constructor (or field initialiser), making it immutable afterwards.

If a setter throws when value < 0, what is the stored value after 'acc.Balance = 100;' then a rejected 'acc.Balance = -1;'?

  • -1
  • 0
  • 100
  • It throws on read

Answer: 100. The second assignment throws before overwriting the backing field, so the last valid value 100 remains.

Continue this course

Frequently asked questions

What is the difference between a field and a property in C#?

A field is a raw variable that stores data directly inside an object. A property looks like a field from the outside but is really a pair of get and set methods that wrap a value, letting you run validation or logic when the value is read or written. The convention is to keep fields private and expose data through public properties.

What does an auto-property actually do?

An auto-property like public string Name { get; set; } tells the compiler to create a hidden private backing field and the simple get and set logic for you. It is shorthand for the full pattern. You start with an auto-property and only expand it to a full property when you need real logic such as validation.

How do I make a property read-only?

Use a get-only auto-property, public int Id { get; }, which can only be assigned in the constructor or its initialiser. For a property that can be set during object creation but is immutable afterwards, use the init accessor, public int Id { get; init; }, available in modern C#. Both prevent later reassignment.

When should I use a property versus a public field?

Almost always use a property. Even a simple auto-property gives you a stable public surface: you can later add validation, make the setter private, or compute the value without breaking any calling code. A public field locks you out of all of that, which is why public fields are discouraged for anything beyond constants.