C# Properties
Introduction
Properties are a fundamental feature in C# that implement encapsulation - one of the core principles of object-oriented programming. Properties provide a flexible mechanism to read, write, or compute the values of private fields. They can be used as if they are public data members, but they are actually special methods called accessors. This enables data to be accessed easily while still promoting safety and flexibility of methods.
In this tutorial, you'll learn:
- What properties are and why they're important
- How to create and use properties
- Different types of properties in C#
- Best practices for implementing properties
Understanding Properties
Why Use Properties?
Before diving into properties, let's understand why they exist. Consider this class with public fields:
public class Person
{
public string name; // Public field, directly accessible
public int age; // Public field, directly accessible
}
Using public fields like this creates several problems:
- No validation: Anyone can set
age
to a negative number - No control: Fields can be changed without any restrictions
- No flexibility: If you later need to calculate or modify how data is accessed, you'd break existing code
Properties solve these issues by acting as intermediaries between your private data and the outside world.
Basic Property Syntax
A basic property consists of:
- A private field (the actual storage)
- A public property (the access point)
- Get and set accessors (the gatekeepers)
Here's the syntax:
public class Person
{
// Private field - stores the actual data
private string _name;
// Public property - provides access to the data
public string Name
{
get { return _name; }
set { _name = value; }
}
}
Here, _name
is our private storage. The Name
property provides controlled access to it through get
and set
accessors. The keyword value
represents the value being assigned during a set operation.
Using Properties
Let's see how to use the properties we just created:
class Program
{
static void Main()
{
Person person = new Person();
// Using the set accessor
person.Name = "John";
// Using the get accessor
Console.WriteLine(person.Name); // Output: John
}
}
To the outside world, using a property looks exactly like using a field, but behind the scenes, our getter and setter methods are running.
Property Types
Auto-Implemented Properties
Since simple get/set properties are so common, C# offers a shorthand syntax:
public class Person
{
// Auto-implemented property
public string Name { get; set; }
}
With auto-implemented properties, C# automatically creates the private field for you. This is perfect when no additional logic is needed in getters and setters.
Read-Only Properties
You can create properties that can only be read but not modified from outside the class:
public class Person
{
private DateTime _birthDate;
public Person(DateTime birthDate)
{
_birthDate = birthDate;
}
// Read-only property
public int Age
{
get
{
return DateTime.Today.Year - _birthDate.Year;
}
}
}
This example shows a read-only property that calculates age based on a birth date. It doesn't have a setter because age isn't something you should directly set - it's computed from the birth date.
Write-Only Properties (Rare)
Though uncommon, you can create write-only properties by providing only a setter:
public class User
{
private string _password;
// Write-only property
public string Password
{
set { _password = HashPassword(value); }
}
private string HashPassword(string input)
{
// Hash the password
return "hashed_" + input;
}
}
Properties with Validation
Properties shine when you need to validate data before storing it:
public class Person
{
private int _age;
public int Age
{
get { return _age; }
set
{
if (value < 0)
{
throw new ArgumentException("Age cannot be negative");
}
_age = value;
}
}
}
Now if anyone tries to set a negative age:
Person person = new Person();
person.Age = -5; // Throws ArgumentException
Expression-Bodied Properties
For simple property implementations, you can use expression body syntax (introduced in C# 6.0):
public class Rectangle
{
public double Length { get; set; }
public double Width { get; set; }
// Expression-bodied read-only property
public double Area => Length * Width;
}
This shorter syntax is especially useful for calculated properties.
Auto-Implemented Properties with Initializers
In C# 6.0 and later, auto-implemented properties can have initializers:
public class Product
{
// Auto-property with an initializer
public string Category { get; set; } = "General";
public decimal Price { get; set; }
}
Init-Only Properties
C# 9.0 introduced init-only properties that can only be set during object initialization:
public class Person
{
public string Name { get; init; }
public DateTime BirthDate { get; init; }
}
You use them like this:
var person = new Person
{
Name = "Maria",
BirthDate = new DateTime(1985, 3, 25)
};
// This would cause a compile-time error:
// person.Name = "Changed"; // Error: Init-only property
Real-World Example: Product Inventory System
Let's see how properties help us create a robust product inventory system:
public class Product
{
// Private backing fields
private string _name;
private decimal _price;
private int _stockQuantity;
// Properties with validation
public string Name
{
get { return _name; }
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("Product name cannot be empty");
_name = value;
}
}
public decimal Price
{
get { return _price; }
set
{
if (value < 0)
throw new ArgumentException("Price cannot be negative");
_price = value;
}
}
public int StockQuantity
{
get { return _stockQuantity; }
set
{
if (value < 0)
throw new ArgumentException("Stock cannot be negative");
_stockQuantity = value;
}
}
// Calculated property
public bool IsInStock => StockQuantity > 0;
// Read-only property with logic
public string Status
{
get
{
if (StockQuantity > 10) return "Available";
if (StockQuantity > 0) return "Low Stock";
return "Out of Stock";
}
}
}
Using this class:
class Program
{
static void Main()
{
try
{
Product laptop = new Product();
laptop.Name = "Laptop Pro";
laptop.Price = 1299.99m;
laptop.StockQuantity = 5;
Console.WriteLine($"Product: {laptop.Name}");
Console.WriteLine($"Price: ${laptop.Price}");
Console.WriteLine($"Available: {laptop.IsInStock}");
Console.WriteLine($"Status: {laptop.Status}");
// This would throw an exception:
// laptop.StockQuantity = -3;
}
catch (ArgumentException ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
}
Output:
Product: Laptop Pro
Price: $1299.99
Available: True
Status: Low Stock
This example demonstrates how properties help us create a robust system:
- Data validation prevents invalid values
- Calculated properties (
IsInStock
) derive values from other properties - Complex logic can be encapsulated in properties (
Status
)
Best Practices for Properties
- Use meaningful names: Properties should have clear, descriptive names
- Follow conventions: Use PascalCase for property names (
FirstName
notfirstName
) - Consider immutability: Make properties read-only when they shouldn't change
- Validate input: Check input in setters to maintain object validity
- Keep accessors simple: Avoid complex or time-consuming operations in getters
- Use auto-implemented properties when no additional logic is needed
- Consider init-only properties for immutable objects
Summary
Properties in C# provide a powerful way to implement encapsulation while maintaining simple syntax for accessing object data. They offer:
- Protection of data through controlled access
- Validation to ensure data integrity
- The ability to compute values on-the-fly
- A clean, field-like syntax for method-like functionality
By mastering properties, you'll create more robust, maintainable C# code that follows best practices in object-oriented programming.
Exercises
- Create a
BankAccount
class with properties forAccountNumber
(read-only) andBalance
(with validation to prevent negative values) - Build a
Temperature
class that stores temperature in Celsius but provides properties to get/set it in Fahrenheit - Implement a
Student
class with auto-implemented properties for name and ID, and a calculated property for determining if they are passing (GPA >= 2.0) - Create a
Password
class with a write-only property that hashes passwords and a method to verify passwords
Additional Resources
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)