C#  

Access Modifiers and Access Specifiers in C#

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:

  1. public

  2. private

  3. protected

  4. internal

  5. protected internal

  6. 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.

  1. Public members are fully visible without restrictions—any class, assembly, or code file can access them.

  2. Commonly used for methods or properties intended for external consumption, such as APIs, helper utilities, and service classes.

  3. Overuse of public modifiers can lead to tightly coupled and insecure code.

  4. 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.

  1. Private members cannot be accessed from outside the class—not even by derived classes.

  2. It is the default modifier for fields and methods if no modifier is specified.

  3. Private is the most restrictive, helping you protect sensitive data.

  4. 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.

  1. Useful in inheritance when child classes must access parent class members.

  2. It cannot be accessed by external classes unless they inherit the parent class.

  3. Commonly used in frameworks where base classes provide reusable logic for subclasses.

  4. 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).

  1. Internal is ideal for keeping functionality available to the application but hidden from external consumers.

  2. Any code within the same project can access internal members, even if classes are in different namespaces.

  3. Prevents access from other assemblies unless InternalsVisibleTo is used.

  4. 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.

  1. Members declared with protected internal can be accessed within the same assembly or by derived classes, even if they are in different assemblies.

  2. Offers more flexibility than either protected or internal alone.

  3. Useful when designing libraries where inheritance is allowed across assemblies.

  4. 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.

  1. Accessible only within the same class or its derived classes, but only if they are in the same assembly.

  2. More restrictive than protected internal and even more specific than protected.

  3. Helps achieve maximum encapsulation in inheritance scenarios.

  4. Useful for limiting accessibility while still supporting inheritance internally.

Example

public class Parent
{
    private protected int id = 10;
}

Access Modifiers Summary Table

ModifierSame ClassDerived Class (Same Assembly)Derived Class (Other Assembly)Same AssemblyDifferent Assembly
publicYesYesYesYesYes
privateYesNoNoNoNo
protectedYesYesYesNoNo
internalYesYesNoYesNo
protected internalYesYesYesYesYes
private protectedYesYes (same assembly only)NoYesNo

This table helps you quickly compare the accessibility of each modifier.

Choosing the Right Access Modifier

  1. Use private by default
    Start with the most restrictive modifier unless something needs wider access.

  2. Expose only what is necessary This promotes encapsulation, maintainability, and security.

  3. Use public carefully Overexposure of members can create long-term technical debt.

  4. Use protected for inheritance-based designs Ideal when subclasses need access to base logic.

  5. Use internal for multi-layer applications Keeps classes accessible only within the project.

  6. Use protected internal sparingly Because it allows wide access across inheritance and assemblies.

  7. 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.