Skip to content

Access modifiers explained

↑

Examples are independent C# snippets for modern .NET console projects unless noted otherwise. Class declarations and top-level code can live in Program.cs when top-level statements come first.

Accessibility controls which code is allowed to use a type or member. Start with the narrowest access that meets your actual design need, and widen it intentionally.

Only the containing type (nested-type rules can also apply).

ModifierContaining typeDerived, same assemblyUnrelated, same assemblyDerived, other assemblyUnrelated, other assembly
public
private
protected
internal
protected internal
private protected

This shows typical class-member accessibility. A member cannot be more accessible than its containing type, and protected access has additional receiver rules. The file modifier instead applies to eligible top-level types in one source file.

public permits access wherever the enclosing type is accessible. private limits access to the containing type (with relevant language rules for nested types). Private implementation details can be changed without breaking callers.

Example.cs
Counter counter = new();
counter.Increment();
Console.WriteLine(counter.Value);
// counter.count = 5; // compiler error
public class Counter
{
private int count;
public int Value => count;
public void Increment() => count++;
}

protected allows access from the containing class and derived classes. internal permits access within the same assembly. In Unity, an assembly definition (.asmdef) creates meaningful assembly boundaries. A namespace is not an assembly.

Example.cs
public class Enemy
{
protected int health = 100;
internal void ResetHealth() => health = 100;
}
public class Boss : Enemy
{
public void Damage() => health -= 10;
}

protected internal means same assembly OR a derived type in another assembly. private protected means derived types within the same assembly (plus containing-type access). file limits eligible top-level types to the current source file. Not every modifier applies to every declaration.

Example.cs
public class Base
{
protected internal void ExtendOrUseLocally() { }
private protected void ExtendWithinAssembly() { }
}
file sealed class FileOnlyHelper { }

Access is about which code may depend on a member

Section titled “Access is about which code may depend on a member”

An access modifier is not a security boundary between untrusted programs; it is a compile-time visibility rule that helps maintain invariants. Start with private: it lets you change implementation details without changing the code that calls your class. Open only the members that another part of the application needs.

Consider a bank account: if callers could directly assign Balance, they could accidentally make it negative. A public method can expose a supported action while a private field preserves the rule.

Program.cs
var account = new Account(100);
account.Withdraw(30);
Console.WriteLine(account.Balance); // 70
public sealed class Account
{
public decimal Balance { get; private set; }
public Account(decimal openingBalance) => Balance = openingBalance;
public void Withdraw(decimal amount)
{
if (amount <= 0 || amount > Balance)
throw new ArgumentOutOfRangeException(nameof(amount));
Balance -= amount;
}
}

protected is for deliberate extension points

Section titled “protected is for deliberate extension points”

A protected member is available to derived types. Use it when subclasses need a specific operation, not as a shortcut to make every field accessible. Prefer a protected method or property over exposing mutable internal state. A derived class can live in a different assembly when the base class is accessible there.

internal means accessible from code compiled into the same assembly, such as the same .dll or normal project output. Putting classes under the same C# namespace does not grant access across assembly boundaries. In Unity, assembly definition (.asmdef) files affect this boundary; splitting scripts into assemblies can expose errors that did not appear before.

  • protected internal: accessible from the same assembly OR from derived classes in another assembly.
  • private protected: accessible from the containing type and derived classes within the same assembly.
  • file: for eligible top-level types, restricts their name to the current source file; it is not a modifier for ordinary fields.

Read “protected internal” as a union, and “private protected” as an intersection (with the normal containing-type access). If an API should be available to every project, use public; if not, avoid widening access simply to work around a design problem.

Accessibility is constrained by the type itself

Section titled “Accessibility is constrained by the type itself”

A public member of an internal class does not make the class public outside its assembly. Likewise, a public method generally cannot expose a less-accessible type in its signature; the compiler reports an inconsistent accessibility error. Top-level types default to internal when no modifier is provided, while class members normally default to private.

You need to… Start with… Why
Hide implementation details private Changes remain local to the class
Offer a stable API to callers public Clearly documents supported operations
Share implementation among project code internal Limits external dependencies
Allow subclasses to customize behavior protected method Defines a controlled extension point
Allow internal code or external subclasses protected internal Union of both audiences
Allow subclasses only inside this assembly private protected Narrower inheritance surface

Use private for implementation details, public for intentional APIs, internal for shared code inside one assembly and protected for deliberate inheritance extension points.

Access modifiers are compile-time accessibility rules, not security boundaries. protected internal is OR, while private protected is restricted to the same assembly.

Choose an access modifier for a helper method used only inside one class, and one for types shared only inside a Unity asmdef.

Reveal a solution

private for the helper; internal for the same-assembly type.