Chapter 11: Access Modifiers and Encapsulation
1. What is Encapsulation? (Super Simple Analogy)
Encapsulation means:
- Hiding the internal details of how an object works
- Exposing only the necessary parts to the outside world
- Protecting the data so it can only be changed in controlled ways
Think of a TV remote:
- You can press Power, Volume Up, Channel → these are the public methods/properties you’re allowed to use
- You cannot open the TV and directly change the internal circuits → that’s private/internal stuff
This way, the TV works safely even if you misuse the remote!
In C#, we achieve encapsulation using access modifiers and properties.
2. All Access Modifiers Explained (With Real Examples)
| Modifier | Accessible from | Common Use Case | Real-World Analogy |
|---|---|---|---|
| public | Anywhere (same project, other projects) | Methods & properties users should call | Remote control buttons |
| private | Only inside the same class | Internal fields, helper methods | Internal wires & circuits of the TV |
| protected | Inside the class and derived classes | Fields/methods that children should access | Protected parts that family members can touch |
| internal | Only inside the same assembly (project) | Types/methods used only within your project | Internal company tools – not for outsiders |
| protected internal | Inside the class, derived classes, and same assembly | Rare – when you want both inheritance & assembly access | Special internal family tools |
| private protected | Inside the class and derived classes in the same assembly (C# 7.2+) | Very rare – protected but only within assembly | Family tools that only close relatives can use |
Quick rule of thumb (what most professionals follow):
- Make fields → private
- Make public methods & properties → public
- Use protected only when you plan to inherit
- Use internal when you want to hide something from other projects (DLLs)
3. Properties with Getters & Setters – The Heart of Encapsulation
Never expose fields directly! Always use properties to control access.
A. Auto-implemented Property (Simple & Clean)
|
0 1 2 3 4 5 6 |
public string Name { get; set; } // Compiler creates private backing field |
B. Full Property with Validation (Real Encapsulation)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
class Person { private string _name; private int _age; public string Name { get => _name; // Short for { return _name; } set { if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException("Name cannot be empty or whitespace!"); _name = value.Trim(); // Clean the input } } public int Age { get => _age; set { if (value < 0 || value > 120) throw new ArgumentException("Age must be between 0 and 120!"); _age = value; } } // Read-only property (only getter) public int BirthYear => DateTime.Now.Year - Age; // Computed property (no backing field) public string Greeting => $"Hello, I'm {Name}, {Age} years old!"; } |
Usage:
|
0 1 2 3 4 5 6 7 8 9 10 11 |
var p = new Person(); p.Name = "Webliance"; // OK p.Age = 25; // OK // p.Age = -5; // Throws exception – protected! Console.WriteLine(p.Greeting); // Hello, I'm Webliance, 25 years old! |
4. Real-Life Example – Bank Account with Proper Encapsulation
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
class BankAccount { // Private fields – completely hidden private string _accountNumber; private decimal _balance; // Public read-only properties public string AccountNumber => _accountNumber; public decimal Balance => _balance; // Only getter – cannot set directly! // Constructor – only way to set account number public BankAccount(string accountNumber, decimal initialBalance = 0) { if (string.IsNullOrWhiteSpace(accountNumber)) throw new ArgumentException("Account number required!"); _accountNumber = accountNumber; _balance = initialBalance >= 0 ? initialBalance : 0; } // Controlled methods to change balance public void Deposit(decimal amount) { if (amount <= 0) throw new ArgumentException("Deposit amount must be positive!"); _balance += amount; Console.WriteLine($"Deposited ₹{amount:N2}. New balance: ₹{_balance:N2}"); } public bool Withdraw(decimal amount) { if (amount <= 0) { Console.WriteLine("Withdrawal amount must be positive!"); return false; } if (amount > _balance) { Console.WriteLine("Insufficient funds!"); return false; } _balance -= amount; Console.WriteLine($"Withdrew ₹{amount:N2}. Remaining: ₹{_balance:N2}"); return true; } // Protected method – only derived classes can use protected void LogTransaction(string message) { Console.WriteLine($"[LOG] {DateTime.Now}: {message}"); } } // Derived class example class SavingsAccount : BankAccount { public SavingsAccount(string accNumber, decimal initial = 0) : base(accNumber, initial) { } public void ApplyInterest(decimal rate) { decimal interest = Balance * (rate / 100m); Deposit(interest); LogTransaction($"Applied interest: ₹{interest:N2}"); // Protected method accessible! } } |
5. Mini-Project: Secure Employee Class
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
class Employee { private string _employeeId; private decimal _salary; public string Name { get; set; } public string Department { get; set; } public string EmployeeId => _employeeId; // Read-only public decimal Salary { get => _salary; private set // Only the class can change salary { if (value < 0) throw new ArgumentException("Salary cannot be negative!"); _salary = value; } } public Employee(string id, string name, decimal startingSalary) { _employeeId = id ?? throw new ArgumentNullException(nameof(id)); Name = name ?? throw new ArgumentNullException(nameof(name)); Salary = startingSalary; } public void GiveRaise(decimal percentage) { if (percentage < 0 || percentage > 50) throw new ArgumentException("Raise must be between 0% and 50%!"); decimal increase = Salary * (percentage / 100m); Salary += increase; Console.WriteLine($"{Name} got a {percentage}% raise! New salary: ₹{Salary:N2}"); } } // Usage var emp = new Employee("E12345", "Webliance", 75000); // emp.Salary = 100000; // ERROR! Salary setter is private emp.GiveRaise(10); // OK – controlled raise Console.WriteLine($"ID: {emp.EmployeeId}, Name: {emp.Name}, Salary: ₹{emp.Salary:N2}"); |
Summary – What We Learned Today
- Encapsulation = hide data + expose controlled access
- public → everyone can use
- private → only inside class
- protected → class + derived classes
- internal → same assembly/project
- Properties → best way to control access (get/set with validation)
- Never expose fields directly – always use properties!
Your Homework (Super Practical!)
- Create a new console project called EncapsulationMaster
- Create a class Product with:
- Private fields: _id, _price, _stock
- Public properties: Name, Description
- Read-only: Id, Price, Stock
- Methods: ReduceStock(int quantity) (with checks)
- IncreasePrice(decimal percentage) (with validation)
- Constructor that sets everything safely
- In Program.cs: Create 2–3 products, try to buy some stock, increase price, and show info
Next lesson: Inheritance – we’re going to learn how classes can inherit from each other and become even more powerful!
You’re doing absolutely fantastic! 🎉 Any part confusing? Want more examples with protected or internal? Just tell me — I’m right here for you! 💙
