Paramclasses Academy - Blog

Tutorials Details

←
Post 19 of 30
→

What is C# Access Modifiers ? Explained: Public, Private, Protected and Internal

What is C# Access Modifiers ? Explained: Public, Private, Protected and Internal

October 26, 2026

Introduction

Access modifiers are an important concept in C# programming. They control the accessibility of classes and their members, such as fields, methods, properties, and constructors.

When developing a C# application, you may not want every part of your program to access every variable or method. For example, a bank account's balance should not be freely modified by unrelated classes. Similarly, an internal helper method may not need to be available to external applications.

C# provides access modifiers to control these access boundaries and help developers build organized, maintainable, and reliable applications.

In this tutorial, you will learn:

  • What access modifiers are in C#.

  • How public, private, protected, and internal work.

  • The differences between protected internal and private protected.

  • How access modifiers behave with classes and class members.

  • Practical examples using inheritance and encapsulation.

  • Common mistakes and best practices.

1. What Are Access Modifiers in C#?

Access modifiers are keywords that determine where a type or member can be accessed.

For example, consider this class:

class Student
{
    public string Name = "Rahul";
    private int age = 20;
}

Here:

  • Name is public, so it can be accessed from code that can access the Student type.

  • age is private, so it can only be accessed within the Student class itself.

Access modifiers help you define which parts of your application can use particular members.

Access modifiers available in C#

C# has six commonly discussed accessibility levels:

  1. public

  2. private

  3. protected

  4. internal

  5. protected internal

  6. private protected

The first four are the fundamental access modifiers. The last two combine accessibility rules.

2. Public Access Modifier

The public modifier allows access wherever the containing type is accessible.

It is commonly used for methods, properties, and classes that are intended to be used by other parts of an application.

Example

using System;

public class Student
{
    public string Name = "Rahul";

    public void Display()
    {
        Console.WriteLine($"Student Name: {Name}");
    }
}

class Program
{
    static void Main()
    {
        Student student = new Student();

        Console.WriteLine(student.Name);
        student.Display();
    }
}

Output:

Rahul
Student Name: Rahul

Explanation

  • public class Student declares a publicly accessible class.

  • public string Name allows accessible callers to read or modify the field.

  • public void Display() allows callers to invoke the method.

  • Main() creates an object and accesses its public members.

When should you use public?

Use public when a class or member is intentionally part of the interface that other code should use.

Examples include:

  • Public methods in a service class.

  • Properties that callers need to read.

  • Methods that form part of a library's API.

  • Operations that other application components must perform.

Best practice: Do not make every member public automatically. Expose only the members that callers genuinely need.

3. Private Access Modifier

The private modifier restricts access to the containing type. In ordinary class-member usage, a private member can be accessed by code inside the class that declares it, but not directly by unrelated classes or derived classes.

It is commonly used for internal data and implementation details.

Example

using System;

class BankAccount
{
    private decimal balance = 1000m;

    public void DisplayBalance()
    {
        Console.WriteLine($"Balance: {balance}");
    }

    public void Deposit(decimal amount)
    {
        if (amount <= 0)
        {
            Console.WriteLine("Amount must be positive.");
            return;
        }

        balance += amount;
    }
}

class Program
{
    static void Main()
    {
        BankAccount account = new BankAccount();

        account.DisplayBalance();
        account.Deposit(500m);
        account.DisplayBalance();

        // account.balance = 5000m;
        // This would cause a compilation error.
    }
}

Output:

Balance: 1000
Balance: 1500

Explanation

The balance field is private. Code outside BankAccount cannot directly read or modify it.

Instead, callers use the public DisplayBalance() and Deposit() methods. The class can validate the deposit amount before changing its internal state.

The commented assignment account.balance = 5000m; would not compile because balance is private.

When should you use private?

Use private for:

  • Internal fields.

  • Helper methods that should not be called externally.

  • Implementation details.

  • State that must be modified only through controlled operations.

Best practice: Prefer private fields and expose carefully designed public properties or methods when external access is necessary.

4. Protected Access Modifier

The protected modifier allows access within the declaring class and within classes derived from it, subject to C# inheritance and access rules.

It is particularly useful when a base class needs to provide data or behavior to its subclasses without making that member publicly accessible to everyone.

Example

using System;

class Employee
{
    protected string Name = "Rahul";

    protected void DisplayName()
    {
        Console.WriteLine($"Employee: {Name}");
    }
}

class Manager : Employee
{
    public void ShowDetails()
    {
        DisplayName();
    }
}

class Program
{
    static void Main()
    {
        Manager manager = new Manager();

        manager.ShowDetails();

        // manager.Name = "Aman";
        // This is not allowed from this location.
    }
}

