Menu

C# Access Modifiers: public, private, protected and internal

The six C# access modifiers (public, private, protected, internal, protected internal, private protected), what each one allows, the defaults when you write none, and the compiler errors they produce.

This page includes runnable editors - edit, run, and see output instantly.

Access modifiers decide which code may use a type or member. They are how a class separates the part other code relies on (its public surface) from the part it is free to change (its internals). C# has six of them, plus the defaults you get when you write none.

The six modifiers

ModifierContaining typeDerived type, same assemblyOther code, same assemblyDerived type, other assemblyOther code, other assembly
publicyesyesyesyesyes
protected internalyesyesyesyesno
protectedyesyesnoyesno
internalyesyesyesnono
private protectedyesyesnonono
privateyesnononono

An assembly is one compiled project: the .dll or .exe it produces. "Same assembly" means "same project", which is why internal works as "visible to my code, hidden from my library's users".

private and public: encapsulation

private is the default for members and the right choice for most fields. The class exposes what callers need through public methods and properties, and those methods keep the object's data consistent.

Output:

False
True
Priya: 180

Because balance is private, the only ways to change it are Deposit and TryWithdraw, and both enforce the rules. No other code in the program can create a negative balance, however it is written. That is what encapsulation buys: the rules live in one place.

private is per type, not per object. A method of Account may read other.balance on a different Account instance, which is how Equals and comparison methods are usually written.

protected: visible to derived classes

A protected member is hidden from the outside world but available to classes that inherit from the declaring class.

Output:

Sam: 4000.00
Rita: 6400.00

There is one rule that surprises people. Inside Manager, you may use baseSalary on this or on another Manager, but not on an arbitrary Employee:

class Manager : Employee
{
    public decimal Compare(Employee other)
    {
        return baseSalary - other.baseSalary;
        // error CS1540: Cannot access protected member 'Employee.baseSalary' via a qualifier
        // of type 'Employee'; the qualifier must be of type 'Manager' (or derived from it)
    }
}

The reason: other could be a Contractor that also derives from Employee, and protected grants access to your own branch of the hierarchy, not to your siblings'.

A protected field couples every derived class to that field forever. Many codebases prefer a protected property or method, or keep fields private and give derived classes only what they need.

internal: visible inside the assembly

internal members and types can be used by any code in the same project and by nothing outside it. A library uses it for the helpers its public classes rely on:

// In the MyShop.Pricing library project
public class PriceCalculator
{
    public decimal Total(Cart cart) => TaxRules.Apply(cart.Subtotal);
}

internal static class TaxRules   // callers of the library cannot see this class
{
    internal static decimal Apply(decimal amount) => amount * 1.2m;
}

Top-level types are internal when you write no modifier. That is why a class you forgot to mark public in a class library is "missing" from the project that references it.

Unit test projects are separate assemblies, so they cannot see internal code either. The standard fix is an attribute in the library:

[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("MyShop.Pricing.Tests")]

protected internal and private protected

These two combine the previous modifiers, in opposite ways, and their names are easy to mix up:

  • protected internal is protected or internal: any code in the same assembly, plus derived classes in other assemblies. It is the wider one.
  • private protected (C# 7.2) is protected and internal: only derived classes that are also in the same assembly. It is the narrower one, for a base class whose extension points should stay inside your library.
public class ReportBase
{
    protected internal string Title;      // same assembly, or subclasses anywhere
    private protected int RowLimit = 500; // subclasses in this assembly only (C# 7.2)
}

Defaults when you write nothing

WhereDefaultAllowed
Type declared in a namespaceinternalpublic, internal, file (C# 11)
Member of a classprivateall six
Member of a structprivatepublic, internal, private (structs cannot be inherited)
Nested typeprivateall six
Interface memberpublicexplicit modifiers allowed since C# 8
Enum memberpublicnone

Being explicit costs one word and removes a question for the next reader, so most style guides ask for the modifier even when it matches the default.

Accessor-level access

A property or indexer can give one accessor a narrower modifier than the property itself:

Output:

report.pdf: 100%

The accessor's modifier must be more restrictive than the property's, and only one of the two accessors may have one.

Inconsistent accessibility

A public member cannot expose a type that is less visible than itself, because callers would receive something they are not allowed to name:

internal class Discount { }

public class Checkout
{
    public Discount Current() => null;
    // error CS0050: Inconsistent accessibility: return type 'Discount' is less accessible than method 'Checkout.Current()'
}

The same rule gives CS0051 for parameter types and CS0053 for property types. Fix it by making the type as visible as the member, or the member as hidden as the type.

Choosing a modifier

Start with the most restrictive modifier that works and widen it only when a caller needs it. Making something more visible later is easy and never breaks anyone; making something less visible breaks every caller that used it. In practice: fields private, the members that form the class's purpose public, helpers private, and library-only types internal.

Frequently Asked Questions

What are the access modifiers in C#?

There are six: public (any code), private (only the containing type), protected (the containing type and types derived from it), internal (any code in the same assembly), protected internal (same assembly, or derived types anywhere) and private protected (derived types in the same assembly, C# 7.2). C# 11 added file for types visible only in one source file.

What is the default access modifier in C#?

Members of a class or struct (fields, methods, properties, nested types) are private by default. Top-level types (a class declared directly in a namespace) are internal by default. Interface members and enum members are public.

What does protected mean in C#?

A protected member is visible inside its own class and inside any class derived from it, but not to other code. A derived class can use it only through its own type: inside Manager, this.baseSalary works, but reaching baseSalary on some other Employee object is error CS1540.

What is internal in C#?

internal makes a type or member visible to all code in the same assembly (the same project's compiled .dll or .exe) and invisible outside it. It is how a library keeps helper classes out of its public API. [assembly: InternalsVisibleTo("MyLib.Tests")] lets a test project see them too.

How do I fix "is inaccessible due to its protection level"?

That is error CS0122: the code is using a member it is not allowed to see, most often a field left at the default private. If outside code should read it, expose a public property or method; do not just make the field public. If it is a type from another project, it is probably internal.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED