アクセス修飾子は、どのコードが型やメンバーを使えるかを決めます。クラスが、他のコードが頼る部分(公開された面)と、自由に変えられる部分(内部)を分ける方法です。C#には6つの修飾子があり、何も書かない場合の既定もあります。
6つの修飾子
| 修飾子 | それを含む型 | 派生型、同じアセンブリ | その他のコード、同じアセンブリ | 派生型、別のアセンブリ | その他のコード、別のアセンブリ |
|---|---|---|---|---|---|
public | 可 | 可 | 可 | 可 | 可 |
protected internal | 可 | 可 | 可 | 可 | 不可 |
protected | 可 | 可 | 不可 | 可 | 不可 |
internal | 可 | 可 | 可 | 不可 | 不可 |
private protected | 可 | 可 | 不可 | 不可 | 不可 |
private | 可 | 不可 | 不可 | 不可 | 不可 |
アセンブリは、コンパイルされた1つのプロジェクト、つまりそれが生成する .dll や .exe です。「同じアセンブリ」は「同じプロジェクト」を意味するので、internal は「自分のコードからは見え、ライブラリの利用者からは隠れる」として働きます。
privateとpublic:カプセル化
private はメンバーの既定で、ほとんどのフィールドに適した選択です。クラスは呼び出し側が必要とするものを public のメソッドやプロパティで公開し、それらのメソッドがオブジェクトのデータの一貫性を保ちます。
出力:
False
True
Priya: 180
balance はprivateなので、それを変更する方法は Deposit と TryWithdraw だけで、どちらもルールを強制します。プログラムの他のどのコードも、どう書かれていようと、負の残高を作れません。これがカプセル化の効果で、ルールが1か所にまとまります。
private はオブジェクトごとではなく型ごとです。Account のメソッドは、別の Account インスタンスの other.balance を読めます。Equals や比較のメソッドは通常この方法で書かれます。
protected:派生クラスから見える
protected のメンバーは外部からは隠されますが、宣言したクラスを継承するクラスからは使えます。
出力:
Sam: 4000.00
Rita: 6400.00
1つ意外なルールがあります。Manager の中では、this や別の Manager の baseSalary は使えますが、任意の 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)
}
}
理由は、other が同じく Employee から派生した Contractor かもしれないからです。protected がアクセスを与えるのは階層の中の自分の枝であって、兄弟の枝ではありません。
protectedなフィールドは、すべての派生クラスをそのフィールドに永遠に結び付けます。多くのコードベースでは、protected なプロパティやメソッドを好むか、フィールドはprivateにして、派生クラスには必要なものだけを与えます。
internal:アセンブリの中で見える
internal のメンバーと型は、同じプロジェクトの任意のコードから使え、その外からは何も使えません。ライブラリは、公開クラスが頼るヘルパーにこれを使います。
// 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;
}
トップレベルの型は、修飾子を書かなければ internal です。クラスライブラリで public を付け忘れたクラスが、それを参照するプロジェクトから「見当たらない」のはそのためです。
単体テストのプロジェクトも別のアセンブリなので、internal のコードは見えません。標準的な直し方は、ライブラリに属性を付けることです。
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("MyShop.Pricing.Tests")]
protected internalとprivate protected
この2つは、前の修飾子を逆の方法で組み合わせたもので、名前を取り違えやすいものです。
protected internalはprotectedまたはinternalです。同じアセンブリの任意のコードに加えて、別のアセンブリの派生クラスからも使えます。広いほうです。private protected(C# 7.2)はprotectedかつinternalです。同じアセンブリにもある派生クラスからだけ使えます。狭いほうで、拡張ポイントをライブラリの中にとどめたい基底クラスのためのものです。
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)
}
何も書かない場合の既定
| 場所 | 既定 | 使えるもの |
|---|---|---|
| 名前空間で宣言した型 | internal | public、internal、file(C# 11) |
| クラスのメンバー | private | 6つすべて |
| 構造体のメンバー | private | public、internal、private(構造体は継承できない) |
| 入れ子の型 | private | 6つすべて |
| インターフェースのメンバー | public | C# 8以降は明示的な修飾子も可 |
| enumのメンバー | public | なし |
明示的に書くコストは1語で、次に読む人の疑問を取り除けるので、ほとんどのスタイルガイドは、既定と同じであっても修飾子を書くよう求めています。
アクセサーのレベルのアクセス
プロパティやインデクサーは、一方のアクセサーに、プロパティ自体より狭い修飾子を付けられます。
出力:
report.pdf: 100%
アクセサーの修飾子はプロパティのものより制限が強くなければならず、付けられるのは2つのアクセサーのうち一方だけです。
一貫性のないアクセシビリティ
publicなメンバーは、自分より見えにくい型を公開できません。呼び出し側が、名前を書くことを許されていないものを受け取ることになるからです。
internal class Discount { }
public class Checkout
{
public Discount Current() => null;
// error CS0050: Inconsistent accessibility: return type 'Discount' is less accessible than method 'Checkout.Current()'
}
同じルールから、引数の型ではCS0051、プロパティの型ではCS0053が出ます。型をメンバーと同じくらい見えるようにするか、メンバーを型と同じくらい隠すことで直します。
修飾子の選び方
動く中で最も制限の強い修飾子から始め、呼び出し側が必要とするときだけ広げます。後から見えるようにするのは簡単で、誰も壊しません。後から見えにくくすると、それを使っていたすべての呼び出し側が壊れます。実際には、フィールドは private、クラスの目的を成すメンバーは public、ヘルパーは private、ライブラリ内だけの型は internal にします。
よくある質問
C#のアクセス修飾子にはどんなものがありますか?
6つあります。public(任意のコード)、private(それを含む型だけ)、protected(それを含む型とそこから派生した型)、internal(同じアセンブリの任意のコード)、protected internal(同じアセンブリ、またはどこにある派生型でも)、private protected(同じアセンブリの派生型、C# 7.2)です。C# 11では、1つのソースファイルでだけ見える型のための file が追加されました。
C#の既定のアクセス修飾子は何ですか?
クラスや構造体のメンバー(フィールド、メソッド、プロパティ、入れ子の型)は既定で private です。トップレベルの型(名前空間に直接宣言したクラス)は既定で internal です。インターフェースのメンバーとenumのメンバーはpublicです。
C#のprotectedとはどういう意味ですか?
protected のメンバーは、自分のクラスの中と、そこから派生したクラスの中で見えますが、それ以外のコードからは見えません。派生クラスがそれを使えるのは自分の型を通してだけです。Manager の中で this.baseSalary は動きますが、別の Employee オブジェクトの baseSalary にアクセスするのはエラーCS1540です。
C#のinternalとは何ですか?
internal は、型やメンバーを、同じアセンブリ(同じプロジェクトのコンパイルされた.dllや.exe)のすべてのコードから見えるようにし、その外からは見えなくします。ライブラリがヘルパークラスを公開APIから外す方法です。[assembly: InternalsVisibleTo("MyLib.Tests")] を使うと、テストプロジェクトからも見えるようになります。
「is inaccessible due to its protection level」はどう直しますか?
これはエラーCS0122で、コードが見ることを許されていないメンバーを使っています。多くの場合、既定の private のままのフィールドです。外部のコードがそれを読むべきなら、公開のプロパティやメソッドを用意します。フィールドをそのままpublicにしてはいけません。別のプロジェクトの型なら、おそらく internal です。