Access Modifiers (also called Access Specifiers) are one of the most essential concepts in C# and Object-Oriented Programming. They define the visibility, scope, and accessibility of classes, methods, properties, fields, and other members in your code.
Understanding access modifiers allows you to:
Protect your data
Control how components interact
Build secure and maintainable applications
Implement proper encapsulation (a core OOP principle)
Prevent accidental misuse of your code
Many developers use access modifiers without fully understanding their real purpose. This article explains each modifier in a clear, detailed, and practical way with examples and a real-world perspective.
By the end of this guide, you will clearly understand:
What access modifiers are
Why are they important
All five access modifiers in C#
Their scope and usage
How they apply to classes and class members
Practical examples
Common mistakes and best practices
What Are Access Modifiers in C#?
Access modifiers are keywords that specify how accessible a class or class member is to other parts of the program.
They determine questions like:
Can another class use this?
Can another module access this method?
Should this property be visible to the outside world?
Should some data stay hidden inside the class?
Access modifiers form the foundation of Encapsulation, which means hiding internal details and exposing only necessary functionality.
Types of Access Modifiers in C#
C# provides five main access modifiers:
public
private
protected
internal
protected internal
private protected (newer addition)
Let’s understand each one in detail with descriptive explanations and examples.
1. Public Access Modifier
The public modifier makes a member accessible from anywhere in the application.
Public members are fully visible without restrictions—any class, assembly, or code file can access them.
Commonly used for methods or properties intended for external consumption, such as APIs, helper utilities, and service classes.
Overuse of public modifiers can lead to tightly coupled and insecure code.
It should be applied only when you intentionally want external code to interact with your member.
Example
public class Student
{
public string Name;
public void Display()
{
Console.WriteLine(Name);
}
}
2. Private Access Modifier
The private modifier restricts access to within the same class only.
Private members cannot be accessed from outside the class—not even by derived classes.
It is the default modifier for fields and methods if no modifier is specified.
Private is the most restrictive, helping you protect sensitive data.
Encourages encapsulation by hiding implementation details from external use.
Example
public class Employee
{
private double salary;
private void CalculateBonus()
{
// logic here
}
}
3. Protected Access Modifier
The protected modifier allows access within the class and its derived classes.
Useful in inheritance when child classes must access parent class members.
It cannot be accessed by external classes unless they inherit the parent class.
Commonly used in frameworks where base classes provide reusable logic for subclasses.
Provides controlled extensibility while still hiding information from unrelated classes.
Example
public class Animal
{
protected void Eat()
{
Console.WriteLine("Animal is eating.");
}
}
public class Dog : Animal
{
public void StartEating()
{
Eat(); // Allowed because Dog inherits Animal
}
}
4. Internal Access Modifier
The internal modifier makes the member accessible within the same assembly (i.e., same project or DLL).
Internal is ideal for keeping functionality available to the application but hidden from external consumers.
Any code within the same project can access internal members, even if classes are in different namespaces.
Prevents access from other assemblies unless InternalsVisibleTo is used.
Commonly used in a multi-layer architecture where utility classes or repository classes must be accessed only inside one project.
Example
internal class Logger
{
internal void Log(string message)
{
Console.WriteLine(message);
}
}
5. Protected Internal Access Modifier
This is a combination of protected and internal.
Members declared with protected internal can be accessed within the same assembly or by derived classes, even if they are in different assemblies.
Offers more flexibility than either protected or internal alone.
Useful when designing libraries where inheritance is allowed across assemblies.
Developers must use it carefully to avoid exposing too much.
Example
public class Base
{
protected internal void Print()
{
Console.WriteLine("Accessible by inheritance or within same assembly.");
}
}
6. Private Protected Access Modifier
This is a combination of private and protected.
Accessible only within the same class or its derived classes, but only if they are in the same assembly.
More restrictive than protected internal and even more specific than protected.
Helps achieve maximum encapsulation in inheritance scenarios.
Useful for limiting accessibility while still supporting inheritance internally.
Example
public class Parent
{
private protected int id = 10;
}
Access Modifiers Summary Table
| Modifier | Same Class | Derived Class (Same Assembly) | Derived Class (Other Assembly) | Same Assembly | Different Assembly |
|---|
| public | Yes | Yes | Yes | Yes | Yes |
| private | Yes | No | No | No | No |
| protected | Yes | Yes | Yes | No | No |
| internal | Yes | Yes | No | Yes | No |
| protected internal | Yes | Yes | Yes | Yes | Yes |
| private protected | Yes | Yes (same assembly only) | No | Yes | No |
This table helps you quickly compare the accessibility of each modifier.
Choosing the Right Access Modifier
Use private by default
Start with the most restrictive modifier unless something needs wider access.
Expose only what is necessary This promotes encapsulation, maintainability, and security.
Use public carefully Overexposure of members can create long-term technical debt.
Use protected for inheritance-based designs Ideal when subclasses need access to base logic.
Use internal for multi-layer applications Keeps classes accessible only within the project.
Use protected internal sparingly Because it allows wide access across inheritance and assemblies.
Use private protected for strict internal inheritance Perfect balance for libraries or internal subsystems.
Real-World Example Demonstrating All Access Modifiers
public class BankAccount
{
private double balance; // Private: only inside class
protected double InterestRate; // Protected: derived classes can access
internal string BankName; // Internal: within assembly
protected internal int AccountType; // Protected OR internal
private protected string Token; // Protected, but only same assembly
public BankAccount()
{
balance = 1000;
BankName = "HDFC";
Token = Guid.NewGuid().ToString();
}
public void Deposit(double amount) => balance += amount;
}
public class SavingsAccount : BankAccount
{
public void CalculateInterest()
{
Console.WriteLine(InterestRate); // Allowed (protected)
Console.WriteLine(AccountType); // Allowed (protected internal)
}
}
This example shows exactly how accessibility rules work in real projects.