Skip to main content

.NET Properties

Introduction

Properties are a fundamental feature in .NET that provide a flexible mechanism to read, write, or compute the values of private fields. They enable controlled access to class state while hiding implementation details, which is a key principle of encapsulation in object-oriented programming.

Properties in .NET allow you to expose class members in a way that appears like regular fields to the outside world, but they can contain additional logic such as validation, calculated values, and varying access levels.

Basic Property Syntax

A property in .NET consists of:

  • A private field (also called a backing field)
  • A getter method (to read the value)
  • A setter method (to assign a value)

Here's the basic syntax:

csharp
private int _myField; // backing field

public int MyProperty
{
get { return _myField; } // getter
set { _myField = value; } // setter
}

The value keyword in the setter represents the value being assigned to the property.

Auto-Implemented Properties

For simple properties that don't require additional logic, C# offers auto-implemented (or auto) properties, which simplify the syntax by having the compiler generate the backing field automatically:

csharp
public int MyProperty { get; set; }

This is equivalent to the longer form but requires less code.

Property Access Modifiers

Properties can have different access levels for getters and setters:

csharp
public int Age 
{
get; // public getter
private set; // private setter - can only be set within the class
}

Common combinations include:

  • public get; private set; - Read from anywhere, set only within the class
  • public get; protected set; - Read from anywhere, set only within the class or derived classes
  • private get; private set; - Used with expression-bodied properties or calculated properties

Property Usage Example

Let's create a Person class with properties to understand how they work:

csharp
using System;

public class Person
{
// Backing field
private string _name;

// Property with validation logic
public string Name
{
get { return _name; }
set
{
if (string.IsNullOrEmpty(value))
throw new ArgumentException("Name cannot be empty");
_name = value;
}
}

// Auto-implemented property
public DateTime DateOfBirth { get; set; }

// Read-only calculated property
public int Age
{
get
{
return DateTime.Today.Year - DateOfBirth.Year -
(DateTime.Today.DayOfYear < DateOfBirth.DayOfYear ? 1 : 0);
}
}

// Property with different access modifiers
public string Email { get; private set; }

public Person(string email)
{
Email = email;
}

// Method to update email (since the setter is private)
public void UpdateEmail(string newEmail)
{
if (newEmail.Contains("@"))
Email = newEmail;
}
}

Here's how we can use this class:

csharp
// Create a new Person
Person person = new Person("[email protected]");

// Setting properties
person.Name = "John Doe";
person.DateOfBirth = new DateTime(1985, 5, 10);

// Reading properties
Console.WriteLine($"Name: {person.Name}");
Console.WriteLine($"DoB: {person.DateOfBirth.ToShortDateString()}");
Console.WriteLine($"Age: {person.Age}");
Console.WriteLine($"Email: {person.Email}");

// Update email using method (since setter is private)
person.UpdateEmail("[email protected]");
Console.WriteLine($"Updated email: {person.Email}");

// This would cause an error:
// person.Email = "[email protected]"; // Won't compile - setter is private

Output:

Name: John Doe
DoB: 5/10/1985
Age: 38 (value will vary based on current date)
Email: [email protected]
Updated email: [email protected]

Expression-Bodied Properties

For simple computed properties, C# 6.0 introduced expression-bodied properties:

csharp
public string FullName => $"{FirstName} {LastName}";

This is shorthand for a read-only property with a getter only.

Init-Only Properties

C# 9.0 introduced init-only properties, which can only be set during object initialization:

csharp
public string ID { get; init; }

Usage:

csharp
var user = new User { ID = "ABC123" }; // Valid
// user.ID = "XYZ789"; // Invalid - would cause compiler error

Properties vs Fields

Using properties instead of public fields offers several advantages:

  1. Encapsulation: Properties can hide implementation details.
  2. Validation: You can validate values before assigning them.
  3. Calculated Values: Properties can be calculated on demand.
  4. Debugging: You can set breakpoints in property accessors.
  5. Flexibility: You can change implementation without affecting the interface.

Practical Example: Banking Application

Let's see a practical example with a simple bank account class:

csharp
public class BankAccount
{
// Backing fields
private decimal _balance;
private string _accountNumber;

// Properties
public string AccountNumber
{
get { return _accountNumber; }
private set { _accountNumber = value; }
}

public decimal Balance
{
get { return _balance; }
private set { _balance = value; }
}

public string AccountOwner { get; set; }

public bool IsOverdrawn => Balance < 0;

// Constructor
public BankAccount(string accountNumber, string owner, decimal initialDeposit)
{
AccountNumber = accountNumber;
AccountOwner = owner;
Balance = initialDeposit;
}

// Methods that modify the balance
public void Deposit(decimal amount)
{
if (amount <= 0)
throw new ArgumentException("Deposit amount must be positive");

Balance += amount;
}

public bool Withdraw(decimal amount)
{
if (amount <= 0)
throw new ArgumentException("Withdrawal amount must be positive");

if (amount > Balance)
return false;

Balance -= amount;
return true;
}
}

Using this class:

csharp
BankAccount account = new BankAccount("123456789", "Alice Smith", 1000m);

// Deposit money
account.Deposit(500m);
Console.WriteLine($"Balance after deposit: {account.Balance}");

// Withdraw money
bool withdrawalSuccess = account.Withdraw(300m);
Console.WriteLine($"Withdrawal {(withdrawalSuccess ? "succeeded" : "failed")}");
Console.WriteLine($"Current balance: {account.Balance}");

// Try to withdraw too much
withdrawalSuccess = account.Withdraw(2000m);
Console.WriteLine($"Excessive withdrawal {(withdrawalSuccess ? "succeeded" : "failed")}");
Console.WriteLine($"Final balance: {account.Balance}");
Console.WriteLine($"Account is {(account.IsOverdrawn ? "overdrawn" : "in good standing")}");

Output:

Balance after deposit: 1500
Withdrawal succeeded
Current balance: 1200
Excessive withdrawal failed
Final balance: 1200
Account is in good standing

Summary

Properties in .NET provide a powerful mechanism for encapsulating class fields while offering controlled access to them. Key points to remember:

  • Properties combine the accessibility of fields with the control of methods
  • Use auto-implemented properties for simple storage
  • Include validation logic in setters for data integrity
  • Use different access modifiers to control how properties are accessed and modified
  • Calculated properties let you expose derived values
  • Init-only properties allow immutability after initialization

By mastering properties, you'll write more maintainable, secure, and professional C# code, adhering to object-oriented best practices.

Additional Resources

Exercises

  1. Create a Rectangle class with Width and Height properties that include validation (no negative values), and an Area calculated property.

  2. Modify a Student class to include properties for Name, ID (read-only after construction), and GradeAverage (calculated from a private list of grades).

  3. Build a Product class for an e-commerce system with properties for price, name, and stock level, with appropriate encapsulation and validation.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)