C# のアクセス修飾子(Access Modifiers)は、型やメンバーの可視性を制御するキーワードです。適切に設定することで、外部に公開する必要のない実装の詳細を隠蔽(カプセル化)し、保守性と安全性を高められます。
C# 11 で追加された file を含め、現在は 7 種類のアクセス修飾子があります。
可視性の一覧
| アクセス修飾子 | 同じクラス | 派生クラス(同一アセンブリ) | 同一アセンブリ | 派生クラス(別アセンブリ) | 別アセンブリ |
|---|---|---|---|---|---|
public |
✅ | ✅ | ✅ | ✅ | ✅ |
private |
✅ | ❌ | ❌ | ❌ | ❌ |
protected |
✅ | ✅ | ❌ | ✅ | ❌ |
internal |
✅ | ✅ | ✅ | ❌ | ❌ |
protected internal |
✅ | ✅ | ✅ | ✅ | ❌ |
private protected |
✅ | ✅ | ❌ | ❌ | ❌ |
file |
同一ファイル内のみ | ❌ | ❌ | ❌ | ❌ |
protected internal は「protected OR internal」、private protected は「protected AND internal」と覚えると分かりやすいです。
public — どこからでもアクセス可能
最も開放的な修飾子です。型やメンバーをライブラリの利用者・別プロジェクトに公開する場合に使います。
public class Greeter
{
public string Message { get; set; } = "Hello!";
public void Greet()
{
Console.WriteLine(Message);
}
}
// どこからでもアクセスできる
var g = new Greeter();
g.Greet(); // Hello!
public の適用対象
- クラス・構造体・インターフェース・列挙型(トップレベル型)
- メソッド・プロパティ・フィールド・イベント・コンストラクタ
- ネストされた型
private — 同じクラス内のみ
最も制限的な修飾子です。クラスの内部実装を隠すために使います。クラスメンバーのデフォルトのアクセス修飾子です。
public class BankAccount
{
private decimal _balance; // 外部からアクセス不可
public BankAccount(decimal initial)
{
_balance = initial;
}
public void Deposit(decimal amount)
{
if (amount <= 0)
throw new ArgumentException("Amount must be positive.");
_balance += amount;
}
public decimal GetBalance() => _balance;
}
var account = new BankAccount(1000m);
account.Deposit(500m);
Console.WriteLine(account.GetBalance()); // 1500
// account._balance = 9999; // コンパイルエラー:private メンバーにはアクセスできない
ネストされた型からはアクセスできる
private メンバーは同じクラス内のネストされた型(inner class)からもアクセスできます。
public class Outer
{
private int _secret = 42;
public class Inner
{
public void Reveal(Outer outer)
{
// ネストされた型から外側クラスの private メンバーにアクセスできる
Console.WriteLine(outer._secret); // 42
}
}
}
var outer = new Outer();
var inner = new Outer.Inner();
inner.Reveal(outer); // 42
protected — 同じクラスと派生クラスのみ
継承関係にあるクラスに実装の詳細を共有したい場合に使います。アセンブリの内外を問わず、派生クラスからアクセスできます。
public class Shape
{
protected double _area;
protected void LogArea()
{
Console.WriteLine($"Area = {_area}");
}
}
public class Circle : Shape
{
public Circle(double radius)
{
// 派生クラスから protected メンバーにアクセスできる
_area = Math.PI * radius * radius;
}
public void PrintArea() => LogArea();
}
var c = new Circle(5);
c.PrintArea(); // Area = 78.53981633974483
// c._area = 0; // コンパイルエラー:外部からはアクセスできない
// c.LogArea(); // コンパイルエラー:外部からはアクセスできない
protected メンバーへの制約
protected メンバーには「自分自身のインスタンス経由」でしかアクセスできないという制約があります。
public class Circle : Shape
{
public void Compare(Shape other)
{
Console.WriteLine(_area); // OK:自分自身の protected メンバー
// Console.WriteLine(other._area); // コンパイルエラー:別インスタンスの protected には直接アクセスできない
}
}
internal — 同じアセンブリ内のみ
internal はアセンブリ(= プロジェクト)を境界とするアクセス制御です。同じプロジェクト内であればどこからでもアクセスでき、別プロジェクトからはアクセスできません。トップレベル型のデフォルトのアクセス修飾子です。
// ── MyLibrary プロジェクト ──
internal class InternalHelper
{
internal void DoWork()
{
Console.WriteLine("Internal work");
}
}
public class PublicService
{
public void Execute()
{
// 同じアセンブリ内なのでアクセスできる
var helper = new InternalHelper();
helper.DoWork();
}
}
// ── ConsoleApp プロジェクト(別アセンブリ)──
var service = new PublicService();
service.Execute(); // OK
// var helper = new InternalHelper(); // コンパイルエラー:別アセンブリからはアクセスできない
InternalsVisibleTo — テストプロジェクトへの公開
internal メンバーをユニットテスト用の別プロジェクトからアクセスしたい場合は、InternalsVisibleTo 属性を使います。
// MyLibrary/AssemblyInfo.cs
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("MyLibrary.Tests")]
これにより MyLibrary.Tests プロジェクトからは internal メンバーにアクセスできるようになります。
protected internal — protected OR internal
protected internal は「同じアセンブリ内からアクセスできる」または「派生クラスからアクセスできる」の和集合です。
// ── MyLibrary プロジェクト ──
public class BaseComponent
{
protected internal void Initialize()
{
Console.WriteLine("Initialized");
}
}
public class SameAssemblyUser
{
public void Setup()
{
var component = new BaseComponent();
component.Initialize(); // OK:同じアセンブリなので internal としてアクセスできる
}
}
// ── PluginProject プロジェクト(別アセンブリ)──
public class CustomComponent : BaseComponent
{
public void Start()
{
Initialize(); // OK:派生クラスなので protected としてアクセスできる
}
}
public class ExternalUser
{
public void Setup()
{
var component = new BaseComponent();
// component.Initialize(); // コンパイルエラー:別アセンブリかつ派生でもないのでアクセスできない
}
}
フレームワーク内部では自由にアクセスさせつつ、継承を通じて外部の拡張にも開放する、というパターンに向いています。
private protected — protected AND internal
C# 7.2 で導入された private protected は、「同じアセンブリ内」かつ「派生クラス」の積集合でのみアクセスできます。
// ── MyLibrary プロジェクト ──
public class BaseEngine
{
private protected void CoreProcess()
{
Console.WriteLine("Core processing");
}
}
// 同じアセンブリ内の派生クラス → OK
public class InternalEngine : BaseEngine
{
public void Run()
{
CoreProcess(); // OK:同じアセンブリ内の派生クラス
}
}
// 同じアセンブリ内だが派生クラスではない → NG
public class SameAssemblyUser
{
public void Try()
{
var engine = new BaseEngine();
// engine.CoreProcess(); // コンパイルエラー:派生クラスではない
}
}
// ── PluginProject プロジェクト(別アセンブリ)──
public class ExternalEngine : BaseEngine
{
public void Run()
{
// CoreProcess(); // コンパイルエラー:派生クラスだが別アセンブリ
}
}
ライブラリ内部でだけ継承を許可し、外部からの継承では触れさせたくないメンバーを保護するのに有効です。
file — 同一ファイル内のみ(C# 11)
C# 11(.NET 7)で導入された file 修飾子は、型宣言(class・struct・interface・enum・delegate)に付けられるアクセス修飾子で、同じソースファイル内からのみアクセス可能にします。
// Validators.cs
file class EmailValidator
{
public bool IsValid(string email) => email.Contains('@');
}
public class UserService
{
private readonly EmailValidator _validator = new();
public bool ValidateEmail(string email) => _validator.IsValid(email);
}
// OtherFile.cs
// var v = new EmailValidator(); // コンパイルエラー:別ファイルからはアクセスできない
file の主な用途
file はソースジェネレーターとの相性を意識して設計されましたが、手書きのコードでも以下の場面で役立ちます。
- ファイルスコープのヘルパークラス — そのファイル内でしか使わないユーティリティをファイルに閉じ込める
- 名前の衝突回避 — 同名の型が別ファイルに存在してもコンパイルエラーにならない
// FileA.cs
file class Helper
{
public static string Tag => "A";
}
// FileB.cs
file class Helper // 同名でも衝突しない
{
public static string Tag => "B";
}
file の制約
fileはメンバー(メソッド・プロパティなど)には付けられない。型宣言のみfile型をネストされた型にはできない。トップレベル型のみpublicなメンバーの戻り値やパラメーターにfile型を使うとコンパイルエラーになる
デフォルトのアクセス修飾子
明示的にアクセス修飾子を書かなかった場合のデフォルトは以下のとおりです。
| 対象 | デフォルト |
|---|---|
| トップレベルの型(class・struct・interface・enum) | internal |
| クラスのメンバー(メソッド・プロパティ・フィールドなど) | private |
| インターフェースのメンバー | public |
| 構造体のメンバー | private |
| enum のメンバー | public |
| ネストされた型 | private |
// デフォルトは internal
class DefaultClass
{
// デフォルトは private
int _value;
// デフォルトは private
void DoSomething() { }
}
意図を明確にするために、private であっても明示的に書くチームも多くあります。
protected internal と private protected の比較
この 2 つは混同しやすいため、改めて対比します。
protected internal = protected OR internal → 広い(和集合)
private protected = protected AND internal → 狭い(積集合)
| シナリオ | protected internal |
private protected |
|---|---|---|
| 同じアセンブリ・派生クラス | ✅ | ✅ |
| 同じアセンブリ・非派生クラス | ✅ | ❌ |
| 別アセンブリ・派生クラス | ✅ | ❌ |
| 別アセンブリ・非派生クラス | ❌ | ❌ |
まとめ
| アクセス修飾子 | 導入バージョン | 一言で |
|---|---|---|
public |
C# 1.0 | どこからでもアクセス可能 |
private |
C# 1.0 | 同じ型の内部のみ |
protected |
C# 1.0 | 同じ型 + 派生クラス |
internal |
C# 1.0 | 同じアセンブリ内のみ |
protected internal |
C# 1.0 | protected OR internal |
private protected |
C# 7.2 | protected AND internal |
file |
C# 11 | 同一ソースファイル内のみ |
アクセス修飾子は「公開する必要がないものは隠す」を原則に選択します。まず private から始めて、必要に応じて段階的に緩めていくのが安全なアプローチです。