C# > Object-Oriented Programming (OOP) > Encapsulation > Access Modifiers for Members
More Encapsulation with Access Modifiers
A more advanced example of encapsulation using different access modifiers and demonstrating information hiding.
Code Example
In this example: * `name` and `salary` are `private` fields, providing data hiding. * `Name` is a `public` property for controlled access to the `name` field. * `Salary` is a `protected` property. It is accessible within the `Employee` class and any derived classes (like `Manager`). * `employeeId` is a `readonly` private field, initialized in the constructor and cannot be modified after that. * `GetEmployeeDetails` is an `internal` method, only accessible within the same assembly. * `CalculateBonus` is a `protected internal virtual` method. It can be accessed from within the same assembly or from derived classes in another assembly, and it's virtual, so it can be overridden. * The `Manager` class inherits from `Employee`. It can access the `Salary` property because it's `protected`. It also overrides the `CalculateBonus` method to provide a different bonus calculation. * The `SetSalary` method of the `Manager` class can access protected members of the base `Employee` class. This demonstrates how different access modifiers can be used to control the visibility and accessibility of class members, achieving encapsulation and information hiding.
using System;
public class Employee
{
private string name;
private decimal salary;
private readonly string employeeId;
public Employee(string name, decimal salary, string employeeId)
{
this.name = name;
this.salary = salary;
this.employeeId = employeeId;
}
public string Name
{
get { return name; }
set { name = value; }
}
protected decimal Salary
{
get { return salary; }
set { salary = value; }
}
internal string GetEmployeeDetails()
{
return $"Name: {name}, Salary: {salary}";
}
public string EmployeeId
{
get { return employeeId; }
}
protected internal virtual void CalculateBonus()
{
decimal bonus = salary * 0.1m;
Console.WriteLine($"Base Employee Bonus: {bonus:C}");
}
}
public class Manager : Employee
{
public Manager(string name, decimal salary, string employeeId) : base(name, salary, employeeId)
{
}
public void SetSalary(decimal newSalary)
{
this.Salary = newSalary; // Accessing protected member.
Console.WriteLine($"Manager Salary Set to: {newSalary:C}");
}
protected internal override void CalculateBonus()
{
decimal bonus = Salary * 0.2m;
Console.WriteLine($"Manager Bonus: {bonus:C}");
}
public string GetEmployeeDetailsForInternalUse()
{
return base.GetEmployeeDetails();
}
}
public class Program
{
public static void Main(string[] args)
{
Employee employee = new Employee("John Doe", 50000, "E123");
Manager manager = new Manager("Jane Smith", 80000, "M456");
Console.WriteLine($"Employee Name: {employee.Name}");
Console.WriteLine($"Employee ID: {employee.EmployeeId}");
//Console.WriteLine($"Employee Salary: {employee.Salary}"); // Compilation error - protected
Console.WriteLine(employee.GetEmployeeDetails()); // Accessible within the same assembly.
manager.SetSalary(90000);
Console.WriteLine(manager.GetEmployeeDetailsForInternalUse());
employee.CalculateBonus();
manager.CalculateBonus();
Console.ReadKey();
}
}
Concepts Illustrated
This snippet showcases the following OOP principles: * **Encapsulation**: Bundling data and methods, controlling access via modifiers. * **Inheritance**: `Manager` inheriting from `Employee`. * **Polymorphism**: `CalculateBonus` being overridden in the `Manager` class. * **Access Modifiers**: `private`, `public`, `protected`, `internal`, `protected internal` * **Readonly**: Prevents modification of the assigned field.
Real-World Scenario
Imagine a software system for managing a hospital. You might have a base `Person` class with attributes like name and address. Derived classes could be `Patient` and `Doctor`. Certain information, like a patient's medical history, should only be accessible within the `Patient` class or by authorized medical personnel (through controlled methods). `protected` and `internal` modifiers would be key to controlling access within the hospital's software system.
Best Practices
* Start with the most restrictive access modifier possible (e.g., `private`) and only loosen it when necessary. * Use properties to control access to data members. * Consider using readonly fields for values that should not change after initialization. * Document the purpose of each access modifier clearly.
Interview Question
How does `protected internal` differ from `protected` or `internal`? A `protected internal` member is accessible either: (1) from any code in the same assembly in which it is declared, or (2) from any derived class in another assembly. `protected` is only accessible in the class and any class that derives from it, and `internal` is only available within the same assembly.
When to Use Which Modifier
* `private`: Data only needed by the class itself. * `public`: Data that needs to be accessed from anywhere. * `protected`: Data needed by the class and its derived classes. * `internal`: Data needed by classes within the same assembly (e.g., a component). * `protected internal`: Data needed by derived classes in different assemblies or any code inside the same assembly. It offers broader accessibility than protected, while still limiting access from outside the assembly unless inheritance is involved.
Memory Implications
Again, the access modifier itself doesn't directly impact memory usage. The data members and the methods consume memory. Access modifiers only control *how* those data members and methods can be accessed.
Alternatives To Classic Encapsulation
* **Records (C# 9.0 and later):** Offer concise syntax for creating immutable data structures. Immutable objects inherently provide a form of encapsulation as their state cannot be changed after creation. * **Data Transfer Objects (DTOs):** Used to transfer data between layers of an application. DTOs typically have public properties but often lack complex logic, focusing on data transfer rather than behavior.
Pros
* Improved data integrity. * Increased code reusability and maintainability. * Reduced coupling between classes. * Enhanced security.
Cons
* Can increase the complexity of class design, especially in large systems. * Overuse of encapsulation can sometimes lead to unnecessary boilerplate code (though properties help mitigate this).
FAQ
-
Why use `readonly`?
To ensure that a field's value cannot be changed after it has been initialized in the constructor. This is useful for representing properties that should be constant for the lifetime of the object. -
What is an assembly in C#?
An assembly is a unit of deployment for .NET applications. It is typically a .dll or .exe file and contains compiled code, metadata, and resources. Access modifiers like `internal` control visibility within the assembly.