C#のアクセス修飾子 2019 〜protectedは 結構でかい〜

この投稿では、2019年におけるC#のアクセス修飾子を紹介します。

C# 7.2から新たに`private protected`が新たに加わりました。これは、『「同一のアセンブリ」 かつ　「その型とその派生型」はアクセスできる』というアクセスレベルです。そしてゲームエンジンUnityに`Assembly Definition Files`や`Package Manager`が加わったことで、Unity開発者は「アセンブリ」を意識して開発する機会が増えました。継続的なライブラリ更新のために、このようなアセンブリに関係するアクセス修飾子をしっかりと理解すべき状況となっています。

自分で開発したライブラリを社内で公開している人、世の中に広く公開したい人は、2019年におけるC#のアクセス修飾子理解することをオススメします。

# アクセス修飾子とアクセスレベル

アクセス修飾子は以下の4個です

* public
* protected
* internal
* private

アクセスレベルは以下の6種です。

* public : 無制限
* protected : その型とその派生型はアクセスできる
* internal : 同一のアセンブリ（同じDllやExe）からのみアクセスできる
* protected internal : 「同一のアセンブリ」 もしくは　「その型とその派生型」はアクセスできる
* private protected: 「同一のアセンブリ」 でありかつ　「その型とその派生型」はアクセスできる
* private : その型からのみアクセスできる


※ `private protected`がC# 7.2から新たに加わったアクセスレベルです。
※ 中間言語のMSILとして「`protected` and `internal`」相当のものは以前から存在していましたが、C#で加わったのはC# 7.2からです。

# protectedはでかい。なぜなら・・・

ライブラリ開発者にとって、アクセスレベル`protected`は非常にアクセス範囲が広いアクセスレベルです。

『あなたはライブラリ開発者で、コード形式ではなくアセンブリ（DLL形式）でライブラリを配布しており、あるAPIを削除しようとしています。』このような状況を考えてみてください。

* 削除しようとしているAPIが`private`アクセスレベルだったら、そのAPIは問題なく削除できます。ライブラリ利用者は`private`メンバにアクセスできないからです。

* 削除しようとしているAPIが`internal`アクセスレベルだったら、そのAPIは問題なく削除できますね。ライブラリ利用者は`internal`なメンバ・型にアクセスできないからです。

* 削除しようとしているAPIが`public`アクセスレベルだったら、そのAPIを削除した場合破壊的変更になってしまうので、簡単に削除できません。Obsoleteにし移行方法をアナウンスするなどコストをかける必要があります。（もしくは、いきなり削除してライブラリ利用者に迷惑をかけるしかありません）

では、削除しようとしているAPIが`protected`アクセスレベルだった場合はどうでしょう？ライブラリ開発において、`protected`アクセスレベルは、`public`アクセスレベルと同等に慎重に扱わないといけません。`protected`アクセスレベルは、「その型とその派生型はアクセスできる」ですが、派生された型であればライブラリ利用者は自由に使うことができます。そのため、`protected` APIを削除することは破壊的な変更になってしまいます。`public`アクセスレベルのAPIと同様に、Obsoleteにし移行方法をアナウンスするなどコストをかける必要があります。（もしくは、いきなり削除してライブラリ利用者に迷惑をかける覚悟を持つ必要があります。）

次に`protected internal`アクセスレベルです。`protected internal`アクセスレベルは「同一のアセンブリ」 もしくは　「その型とその派生型」はアクセスできます。そのため、ライブラリ利用者は派生された型の内部で`protected internal`なメンバにアクセスできます。そのため、`protected internal`なAPIを削除することは破壊的な変更になってしまいます。これも`public`アクセスレベルと同等に慎重に扱わないといけません。

では、C# 7.2から加わった`private protected`アクセスレベルはどうでしょうか？これは、「同一のアセンブリ」 でありかつ　「その型とその派生型」はアクセスできるというアクセスレベルです。ライブラリ利用者は`protected internal`なメンバにアクセスすることができません。「同一のアセンブリ」ではないからです。そのため、そのAPIは問題なく削除できます。

おさらいしましょう。

アセンブリ（DLL形式）でライブラリを配布しているのであれば、

* public
* protected
* protected internal


なアクセスレベルメンバ・型の削除は、破壊的な変更になってしまうので簡単には削除できません。Obsoleteにし移行方法をアナウンスするなどコストをかける必要があります。（もしくは、いきなり削除してライブラリ利用者に迷惑をかけるしかありません）

`protected`そして、`protected internal`は実は結構アクセス範囲が広いということに注意してください。

`protected internal`は、『「同一のアセンブリ」 または　「その型とその派生型」はアクセスできる』であり、『「同一のアセンブリ」 かつ　「その型とその派生型」はアクセスできる』ではないことに注意してください。


※ これはライブラリの話であり、アプリケーションの話とは違うのでその点に注意してください。
※ ベータ版など、破壊的な変更が入る予定があるライブラリもたくさんあります。

# リフレクションを使えばprivateメンバにもアクセスできる

リフレクションを使えばprivateメンバにアクセスできます。

ライブラリ利用者に対して、どうしても隠しておかないといけない情報はprivateにしたとしても、リフレクションを使えばアクセスできます。この点に注意してください。


# アクセス修飾の順番


`private protected` は、`protected private`と同じ意味です。

また、`protected internal`と`internal protected`と同じ意味です。

これらは修飾子の順番を変えても有効です。

# interanalな型をテストしたい場合

interanalな型を別アセンブリのテストプロジェクトでテストしたい場合、`InternalsVisibleToAttribute`を活用しましょう。

# その他いろいろ

アクセス修飾子を省略した場合や、入れ子の型のアクセス修飾子については、公式ドキュンメントをみてください。


* [Accessibility Levels](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/accessibility-levels)
* [Accessibility Domain](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/accessibility-domain)
* [Restrictions on using accessibility levels](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/restrictions-on-using-accessibility-levels)

# 関連

* [アクセス修飾子（公式ドキュメント）](https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/keywords/access-modifiers)
* [C# 7.2 の新機能](https://docs.microsoft.com/ja-jp/dotnet/csharp/whats-new/csharp-7-2#private-protected-access-modifier)

# 補足

この記事は、

* Unity 2019.2
* C# 8

までの仕様です。