C# (.NET 8 LTS) track

Lesson 07 · Part 2: Object-Oriented Programming

Properties and Encapsulation

Protect object state with properties, init-only setters, required members and validation.

Part 2: Object-Oriented Programming5 min readFree lesson

In the previous section, we protected data with the private field + public method (GetBalance, etc.). There is a separate property in C# that handles this pattern neatly. A member looks like a field on the outside but acts like a method on the inside.

Learning Objectives

  • Know the meaning and operation of auto-property({ get; set; })
  • Create a property that can be set only once with an init accessor
  • Know the difference between a readonly field and the required modifier (C# 11)
  • Validate values in a full property with a backing field

Key concepts

1) Auto-properties

class Person
{
    public string Name { get; set; } = "";   //Connect with automatically created hidden fields
    public int Age { get; set; }
}

The compiler creates internal hidden fields such as _name and _age and automatically creates get/set.

2) Read-only properties with get-only accessors

public string Name { get; }            //Can only be set in constructor

Once an object is created, it cannot be changed.

3) init-only setters (C# 9+)

public string Name { get; init; } = "";

Can only be set at object initialization (new Person { Name = "A" }) or within the constructor. After that, it is read-only. This is useful when creating immutable objects.

var p = new Person { Name = "Alice" };
//p.Name = "Bob";   // compile error

4) The required modifier (C# 11)

This indicates that “this property must be given a value when creating an object.”

class User
{
    public required string Email { get; init; }
}

var u = new User { Email = "[email protected]" };  // OK
//var u2 = new User();                  // compile error

It has both the safety of constructor + init and the convenience of object initialization syntax.

5) readonly field

A keyword that prevents the use of the field itself, not the property, only once.

class Circle
{
    public readonly double Pi = 3.14159;
}

Values can only be assigned in the constructor. It is usually used for values ​​close to constants.

6) full property — backing field + verification

Value verification is not possible with auto-property alone. If verification is necessary, place a backing field directly and place the logic in set.

class Temperature
{
    private double celsius;

    public double Celsius
    {
        get => celsius;
        set
        {
            if (value < -273.15)
                throw new ArgumentException("Below absolute zero");
            celsius = value;
        }
    }
}

value in set is a reserved parameter that refers to the “incoming new value”.

See as example

Example 1 — AutoProperty: most basic form

// Program.cs
using CodingNow.Lecture.Oop07;

var p = new Person();
p.Name = "Alice";
p.Age = 30;
Console.WriteLine($"{p.Name} / {p.Age}");
// Person.cs
namespace CodingNow.Lecture.Oop07;

internal class Person
{
    public string Name { get; set; } = "";
    public int Age { get; set; }
}

Execution result

Alice / 30

Note: Although it seems similar to exposing the field directly, it still leaves room to add logic to set later.

Example 2 — GetInit: Object initialization + init

// Program.cs
using CodingNow.Lecture.Oop07;

var alice = new Person { Name = "Alice", Age = 30 };
Console.WriteLine($"{alice.Name} / {alice.Age}");

//alice.Name = "Bob";   // Since it is init, it cannot be changed afterward (compilation error)
// Person.cs
namespace CodingNow.Lecture.Oop07;

internal class Person
{
    public string Name { get; init; } = "";
    public int Age { get; init; }
}

Execution result

Alice / 30

Memo: Values can only be set within the object initialization statement new Person { ... }, after which they are read-only.

Example 3 — RequiredProp: Required properties

// Program.cs
using CodingNow.Lecture.Oop07;

var u = new User { Email = "[email protected]" };
Console.WriteLine(u.Email);

//var u2 = new User();   // Compilation error because email was not provided
// User.cs
namespace CodingNow.Lecture.Oop07;

internal class User
{
    public required string Email { get; init; }
    public string DisplayName { get; init; } = "anonymous";
}

Execution result

[email protected]

Memo: Properties marked with required must be given a value when creating an object. It is safe without a constructor.

Example 4 — FullProperty: With value validation

// Program.cs
using CodingNow.Lecture.Oop07;

var t = new Temperature();
t.Celsius = 25;
Console.WriteLine($"{t.Celsius}°C");