Output:

Employee: Rahul

Explanation

  • Employee declares a protected field and method.

  • Manager inherits from Employee.

  • The derived class can access Name and call DisplayName().

  • The Main() method cannot directly access manager.Name.

This lets a base class expose selected functionality to subclasses without making it public.

When should you use protected?

Use it when:

  • Derived classes need access to shared state.

  • Subclasses must use or customize selected base-class behavior.

  • A base class provides extension points for inheritance.

Best practice: Use protected deliberately. A protected member becomes part of what derived classes can depend on, so changing it later may affect subclasses.

5. Internal Access Modifier

The internal modifier allows access from within the same assembly.

An assembly is a compiled .NET unit, commonly a .dll or .exe file. Two projects may be part of the same solution but compile into different assemblies.

Example

using System;

internal class Calculator
{
    internal int Add(int a, int b)
    {
        return a + b;
    }
}

class Program
{
    static void Main()
    {
        Calculator calculator = new Calculator();

        int result = calculator.Add(10, 20);

        Console.WriteLine($"Result: {result}");
    }
}

Output:

Result: 30

Explanation

Both Calculator and its Add() method are internal. They can be accessed by other code in the same assembly, provided the containing types and other accessibility rules allow it.

Ordinary code in a separate assembly cannot access these internal members directly unless access has been granted through a supported mechanism, such as InternalsVisibleToAttribute for a friend assembly.

When should you use internal?

Use internal for:

  • Implementation classes that should remain within a library.

  • Internal services and helpers.

  • Components that are shared between files in the same assembly but should not be part of its public API.

Best practice: Use internal to keep library implementation details private from external consumers.

6. Protected Internal Access Modifier

The protected internal modifier combines two accessibility conditions.

A member declared protected internal can be accessed from:

  • Code within the same assembly, subject to the usual containing-type rules.

  • Derived classes in other assemblies, subject to the rules for protected access.

In other words, access is permitted if the code satisfies either the internal-access condition or the protected-access condition.

Example

using System;

public class Vehicle
{
    protected internal void Start()
    {
        Console.WriteLine("Vehicle started.");
    }
}

class Car : Vehicle
{
    public void StartCar()
    {
        Start();
    }
}

class Program
{
    static void Main()
    {
        Vehicle vehicle = new Vehicle();

        vehicle.Start();
    }
}

Output:

Vehicle started.

Explanation

All the code in this example belongs to the same assembly. Therefore, Main() can call the protected internal method.

The Car class can also call the method because it derives from Vehicle.

In a different assembly, unrelated code would not be able to call Start() simply because it is protected internal. A derived class in that other assembly may access it through the protected rules, subject to C#'s restrictions on accessing inherited protected members.

When should you use protected internal?

Use it when a member is intended to be available both within the current assembly and to eligible derived classes outside it.

Because it offers broader access than protected alone, use it only when both access paths are intentional.

7. Private Protected Access Modifier

The private protected modifier restricts access to the declaring class and eligible derived classes within the same assembly.

It is more restrictive than protected internal.

Example

using System;

public class Employee
{
    private protected void ShowSalary()
    {
        Console.WriteLine("Salary details displayed.");
    }
}

class Manager : Employee
{
    public void DisplaySalary()
    {
        ShowSalary();
    }
}

class Program
{
    static void Main()
    {
        Manager manager = new Manager();

        manager.DisplaySalary();

        // manager.ShowSalary();
        // This would cause a compilation error.
    }
}

Output:

Salary details displayed.

Explanation

Manager derives from Employee and belongs to the same assembly, so it can call ShowSalary().

However, Main() cannot call ShowSalary() directly through the manager object. The method is not publicly accessible.

A derived class located in another assembly cannot access this member through inheritance alone.

When should you use private protected?

Use it when a member should be available to derived classes in the same assembly but should not be exposed to unrelated code or derived classes in other assemblies.

8. Access Modifiers Comparison Table

Access modifier Same class Derived class in same assembly Unrelated code in same assembly Derived class in another assembly Unrelated code in another assembly
public Yes Yes Yes, if containing types are accessible Yes, if containing types are accessible Yes, if containing types are accessible
private Yes No, not through inheritance alone No No No
protected Yes Yes No Yes, subject to protected-access rules No
internal Yes Yes Yes No, ordinarily No, ordinarily
protected internal Yes Yes Yes Yes, subject to protected-access rules No, ordinarily
private protected Yes Yes No No, through inheritance alone No

The table describes ordinary class-member access. Exact accessibility also depends on the containing type, the kind of member, and the context of the access.

9. Default Access Modifiers in C#

C# applies default accessibility when you omit an access modifier. The default depends on the declaration type and location.

