Monday, 7 September 2026

C# Access Specifiers: A Comprehensive Guide with Examples

Leave a Comment

In C#, access specifiers are used to specify whether types and their members are visible or accessible.
To put it simply, an access specifier establishes who may access a class, method, property, field, constructor, or other member, as well as from what location.

Let's take an example where a class has several methods. While certain methods may only be accessible inside the same class or within derived classes, others may need to be accessible to all classes in an application.


To manage this visibility and assist developers in implementing encapsulation, C# has access modifiers.

Take a look at this 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:

  • From anywhere within the same assembly, or

  • From a derived class, including a derived class in another assembly.

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:

  • Within the containing type, or

  • From derived types that are located in the same assembly.

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:

  • Use private for implementation details that should remain inside the type.

  • Use public only when functionality needs to be exposed to consumers.

  • Use protected when derived classes need access to a member.

  • Use internal for functionality that should remain within the current assembly.

  • Use protected internal when both same-assembly access and derived-type access are required.

  • Use private protected when access should be limited to derived types within the same assembly.

  • Prefer encapsulation instead of exposing internal fields directly.

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.

Best ASP.NET Core 10.0 Hosting Recommendation

One of the most important things when choosing a good ASP.NET Core 10.0 hosting is the feature and reliability. HostForLIFE is the leading provider of Windows hosting and affordable ASP.NET Core, their servers are optimized for PHP web applications. The performance and the uptime of the hosting service are excellent and the features of the web hosting plan are even greater than what many hosting providers ask you to pay for. 

At HostForLIFEASP.NET, customers can also experience fast ASP.NET Core hosting. The company invested a lot of money to ensure the best and fastest performance of the datacenters, servers, network and other facilities. Its datacenters are equipped with the top equipments like cooling system, fire detection, high speed Internet connection, and so on. That is why HostForLIFEASP.NET guarantees 99.9% uptime for ASP.NET Core. And the engineers do regular maintenance and monitoring works to assure its Orchard hosting are security and always up.

0 comments:

Post a Comment