C# - Encapsulation
C# - Encapsulation
Section titled “C# - Encapsulation”Encapsulation is one of the four fundamental principles of Object-Oriented Programming (OOP). It refers to the bundling of data (fields or attributes) with the methods (or functions) that operate on that data into a single unit, or ‘class’. More importantly, it involves restricting direct access to an object’s internal state. This is also known as information hiding.
By hiding implementation details, encapsulation helps manage complexity, increases robustness (by preventing external code from putting the object into an invalid state), and improves maintainability.
Implementing Encapsulation with Properties
Section titled “Implementing Encapsulation with Properties”In C#, encapsulation is primarily achieved using properties in conjunction with access modifiers. Instead of exposing data fields directly, we expose them through properties, which look like fields but contain logic in get and set accessors.
The Problem: Public Fields
Section titled “The Problem: Public Fields”Exposing fields publicly is considered bad practice because you lose all control over the data. Any external code can change the field to any value, potentially breaking the object’s internal rules (invariants).
public class Employee{ // BAD PRACTICE: Public field can be set to an invalid value. public double salary;}
var emp = new Employee();emp.salary = -5000; // Uncontrolled, invalid state!The Solution: Properties with Backing Fields
Section titled “The Solution: Properties with Backing Fields”A property with a backing field allows you to add validation logic. The data is stored in a private field, and access is controlled through a public property.
public class Employee{ // 1. Private backing field to store the data private double _salary;
// 2. Public property to control access public double Salary { get { return _salary; } // The 'get' accessor returns the private field's value set { // The 'set' accessor validates the incoming 'value' if (value < 0) { throw new ArgumentException("Salary cannot be negative."); } _salary = value; } }}
var emp = new Employee();emp.Salary = 50000; // OKConsole.WriteLine($"Employee Salary: {emp.Salary}");
try{ emp.Salary = -100; // Throws an ArgumentException}catch (Exception ex){ Console.WriteLine(ex.Message);}When the above code is compiled and executed, it produces the following result:
Employee Salary: 50000Salary cannot be negative.Modern C# Simplification: Auto-Implemented Properties
Section titled “Modern C# Simplification: Auto-Implemented Properties”If you don’t need custom validation logic, C# provides a much more concise syntax called auto-implemented properties. The compiler automatically generates the private backing field for you.
public class Car{ // The compiler creates a hidden private backing field. public string Model { get; set; } public int Year { get; set; }}
var myCar = new Car { Model = "Tesla Model 3", Year = 2023 };Console.WriteLine($"My car: {myCar.Year} {myCar.Model}");Creating Immutable Objects with init
Section titled “Creating Immutable Objects with init”To create immutable (unchangeable) objects, you can use init-only setters (available in C# 9+). This ensures a property can only be set during object initialization.
public class Transaction{ public Guid TransactionId { get; init; } public decimal Amount { get; init; } public DateTime Timestamp { get; init; }}
var tx = new Transaction{ TransactionId = Guid.NewGuid(), Amount = 19.99m, Timestamp = DateTime.UtcNow};
// The following line would cause a compile-time error:// tx.Amount = 25.00m; // Error: Init-only property can only be assigned in an initializer.Access Modifiers
Section titled “Access Modifiers”Access modifiers define the visibility of a class and its members. They are the core mechanism for enforcing encapsulation.
public: The member is accessible from anywhere. No restrictions.private: The member is only accessible within the same class. This is the default for class members and provides the highest level of encapsulation.protected: The member is accessible within the same class and by derived (child) classes.internal: The member is accessible only within the same assembly (.dll or .exe). This is the default for classes.protected internal: A combination ofprotectedORinternal. The member is accessible by derived classes OR any class in the same assembly.private protected: A combination ofprivateANDprotected. The member is accessible only by derived classes that are also in the same assembly.