try
{
    t.Celsius = -500;   //Below absolute zero → exception
}
catch (ArgumentException ex)
{
    Console.WriteLine($"Error: {ex.Message}");
}
// Temperature.cs
namespace CodingNow.Lecture.Oop07;

internal class Temperature
{
    private double celsius;

    public double Celsius
    {
        get => celsius;
        set
        {
            if (value < -273.15)
                throw new ArgumentException("It cannot go below absolute zero (-273.15°C).");
            celsius = value;
        }
    }
}

Execution result

25°C
Error: Cannot go below absolute zero (-273.15°C).

Memo: Externally, it is simply used as a field like t.Celsius = 25;, but internally, a method is called. This is the essence of properties.

Common Mistakes

  1. Make fields public like public string Name; — use properties as much as possible.
  2. It is init, but you are trying to reassign it from outside — only possible at object initialization or within the constructor.
  3. Creating an object by omitting a property with required → Compilation error.
  4. Within set, it calls itself again like Celsius = value;, resulting in infinite recursion (StackOverflow). Be sure to substitute it into the backing field (celsius).
  5. Trying to change the readonly field in a regular method rather than a constructor — compilation error.

Cleanup

  • A property is a “method that looks like a field”. You can hide the internal logic while keeping the external interface simple.
  • init for immutable data, required for required input, full property + backing field if verification is required.
  • The essence of encapsulation is to prevent “the outside world from tampering with the internal state of an object.”

Try it yourself

cd src/AutoProperty
dotnet run

cd ../GetInit
dotnet run

cd ../RequiredProp
dotnet run

cd ../FullProperty
dotnet run

Example files

The runnable files used in this lesson. Copy them into your own project and experiment.

src/AutoProperty/AutoProperty.csprojText
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <RootNamespace>CodingNow.Lecture.Oop07</RootNamespace>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>
src/AutoProperty/Person.csC#
namespace CodingNow.Lecture.Oop07;

internal class Person
{
    //auto-property: The compiler automatically creates hidden fields and get/set.
    public string Name { get; set; } = "";
    public int Age { get; set; }
}
src/AutoProperty/Program.csC#
using CodingNow.Lecture.Oop07;

var p = new Person();
p.Name = "Alice";
p.Age = 30;

Console.WriteLine($"{p.Name} / {p.Age}");
src/FullProperty/FullProperty.csprojText
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <RootNamespace>CodingNow.Lecture.Oop07</RootNamespace>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>
src/FullProperty/Program.csC#
using CodingNow.Lecture.Oop07;

var t = new Temperature();
t.Celsius = 25;
Console.WriteLine($"{t.Celsius}°C");

try
{
    t.Celsius = -500;   //Below absolute zero → exception occurs
}
catch (ArgumentException ex)
{
    Console.WriteLine($"Error: {ex.Message}");
}
src/FullProperty/Temperature.csC#
namespace CodingNow.Lecture.Oop07;

internal class Temperature
{
    //backing field: actual data is stored here
    private double celsius;

    public double Celsius
    {
        get => celsius;
        set
        {
            //The value in the set is a reserved parameter that points to the “new value coming in.”
            if (value < -273.15)
                throw new ArgumentException("It cannot go below absolute zero (-273.15°C).");
            celsius = value;
        }
    }
}
src/GetInit/GetInit.csprojText
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <RootNamespace>CodingNow.Lecture.Oop07</RootNamespace>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>
src/GetInit/Person.csC#
namespace CodingNow.Lecture.Oop07;

internal class Person
{
    //init: Can only be set at the time of object initialization or within the constructor. After that, it is read-only.
    public string Name { get; init; } = "";
    public int Age { get; init; }
}
src/GetInit/Program.csC#
using CodingNow.Lecture.Oop07;

//Values can be set only in object initialization statements.
var alice = new Person { Name = "Alice", Age = 30 };
Console.WriteLine($"{alice.Name} / {alice.Age}");

//alice.Name = "Bob";   // Since it is init, it cannot be changed afterward (compilation error occurs if you uncomment it)
src/RequiredProp/Program.csC#
using CodingNow.Lecture.Oop07;

//The required property must be given a value when initializing an object.
var u = new User { Email = "[email protected]" };
Console.WriteLine($"{u.Email} / {u.DisplayName}");

