Introduction

Access specifiers in C# are used to define the accessibility or visibility of types and their members.

In simple terms, an access specifier determines who can access a class, method, property, field, constructor, or other member and from where it can be accessed.

For example, suppose a class contains several methods. Some methods may need to be available to every class in an application, while others may need to be accessible only within the same class or within derived classes.

C# provides access modifiers to control this visibility and help developers implement encapsulation.

Consider the following example:

public class Employee
{
    private string employeeName;

    public void SetEmployeeName(string name)
    {
        employeeName = name;
    }

    public string GetEmployeeName()
    {
        return employeeName;
    }
}

Here, employeeName is declared as private, so it cannot be accessed directly from outside the Employee class. The public methods provide controlled access to the value.

This is one of the fundamental concepts of object-oriented programming in C#.

Different Types of Access Specifiers in C#

C# provides six commonly used access modifiers:

  1. private

  2. public

  3. protected

  4. internal

  5. protected internal

  6. private protected

The private protected access modifier was introduced in C# 7.2.

The accessibility provided by these modifiers depends on whether the accessing code is located in the same class, the same assembly, or another assembly, and whether inheritance is involved.

1. Private Access Modifier

The private access modifier restricts access to the containing type.

A private member can be accessed from within the class where it is declared, but it cannot normally be accessed directly from derived classes or unrelated classes.

Example

public class Employee
{
    private string employeeName = "John";

    public void DisplayName()
    {
        Console.WriteLine(employeeName);
    }
}

The employeeName field can be accessed inside the Employee class:

Employee employee = new Employee();
employee.DisplayName();

But the following code is not allowed:

Employee employee = new Employee();

// Compile-time error
// employee.employeeName = "David";

Accessibility of a Private Member

Accessing Code

Accessible?

Containing class

Yes

Derived class in same assembly

No

Non-derived class in same assembly

No

Derived class in another assembly

No

Non-derived class in another assembly

No

private is useful when an implementation detail should be completely hidden from other types.

2. Public Access Modifier

The public access modifier provides the widest accessibility.

A public member can be accessed from code that can access the containing type, including code in other assemblies.

Example

public class Employee
{
    public string EmployeeName = "John";

    public void DisplayName()
    {
        Console.WriteLine(EmployeeName);
    }
}

The member can be accessed from another class:

Employee employee = new Employee();

Console.WriteLine(employee.EmployeeName);
employee.DisplayName();

It can also be accessed from a derived class:

public class Manager : Employee
{
    public void DisplayManagerName()
    {
        Console.WriteLine(EmployeeName);
    }
}

Accessibility of a Public Member

Accessing Code

Accessible?

Containing class

Yes

Derived class in same assembly

Yes

Non-derived class in same assembly

Yes

Derived class in another assembly

Yes

Non-derived class in another assembly

Yes

Public members should generally represent functionality that is intentionally exposed as part of a type's API.

3. Protected Access Modifier

The protected access modifier allows access within the containing type and from derived types.

Unlike public, a protected member cannot normally be accessed through an object from an unrelated class.

Example

public class Employee
{
    protected string employeeName = "John";
}

public class Manager : Employee
{
    public void DisplayName()
    {
        Console.WriteLine(employeeName);
    }
}

Manager can access employeeName because Manager derives from Employee.

However, an unrelated class cannot access it directly:

public class Test
{
    public void Display()
    {
        Employee employee = new Employee();

        // Compile-time error
        // Console.WriteLine(employee.employeeName);
    }
}

Accessibility of a Protected Member

Accessing Code

Accessible?

Containing class

Yes

Derived class in same assembly

Yes

Non-derived class in same assembly

No

Derived class in another assembly

Yes

Non-derived class in another assembly

No

protected is commonly used when a base class needs to expose implementation details to derived classes without making those details publicly accessible.

4. Internal Access Modifier

The internal access modifier restricts access to the current assembly.

An assembly is typically the compiled output of a .NET project, such as a .dll or .exe.

An internal member can be accessed by other types within the same assembly but not directly from another assembly.

Example

internal class Employee
{
    internal string EmployeeName = "John";
}

Another class in the same project can access it:

public class Department
{
    public void DisplayEmployee()
    {
        Employee employee = new Employee();

        Console.WriteLine(employee.EmployeeName);
    }
}

However, code in another assembly cannot directly access the internal type or member unless mechanisms such as InternalsVisibleTo are used.

Accessibility of an Internal Member

Accessing Code

Accessible?

Containing class

Yes

Derived class in same assembly

Yes

Non-derived class in same assembly

Yes

Derived class in another assembly

No

Non-derived class in another assembly

No