Default accessibility of class members

Members declared inside a class or struct default to private, unless a language rule specifies otherwise.

class Student
{
    string name = "Rahul";

    void Display()
    {
        Console.WriteLine(name);
    }
}

Here, both name and Display() are private because no access modifiers are specified.

Default accessibility of top-level types

A top-level class, struct, interface, enum, or delegate declared without an access modifier defaults to internal.

class Student
{
}

This class is internal by default.

A nested type declared inside another type also follows different default-accessibility rules from a top-level type. For example, a nested class declared inside a class defaults to private.

Best practice

Specify access modifiers explicitly when doing so makes the intended design clearer, especially for public APIs and reusable libraries.

10. Practical Example: Employee Management System

Let's combine access modifiers in a small employee-management example.

The Employee class will store an employee's name and salary, expose a public method to display details, and provide a protected member that derived classes can use.

using System;

public class Employee
{
    private decimal salary;

    public string Name { get; }

    public Employee(string name, decimal salary)
    {
        if (string.IsNullOrWhiteSpace(name))
        {
            throw new ArgumentException(
                "Name is required.",
                nameof(name)
            );
        }

        if (salary < 0)
        {
            throw new ArgumentOutOfRangeException(
                nameof(salary)
            );
        }

        Name = name;
        this.salary = salary;
    }

    public void DisplayDetails()
    {
        Console.WriteLine($"Name: {Name}");
        Console.WriteLine($"Salary: {salary}");
    }

    protected decimal GetSalary()
    {
        return salary;
    }
}

public class Manager : Employee
{
    public Manager(string name, decimal salary)
        : base(name, salary)
    {
    }

    public void DisplayManagerSalary()
    {
        Console.WriteLine($"Manager salary: {GetSalary()}");
    }
}

class Program
{
    static void Main()
    {
        Manager manager = new Manager("Aman", 60000m);

        manager.DisplayDetails();
        manager.DisplayManagerSalary();
    }
}

Output:

Name: Aman
Salary: 60000
Manager salary: 60000

Explanation

  • public class Employee allows the class to be used by accessible callers.

  • private decimal salary restricts direct access to the salary field.

  • public string Name provides read-only access to the employee's name.

  • The public constructor validates the input before creating the object.

  • public void DisplayDetails() allows callers to display employee information.

  • protected decimal GetSalary() allows the Manager class to access the salary through inheritance.

  • Manager reuses the Employee constructor and protected method.

This example demonstrates how access modifiers support encapsulation and controlled access.

In a real application, displaying salary information would also require appropriate authorization and privacy controls.

11. Access Modifiers and Properties

Access modifiers are particularly useful when designing properties.

A property can have different access levels for its getter and setter, allowing a class to expose read access while restricting modification.

Example

using System;

class Product
{
    public string Name { get; set; }

    public decimal Price { get; private set; }

    public Product(string name, decimal price)
    {
        Name = name;
        SetPrice(price);
    }

    public void SetPrice(decimal price)
    {
        if (price < 0)
        {
            throw new ArgumentOutOfRangeException(nameof(price));
        }

        Price = price;
    }
}

class Program
{
    static void Main()
    {
        Product product = new Product("Laptop", 50000m);

        Console.WriteLine(product.Price);

        product.SetPrice(45000m);

        Console.WriteLine(product.Price);

        // product.Price = 1000m;
        // This would cause a compilation error here.
    }
}

Output:

50000
45000

Explanation

  • Name has public getter and setter access.

  • Price has a public getter but a private setter.

  • External code can read Price but cannot assign it directly.

  • The class controls price changes through SetPrice().

This pattern is useful when changes to a property must follow specific validation rules.

12. Common Mistakes Beginners Should Avoid

Mistake 1: Making every member public

Public members expose implementation details and allow more code to depend on them than necessary.

Solution: Start with the most restrictive appropriate access level and expand access only when required.

Mistake 2: Trying to access private members from a derived class

A derived class cannot directly access a private member declared in its base class.

Solution: Use a protected member or a suitable public or protected method when derived classes need access.

Mistake 3: Confusing internal with private

private limits access to the declaring type, whereas internal allows access throughout the same assembly, subject to other accessibility rules.

Mistake 4: Assuming protected internal means protected access only

protected internal permits access through either the internal or protected path.

By contrast, private protected requires the derived-class access condition and the same-assembly condition to be satisfied together.

Mistake 5: Forgetting that top-level classes default to internal

A top-level class without an access modifier is internal by default, not public.

Mistake 6: Exposing a public setter unnecessarily

A public setter may allow callers to put an object into an invalid state.

Solution: Consider private setters, read-only properties, or methods that validate changes.