//var u2 = new User();   // Missing Email → Compilation error
src/RequiredProp/RequiredProp.csprojText
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <RootNamespace>CodingNow.Lecture.Oop07</RootNamespace>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>
src/RequiredProp/User.csC#
namespace CodingNow.Lecture.Oop07;

internal class User
{
    //required: A property that must be given a value when creating an object (C# 11+)
    public required string Email { get; init; }

    //There is a default value so it can be set optionally.
    public string DisplayName { get; init; } = "anonymous";
}

Assignment

Problem 1 — Temperature (Absolute Zero Verification)

  • Reference solution: shown below
  • Core concepts: full property, backing field, verification within set

Requirements

  • Create a Celsius property in the Temperature class.
  • When setting the value, if it is less than -273.15, ArgumentException is thrown.
  • Also add the Fahrenheit property (read-only). Formula: °F = °C × 9/5 + 32.
  • Set normal and abnormal values in Program.cs and output them.

Expected output

0°C = 32°F
100°C = 212°F
Error: Cannot go below absolute zero (-273.15°C).

Hint

  • Set the backing field to private double celsius;.
  • Fahrenheit is calculated like get => celsius * 9 / 5 + 32;.

Problem 2 — Product (validating price above 0)

  • Reference solution: shown below
  • Core concepts: required, init, full property + verification

Requirements

  • Create the Product class.
  • Name is made from required string ... { get; init; }.
  • Make Price a full property, and if you set a negative value, it becomes ArgumentException.
  • Create with object initialization syntax new Product { Name = "...", Price = ... }.

Expected output

Apple / 1,500 won
Pear / 0 won
Error: Price must be greater than or equal to 0.

Hint

  • The backing field of Price is private int price;.
  • If you want to set the initial price to 0, there is no need to give a separate default value (int default value is 0).

Check the correct answer

After solving it yourself, compare it with the reference solution below.

Show the reference solution

Try the assignment yourself first — comparing your attempt with a working solution is where most of the learning happens.

Homework01/Homework01.csprojText
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <RootNamespace>CodingNow.Lecture.Oop07</RootNamespace>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>
Homework01/Program.csC#
using CodingNow.Lecture.Oop07;

var t = new Temperature();

t.Celsius = 0;
Console.WriteLine($"{t.Celsius}°C = {t.Fahrenheit}°F");

t.Celsius = 100;
Console.WriteLine($"{t.Celsius}°C = {t.Fahrenheit}°F");

try
{
    t.Celsius = -300;
}
catch (ArgumentException ex)
{
    Console.WriteLine($"Error: {ex.Message}");
}
Homework01/Temperature.csC#
namespace CodingNow.Lecture.Oop07;

internal class Temperature
{
    private double celsius;

    public double Celsius
    {
        get => celsius;
        set
        {
            if (value < -273.15)
                throw new ArgumentException("It cannot go below absolute zero (-273.15°C).");
            celsius = value;
        }
    }

    //Read-only computed properties
    public double Fahrenheit => celsius * 9 / 5 + 32;
}
Homework02/Homework02.csprojText
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <RootNamespace>CodingNow.Lecture.Oop07</RootNamespace>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>
Homework02/Product.csC#
namespace CodingNow.Lecture.Oop07;

internal class Product
{
    //required: A value must be given when creating an object.
    public required string Name { get; init; }

    private int price;
    public int Price
    {
        get => price;
        set
        {
            if (value < 0)
                throw new ArgumentException("Price must be greater than or equal to 0.");
            price = value;
        }
    }
}
Homework02/Program.csC#
using CodingNow.Lecture.Oop07;

var apple = new Product { Name = "apple" };
apple.Price = 1500;
Console.WriteLine($"{apple.Name} / {apple.Price} Won");

var pear = new Product { Name = "ship" };
Console.WriteLine($"{pear.Name} / {pear.Price} Won");

try
{
    pear.Price = -100;
}
catch (ArgumentException ex)
{
    Console.WriteLine($"Error: {ex.Message}");
}

Learn this lesson with a mentor

Work through the C# (.NET 8 LTS) track on your own, or join a guided cohort at our Pitampura campus and online.

Call NowEnroll Now