internal is useful when functionality needs to be shared among types within a project but should not form part of the project's public API.

5. Protected Internal Access Modifier

protected internal combines two accessibility conditions.

A member declared as protected internal can be accessed:

The important point is that this is effectively an OR relationship between protected and internal.

Example

public class Employee
{
    protected internal string EmployeeName = "John";
}

A class in the same assembly can access the member:

public class Department
{
    public void DisplayEmployee()
    {
        Employee employee = new Employee();

        Console.WriteLine(employee.EmployeeName);
    }
}

A derived class in another assembly can also access the member.

Accessibility of a Protected Internal Member

Accessing Code

Accessible?

Containing class

Yes

Derived class in same assembly

Yes

Non-derived class in same assembly

Yes

Derived class in another assembly

Yes

Non-derived class in another assembly

No

Because protected internal provides relatively broad access, it should be used when both same-assembly access and derived-type access are intentionally required.

6. Private Protected Access Modifier

The private protected access modifier provides more restricted access than protected internal.

A private protected member can be accessed only:

It was introduced in C# 7.2.

Example

public class Employee
{
    private protected string employeeName = "John";
}

public class Manager : Employee
{
    public void DisplayName()
    {
        Console.WriteLine(employeeName);
    }
}

The Manager class can access employeeName because it derives from Employee and is in the same assembly.

A derived class in another assembly cannot access the member.

Accessibility of a Private Protected Member

Accessing Code

Accessible?

Containing class

Yes

Derived class in same assembly

Yes

Non-derived class in same assembly

No

Derived class in another assembly

No

Non-derived class in another assembly

No

This modifier is useful when a base class wants to expose a member only to derived types that are part of the same assembly.

Access Modifier Comparison

The following table provides a quick comparison:

Access Modifier

Same Class

Same Assembly

Derived Type in Other Assembly

Non-Derived Type in Other Assembly

private

Yes

No

No

No

public

Yes

Yes

Yes

Yes

protected

Yes

No for unrelated types

Yes for derived types

No

internal

Yes

Yes

No

No

protected internal

Yes

Yes

Yes for derived types

No

private protected

Yes

Yes for derived types

No

No

The key distinction to remember is:

protected internal = protected OR internal

private protected = protected AND internal

This makes the difference between the two modifiers easier to understand.

Access Modifiers for Types

Access modifiers can also be applied to types such as classes, interfaces, structs, delegates, and enums, subject to the accessibility rules of each type.

For example:

public class Employee
{
}

The Employee class is publicly accessible.

An internal class can be declared as:

internal class Department
{
}

The Department type is accessible only within the same assembly.

For top-level types, private and protected are not valid access modifiers. They are primarily used for members and nested types.

Why Access Modifiers Are Important

Access modifiers are an important part of encapsulation.

They allow developers to decide which parts of a class should be exposed and which implementation details should remain hidden.

For example:

public class BankAccount
{
    private decimal balance;

    public void Deposit(decimal amount)
    {
        if (amount > 0)
        {
            balance += amount;
        }
    }

    public decimal GetBalance()
    {
        return balance;
    }
}

Here, balance is private. External code cannot directly change it:

BankAccount account = new BankAccount();

// Not allowed
// account.balance = -5000;

Instead, the class controls how the balance changes through the public Deposit method.

This helps protect the internal state of the object and keeps business rules inside the class.

Common Mistakes

Making Everything Public

A common beginner mistake is declaring every field and method as public.

For example:

public class Employee
{
    public string name;
    public decimal salary;
}

This exposes the internal state of the class unnecessarily.

A better approach is to expose only what other parts of the application actually need.

Confusing Protected and Internal

protected is primarily related to inheritance, while internal is related to the assembly boundary.

For example:

protected
    -> containing type + derived types

internal
    -> types within the same assembly

Understanding these two boundaries makes the other access modifiers much easier to understand.

Confusing Protected Internal and Private Protected

These two modifiers look similar but provide different levels of access.

protected internal
    = protected OR internal

private protected
    = protected AND internal

Therefore, protected internal is broader, while private protected is more restrictive.

Best Practices

When choosing an access modifier, consider the smallest scope required by the member.

Some general guidelines are:

Conclusion

Access specifiers in C# provide a mechanism for controlling the accessibility and visibility of types and their members.

The six commonly used access modifiers are private, public, protected, internal, protected internal, and private protected.

Understanding the difference between these modifiers is important for designing classes with proper encapsulation and well-defined APIs.

For beginners, the easiest way to remember them is to focus on two boundaries: inheritance and assembly. Once these boundaries are clear, choosing the appropriate access modifier becomes much easier.