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
initaccessor - Know the difference between a
readonlyfield and therequiredmodifier (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
- Make fields public like
public string Name;— use properties as much as possible. - It is
init, but you are trying to reassign it from outside — only possible at object initialization or within the constructor. - Creating an object by omitting a property with
required→ Compilation error. - Within
set, it calls itself again likeCelsius = value;, resulting in infinite recursion (StackOverflow). Be sure to substitute it into the backing field (celsius). - Trying to change the
readonlyfield 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.
initfor immutable data,requiredfor 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