Access Modifiers in C#

Access modifiers in C# control where a class, method, field, property, constructor, or nested type can be used from. They define the boundary between the public face of your code and the internal details that should stay protected inside a class or assembly.

This matters because object oriented programming is not only about creating classes. It is also about controlling how those classes are used. If every field and method is public, outside code can change object state in unsafe ways. If everything is private, the class becomes hard to use. Access modifiers give you the middle ground.

The main access modifiers in C# are public, private, protected, internal, protected internal, and private protected. Each keyword answers one question: who is allowed to access this code?


What Are Access Modifiers in C#?

Access modifiers are keywords used to specify the accessibility of types and members. A type can be a class, struct, interface, enum, or delegate. A member can be a field, property, method, constructor, event, indexer, or nested type.

public class BankAccount
{
    private decimal balance;

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

    public decimal GetBalance()
    {
        return balance;
    }
}

In this example, BankAccount is public, so other code can create and use it. The balance field is private, so outside code cannot change it directly. The class exposes controlled public methods instead.

Why Access Modifiers Are Important

  • They protect data from invalid changes.
  • They hide implementation details from outside code.
  • They make public APIs cleaner and easier to understand.
  • They reduce accidental dependencies on internal logic.
  • They support encapsulation, one of the main ideas of OOP.

A class should expose what callers need, not every detail it uses internally. That is why access modifiers are used heavily in professional C# code.

Types of Access Modifiers in C#

ModifierAccess LevelCommon Use
publicAccessible from anywherePublic API, services, model properties
privateAccessible only inside the same typeFields, helper methods, hidden state
protectedAccessible inside same class and derived classesInheritance customization
internalAccessible inside the same assemblyProject-level helpers
protected internalAccessible from same assembly or derived classesLibrary extension points
private protectedAccessible from derived classes in same assemblyRestricted inheritance support

public Access Modifier

The public modifier gives the widest access. A public class or member can be accessed from any code that can reference it. Use public only for behavior that is intentionally part of the class interface.

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

Here, both the class and the method are public. Another class can create a Calculator object and call Add. This is correct because addition is the useful behavior the class is meant to provide.

private Access Modifier

The private modifier restricts access to the same class or struct. It is the safest access level for internal data because outside code cannot touch it directly. Most fields should be private unless there is a strong reason to expose them.

public class Student
{
    private int marks;

    public void SetMarks(int value)
    {
        if (value >= 0 && value <= 100)
        {
            marks = value;
        }
    }
}

The marks field cannot be changed directly from outside the class. The method checks whether the value is valid before storing it. This is a simple example of encapsulation.

protected Access Modifier

The protected modifier allows access inside the same class and inside classes that inherit from it. It is useful when a base class wants to provide members for derived classes, but not for all outside code.

public class Animal
{
    protected string name;

    protected void Eat()
    {
        Console.WriteLine("Eating food");
    }
}

public class Dog : Animal
{
    public void Show()
    {
        name = "Buddy";
        Eat();
    }
}

The Dog class can access name and Eat because it inherits from Animal. Code that only creates a Dog object cannot access those protected members directly.

internal Access Modifier

The internal modifier allows access only within the same assembly. In simple terms, an assembly is usually the compiled output of a project, such as a DLL or EXE. This is useful when code should be shared inside the project but hidden from other projects.

internal class ReportBuilder
{
    internal void Build()
    {
        Console.WriteLine("Building report");
    }
}

Library projects use internal heavily. A library may contain many helper classes, but only a few public classes should be exposed to users of the library.

protected internal Access Modifier

The protected internal modifier means the member can be accessed from derived classes or from anywhere inside the same assembly. It is wider than protected and wider than internal in practical use because either condition is enough.

public class BaseService
{
    protected internal void LogOperation()
    {
        Console.WriteLine("Operation logged");
    }
}

This can be useful in framework style code where internal project code and derived classes both need access. However, it should be used carefully because it exposes more than many developers expect.

private protected Access Modifier