Mistake 7: Assuming access modifiers provide complete security

Access modifiers control code accessibility. They do not replace authentication, authorization, encryption, or other security controls.

13. Best Practices for Professional C# Development

  • Keep implementation fields private by default.

  • Expose public members only when they are part of the intended API.

  • Use protected members when derived classes genuinely need access.

  • Use internal types to keep library implementation details within an assembly.

  • Use protected internal and private protected only when their precise accessibility rules are required.

  • Validate data when creating objects and changing important state.

  • Prefer read-only properties when callers need to inspect values but should not change them.

  • Avoid unnecessarily large public APIs.

  • Use clear naming conventions for fields, properties, methods, and types.

  • Review accessibility when designing reusable libraries and frameworks.

14. Practice Exercises

Complete these exercises to strengthen your understanding.

Exercise 1: Student class

Create a Student class with:

  • A public Name property.

  • A private marks field.

  • A public method to update marks after validation.

  • A public method to display student details.

Goal: Practice public and private.

Exercise 2: Employee inheritance

Create a base class named Employee with a protected Department field. Create a derived class named Manager that accesses this field.

Goal: Practice protected.

Exercise 3: Internal calculator

Create an internal class called Calculator with an internal Multiply() method.

Test access from another class in the same assembly.

Goal: Understand assembly-level accessibility.

Exercise 4: Property access control

Create a Product class with a public Name property and a Price property whose setter is private.

Provide a method that validates and updates the price.

Goal: Practice accessor accessibility.

Exercise 5: Protected internal vs private protected

Create a base class with one protected internal method and another private protected method. Test access from a derived class in the same assembly.

Then investigate how access changes when a derived class is moved to a separate assembly.

Goal: Understand the two combined accessibility levels.

15. Quick Knowledge Check

Q1. Which access modifier allows access from anywhere the containing type is accessible?

A. private
B. protected
C. public
D. private protected

Answer: C. public.

Q2. Which access modifier restricts a class member to its declaring type?

A. internal
B. private
C. public
D. protected internal

Answer: B. private.

Q3. Which access modifier allows access within the same assembly?

A. internal
B. private
C. protected
D. private protected

Answer: A. internal.

Q4. Which modifier is useful when derived classes need access to a member?

A. private
B. protected
C. internal only
D. None of these

Answer: B. protected.

Q5. What is the default accessibility of a top-level class without an access modifier?

A. public
B. private
C. internal
D. protected

Answer: C. internal.

Q6. What does protected internal mean?

A. Access only inside the declaring class.
B. Access only from derived classes in the same assembly.
C. Access through either the internal or protected accessibility path.
D. Access only from public classes.

Answer: C. Access through either the internal or protected accessibility path.

Q7. Can unrelated code in the same assembly access a private member of a class?

A. Yes
B. No

Answer: B. No.

16. Frequently Asked Questions (FAQs)

1. What are access modifiers in C#?

Access modifiers define where classes and their members can be accessed. They help control visibility and support encapsulation.

2. What is the difference between public and private?

public permits access wherever the containing type is accessible. private restricts access to the declaring type.

3. What is the difference between protected and internal?

protected provides access to the declaring type and eligible derived classes. internal provides access throughout the same assembly, subject to containing-type accessibility.

4. What is the difference between protected internal and private protected?

protected internal allows access through either the internal or protected path. private protected allows access to the declaring type and eligible derived classes within the same assembly.

5. Can a private member be accessed by a derived class?

Not directly through inheritance. A derived class can use an accessible method or property exposed by the base class, but it cannot directly access the base class's private member.

6. What is the default access modifier for class members?

Members of a class generally default to private, while top-level types generally default to internal. Some declarations have their own specific rules.

7. Can a class be private in C#?

A top-level class cannot be declared private. A nested class can be private within its containing type.

8. Do access modifiers improve security?

They help control which code can access program elements, but they do not replace application security measures such as authentication, authorization, and input validation.

Conclusion

Access modifiers are essential for designing well-structured C# applications. They allow developers to control how types and members are exposed and help prevent unnecessary access to internal implementation details.

Remember these key points:

  • Public: Accessible wherever the containing type is accessible.

  • Private: Accessible within the declaring type.

  • Protected: Accessible within the declaring type and eligible derived classes.

  • Internal: Accessible within the same assembly.

  • Protected internal: Accessible through either the internal or protected path.

  • Private protected: Accessible within the declaring type and eligible derived classes in the same assembly.

Understanding these modifiers will help you write cleaner classes, maintain encapsulation, and design better APIs.

Practice the examples and exercises, then apply these rules in a small employee-management or banking application.

←
Post 19 of 30
→