The private protected modifier allows access only from derived classes that are also in the same assembly. It is narrower than protected internal. Use it when inheritance support should stay inside your own project or library.

public class BaseController
{
    private protected void ValidateRequest()
    {
        Console.WriteLine("Request validated");
    }
}

An external class that inherits from BaseController in another assembly cannot access this method. That makes private protected useful when you want restricted inheritance behavior.

Default Access Modifiers in C#

ItemDefault Access
Top level classinternal
Class memberprivate
Struct memberprivate
Interface memberpublic
Enum memberpublic

Even though C# has default access rules, writing the access modifier explicitly often makes code easier to read. A developer should not need to guess whether a member is private, internal, or public.

Access Modifiers with Properties

Properties can use different access levels for getters and setters. This is common when outside code should read a value but only the class should change it.

public class Order
{
    public int OrderId { get; private set; }
    public decimal TotalAmount { get; private set; }

    public Order(int orderId)
    {
        OrderId = orderId;
    }

    public void AddAmount(decimal amount)
    {
        if (amount > 0)
        {
            TotalAmount += amount;
        }
    }
}

Outside code can read OrderId and TotalAmount, but it cannot set them directly. The class controls how the amount changes through the AddAmount method. This keeps the object state valid.

Access Modifiers with Top Level and Nested Types

Top level types have fewer access choices than members. A top level class can normally be public or internal. It cannot be private because private access only makes sense inside another type. Nested classes, however, can use access modifiers such as private, protected, and public.

public class OuterClass
{
    private class Helper
    {
        public void Work()
        {
            Console.WriteLine("Helper working");
        }
    }
}

The Helper class is hidden inside OuterClass. This is useful when a small helper type is only meaningful for one class and should not be used directly anywhere else.

Choosing the Right Access Modifier

The best rule is to start with the most restrictive access level that works. Make something public only when other code genuinely needs to call it. Keep implementation details private. Use internal when the code belongs to the project but should not become part of a public library API.

For inheritance, be careful with protected. Protected members become part of the contract for derived classes. Once derived classes depend on those members, changing them later becomes difficult. Do not make a member protected only because it feels convenient.

Best Practices for Access Modifiers

  • Keep fields private and expose data through properties or methods.
  • Use public only for members that are part of the intended API.
  • Use private helper methods for repeated internal logic.
  • Use protected only when derived classes genuinely need access.
  • Use internal for project-level helpers and implementation classes.
  • Avoid exposing mutable collections directly from public properties.

Common Mistakes with Access Modifiers

  • Making fields public because it feels faster during development.
  • Using public setters for values that should be validated.
  • Forgetting that top level classes are internal by default.
  • Using protected for members that derived classes should not control.
  • Exposing helper methods that should remain private.
  • Using protected internal without understanding that it gives broad access.

The safest habit is to design the public surface intentionally. Anything public can be used by other code, tested by other teams, and depended on for years. Keep public members stable, clear, and meaningful.

Access Modifier Design Example

In real projects, access modifiers are used to shape how other developers interact with your class. For example, a payment class may expose a public Pay method, keep validation methods private, and keep shared helper classes internal to the project. This makes the class easier to use because callers see only the actions they should perform.

This also makes future refactoring safer. If a helper method is private, you can rename it, change its parameters, or remove it without breaking outside code. If the same helper method is public, other code may already depend on it. That is why public access should be treated as a long-term promise.

Good access control keeps code flexible because internal details can change without forcing every caller to change with them.

FAQs on Access Modifiers in C#

Which access modifier is most restrictive in C#?

private is the most restrictive common access modifier. It allows access only inside the same class or struct.

Can a top level class be private in C#?

No. A top level class can usually be public or internal. A nested class can be private.

Should fields be public in C#?

Usually no. Fields should normally be private, and outside code should use properties or methods. This protects validation and future changes.

What is the default access modifier for class members?

Class members are private by default. Still, writing private explicitly often improves readability.


Continue learning C# in order
Follow the topic sequence with the previous and next lesson.