構文:アクセス修飾子の種類と違いとは?[C#/VB].NET TIPS

.NETのアクセス修飾子には多くの種類がある。それらを使いこなすことで、クラスや構造体などのコンテナおよびそれらのメンバへのアクセスを適切に設定できる。

» 2018年09月25日 05時00分 公開
[山本康彦BluewaterSoft/Microsoft MVP for Windows Development]
「.NET TIPS」のインデックス

連載「.NET TIPS」

 型とそのメンバにはアクセシビリティーのレベルがあり、それを指定するのがpublicやprivateといったアクセス修飾子(access modifier)だ。本稿ではアクセス修飾子の種類と違いについて解説する。なお、本稿ではpublic(C#)/Public(VB)など、先頭文字が大文字か小文字かだけの違いしかない場合にはC#の記述(先頭文字も小文字)を採用して記述する。

POINT アクセス修飾子の種類と違い

アクセス修飾子の種類と違いのまとめ アクセス修飾子の種類と違いのまとめ


 特定のトピックをすぐに知りたいという方は以下のリンクを活用してほしい。

 なお、本稿に掲載したサンプルコードをそのまま試すにはVisual Studio 2017 Update 5(バージョン15.5)以降が必要である。

アセンブリについて

 ご存じの方は読み飛ばしていただいて構わない。アクセス修飾子の理解にはアセンブリの知識も必要なので、簡単に説明する。

 アセンブリとは、.NET Framework用に作成された(通常は1つの)バイナリファイル(exeファイルまたはdllファイル)を指す。Visual Studio上の1プロジェクトが1アセンブリになる*1。「アセンブリ内からだけアクセス可能」というのは、「プロジェクトの内部からだけアクセスできる」(=別のプロジェクトからはアクセスできない)という意味になる。

*1 複数のファイルにまたがったアセンブリ(マルチファイルアセンブリ)を作ることも可能ではある(「マルチファイル アセンブリ」を参照)。その場合、internal(C#)/Friend(VB)アクセス修飾子の範囲は複数のファイルにまたがることになる。


クラス(トップレベルコンテナ)に付けるアクセス修飾子とは?

 付けられるアクセス修飾子は、トップレベルコンテナとメンバとで異なる。まず、トップレベルコンテナから見ていこう。

 トップレベルコンテナとは、入れ子にされていない型のことだ。名前空間の直下にある型ともいえる。ここで、型とはクラス/列挙型/構造体/インタフェースなどだ。

 トップレベルコンテナのアクセシビリティーレベルは簡単だ。次に示す2項目だけである。

  • 付けられるアクセス修飾子:public、または、internal(C#)/Friend(VB)
  • 既定のアクセシビリティー(アクセス修飾子を付けない場合):internal(C#)/Friend(VB)

 publicアクセス修飾子を付けたトップレベルコンテナは、そのアセンブリの外からもアクセスできる。既定、またはinternal(C#)/Friend(VB)アクセス修飾子を付けたトップレベルコンテナは、アセンブリの外からアクセスできなくなる(同じアセンブリ内だけからアクセス可能)。

 例として、Visual Studio上に2つのコンソールアプリのプロジェクト「ProjectA」/「ProjectB」を作って試してみよう。

 ProjectAプロジェクトには、3つのクラスを追加する(次のコード)。

namespace ProjectA
{
  public class PublicClass
  {}
  internal class InternalClass
  {}
  class DefaultClass
  {}
}

Public Class PublicClass
End Class
Friend Class InternalClass
End Class
Class DefaultClass
End Class

ProjectAプロジェクトに追加した3つのクラス(上:C#、下:VB)

 同じProjectAプロジェクトのMainメソッドからは、3つのクラスのどれにでもアクセスできる(次のコード)。同じアセンブリ内からのアクセスであるからだ。

static void Main(string[] args)
{
  var pc = new PublicClass();
  var ic = new InternalClass();
  var dc = new DefaultClass();
}

Sub Main()
  Dim pc = New PublicClass()
  Dim ic = New InternalClass()
  Dim dc = New DefaultClass()
End Sub

同じアセンブリ内からのアクセス(上:C#、下:VB)
クラスに付けられたアクセス修飾子の違いによらず、同じアセンブリ内からはアクセス可能。

 別のアセンブリであるProjectBプロジェクトのMainメソッドからは、前述した3つのクラスのうち、publicアクセス修飾子を付けたクラスにしかアクセスできない(次のコード)。

static void Main(string[] args)
{
  var pc = new ProjectA.PublicClass();
  //var ic = new ProjectA.InternalClass(); // コンパイルエラー
  //var dc = new ProjectA.DefaultClass(); // コンパイルエラー
}

Sub Main()
  Dim pc = New ProjectA.PublicClass()
  'Dim ic = New ProjectA.InternalClass() ' コンパイルエラー
  'Dim dc = New ProjectA.DefaultClass() ' コンパイルエラー
End Sub

別のアセンブリ内からのアクセス(上:C#、下:VB)
こちらはProjectBプロジェクトのMainメソッドである。ProjectBプロジェクトには、あらかじめProjectAプロジェクトへの参照を追加しておく。
このように、別のアセンブリからは、publicアクセス修飾子を付けたクラスにしかアクセスできない。

publicではないクラスに別のアセンブリからアクセスするには?

 トップレベルコンテナにはpublicアクセス修飾子が付いていない限りは別のアセンブリからアクセスできないと上で述べた。

 基本はそうなのであるが、特定のアセンブリだけからはアクセスを許可したい場合がある。例えば、ユニットテストのプロジェクトからはアクセスさせたいといった場合だ。そのようなときには、アクセスされる側のアセンブリにInternalsVisibleTo属性(Runtime.CompilerServices名前空間)を追加する(次のコード)。

[assembly:InternalsVisibleTo("ProjectB")]

<Assembly: Runtime.CompilerServices.InternalsVisibleTo("ProjectB")>

InternalsVisibleTo属性の例(上:C#、下:VB)
この記述は、アクセスされる側のプロジェクトのAssemblyInfo.cs/.vbファイルの末尾に追加する。この例では、ProjectAプロジェクトのAssemblyInfo.cs/.vbファイルに上の記述を追加することで、ProjectBプロジェクト(=アセンブリ)から、ProjectAプロジェクトのアセンブリ内にある全てのトップレベルコンテナにアクセス可能になる(合わせて、internal(C#)/Friend(VB)アクセス修飾子を付けたメンバにもアクセス可能になる)。
AssemblyInfo.cs/.vbファイルは、C#ではプロジェクトのPropertiesフォルダに、VBではプロジェクトのMy Projectフォルダにある。なお、VBのプロジェクトでは既定ではAssemblyInfo.vbファイルは隠されている。ソリューションエクスプローラで全てのファイルを表示するように設定を切り替えて作業する。

メンバに付けるアクセス修飾子とは?

 メンバに対するアクセシビリティーは、6種類(C#)/5種類(VB)ある。

 ただし、アクセス修飾子を付けないときの既定のアクセシビリティーは、コンテナの種類によってpublicかprivateのどちらかに決まっている。

  • public:コンテナ外からもアクセスできる
  • private:同じコンテナの中からしかアクセスできない

 コンテナの種類とそのメンバに対する既定のアクセシビリティーの一覧を次の表に示す。

コンテナ そのメンバに対する既定のアクセシビリティー
クラス private
構造体 private(C#)/Public(VB)
列挙型 public
インタフェース public
コンテナの種類とそのメンバに対する既定のアクセシビリティー
C#:クラスと構造体のメンバは、既定ではprivate(他からアクセスできない)。
VB:クラスのメンバは、既定ではPrivate(他からアクセスできない)。構造体のメンバは、既定でPublic(他からもアクセス可能)。
列挙型とインタフェースのメンバは、既定でpublic(他からもアクセス可能)。
列挙値は他からアクセスできないと意味がないし、インタフェースは公開するメンバを強制するための仕組みなのでpublicになっている。

 さて、メンバにどのようなアクセス修飾子を付けられるかだが、まず列挙型とインタフェースのメンバには何も付けられない。上の表で示した既定のアクセシビリティーであるpublicになる。列挙型とインタフェースのメンバは常にpublicなのである。

 残りのクラスと構造体のメンバには、public/private以外にも付けられるアクセス修飾子がある(次の表)。なお、クラスには全てのアクセス修飾子を付けられるが、構造体にはpublic/internal(C#)/Friend(VB)/privateアクセス修飾子の3種類しか付けられない。

アクセス修飾子 構造体での利用 意味
public どこからでもアクセスできる(ただし、コンテナのアクセシビリティーに制限される)
internal(C#)/
Friend(VB)
アセンブリ外からはアクセスできない
private そのコンテナ内からしかアクセスできない
protected × privateに加えて、継承したクラスからもアクセスできる
protected internal(C#)/
Protected Friend(VB)
× protectedに加えて、同じアセンブリからもアクセスできる。いわば「protected OR internal」
protected private(C# 7.2以降のみ) × protectedと同様だが、ただし、継承したクラスであっても別のアセンブリからはアクセスできない。いわば「protected AND internal」
クラスと構造体のメンバに付けられるアクセス修飾子
構造体は継承できないので、「protected」を含むアクセス修飾子を利用できない。静的クラス(static修飾子を付けたクラス)も継承できないので、構造体と同様である。
メンバにpublicアクセス修飾子を付けていても、コンテナのアクセシビリティーに制限されることがある。コンテナにpublicアクセス修飾子が付いていないときは、メンバにpublicアクセス修飾子が付いていても別のアセンブリからアクセスできない。
C#の「protected internal」/「protected private」は、記述順を入れ替えてもよい(「protected private」はマイクロソフトのドキュメントでは「private protected」と記載されている)。
「protected private」はC# 7.2で導入された新機能である。継承先からのアクセスを他のアセンブリにも許すかどうかで、protectedと使い分ける。なお、VB 15(Visual Studio 2017のVB)ではサポートされていない。また、Visual Studio 2017 15.5でC# 7.2を使うには、プロジェクトの設定を変更する必要がある(変更手順は「Dev Basics/Keyword:C# 7.1」を参照)。

 クラスのメンバに付けられるアクセス修飾子の例を見てみよう。前述のProjectAプロジェクトのPublicClassクラスに、次のコードのようにメンバを追加する。

namespace ProjectA
{
  public class PublicClass
  {
    public string PublicMember { get; set; }
    internal string InternalMember { get; set; }
    private string PrivateMember { get; set; }
    protected string ProtectedMember { get; set; }
    protected internal string ProtectedInternalMember { get; set; }
    protected private string ProtectedPrivateMember { get; set; }
  }
}

Public Class PublicClass
  Public Property PublicMember As String
  Friend Property InternalMember As String
  Private PrivateMember As String
  Protected Property ProtectedMember As String
  Protected Friend Property ProtectedInternalMember As String
  'Protected Private Property ProtectedPrivateMember As String ' VB 15では未サポート
End Class

ProjectAプロジェクトのPublicClassクラスにメンバを追加した(上:C#、下:VB)
C#のinternalとVBのFriendは同じ意味である。メンバ名をC#とVBでそろえたが、例えばVBのInternalMemberプロパティは「Friendなメンバ」の意味だ。

 上記のメンバに対して、同じクラスの中からは全てにアクセスできる(次のコード)。

namespace ProjectA
{
  public class PublicClass
  {
    ……省略……
    public void PublicClassMethod()
    {
      this.PublicMember = "foo";
      this.InternalMember = "foo";
      this.PrivateMember = "foo";
      this.ProtectedMember = "foo";
      this.ProtectedInternalMember = "foo";
      this.ProtectedPrivateMember = "foo";
    }
  }
}

Public Class PublicClass
  ……省略……
  Public Sub PublicClassMethod()
    Me.PublicMember = "foo"
    Me.InternalMember = "foo"
    Me.PrivateMember = "foo"
    Me.ProtectedMember = "foo"
    Me.ProtectedInternalMember = "foo"
  End Sub
End Class

同じクラス内からのアクセス(上:C#、下:VB)
同一クラス内からであれば、どのアクセス修飾子のメンバにもアクセスできる。

 ProjectAプロジェクトに、PublicClassを継承したChildClassを追加し、その中からPublicClassのメンバにアクセスしてみよう(次のコード)。同じアセンブリ内で継承したクラスでは、このようにprivate以外のメンバにアクセスできる。

namespace ProjectA
{
  class ChildClass : PublicClass
  {
    void ChildClassMethod()
    {
      base.PublicMember = "foo";
      base.InternalMember = "foo";
      //base.PrivateMember = "foo"; // コンパイルエラー
      base.ProtectedMember = "foo";
      base.ProtectedInternalMember = "foo";
      base.ProtectedPrivateMember = "foo";
    }
  }
}

Public Class ChildClass
  Inherits PublicClass

  Public Sub ChildClassMethod()
    MyBase.PublicMember = "foo"
    MyBase.InternalMember = "foo"
    'MyBase.PrivateMember = "foo" ' コンパイルエラー
    MyBase.ProtectedMember = "foo"
    MyBase.ProtectedInternalMember = "foo"
  End Sub
End Class

同じアセンブリ内の子クラスからアクセス(上:C#、下:VB)
private以外のメンバにアクセスできる。

 同じアセンブリ内でも継承関係にないクラスからのアクセスでは、さらにprotectedメンバにもアクセスできなくなる(次のコード)。ただし、「protected internal(C#)/Protected Friend(VB)」アクセス修飾子には、internalとしてのアクセスが可能だ。

namespace ProjectA
{
  class SameAssemblyClass
  {
    void SameAssemblyClassMethod()
    {
      var pc = new PublicClass();
      pc.PublicMember = "foo";
      pc.InternalMember = "foo";
      //pc.PrivateMember = "foo"; // コンパイルエラー
      //pc.ProtectedMember = "foo"; // コンパイルエラー
      pc.ProtectedInternalMember = "foo"; // internalとしてのアクセス
      //pc.ProtectedPrivateMember = "foo"; // コンパイルエラー
    }
  }
}

Public Class SameAssemblyClass
  Public Sub SameAssemblyClassMethod()
    Dim pc = New PublicClass()
    pc.PublicMember = "foo"
    pc.InternalMember = "foo"
    'pc.PrivateMember = "foo" ' コンパイルエラー
    'pc.ProtectedMember = "foo" ' コンパイルエラー
    pc.ProtectedInternalMember = "foo" ' Friendとしてのアクセス
  End Sub
End Class

同じアセンブリ内の継承関係にないクラスからアクセス(上:C#、下:VB)
ProjectAプロジェクトに、このSameAssemblyClassクラスを追加した。
publicとinternalのメンバにアクセスできる。ただし、「protected internal(C#)/Protected Friend(VB)」のメンバには、internalとしてのアクセスが可能だ。

 では、別のアセンブリからのアクセスを試してみよう。まずは、継承関係にないAnotherAssemblyClassクラスをProjectBプロジェクトに作り、ここまでと同じようにProjectAプロジェクトのPublicクラスにアクセスしてみる(次のコード)。

namespace ProjectB
{
  class AnotherAssemblyClass
  {
    void AnotherAssemblyClassMethod()
    {
      var pc = new ProjectA.PublicClass();
      pc.PublicMember = "foo";
      //pc.InternalMember = "foo"; // コンパイルエラー
      //pc.PrivateMember = "foo"; // コンパイルエラー
      //pc.ProtectedMember = "foo"; // コンパイルエラー
      //pc.ProtectedInternalMember = "foo"; // コンパイルエラー
      //pc.ProtectedPrivateMember = "foo"; // コンパイルエラー
    }
  }
}

Public Class AnotherAssemblyClass
  Public Sub AnotherAssemblyClassMethod()
    Dim pc = New ProjectA.PublicClass()
    pc.PublicMember = "foo"
    'pc.InternalMember = "foo" ' コンパイルエラー
    'pc.PrivateMember = "foo" ' コンパイルエラー
    'pc.ProtectedMember = "foo" ' コンパイルエラー
    'pc.ProtectedInternalMember = "foo" ' コンパイルエラー
  End Sub
End Class

別のアセンブリ内の継承関係にないクラスからアクセス(上:C#、下:VB)
ProjectBプロジェクトに、このAnotherAssemblyClassクラスを追加した。
publicメンバだけにアクセスできる。
なお、「publicではないクラスにアセンブリ外からアクセスするには?」で述べたInternalsVisibleTo属性がProjectAプロジェクトに付いている場合は、internal/protected internal(Protected Friend)メンバにもアクセス可能になる。

 最後に、別のアセンブリで継承したクラスの場合だ。ProjectAプロジェクトのPublicクラスを継承したAnotherAssemblyChildClassクラスを、ProjectBプロジェクトに追加する(次のコード)。前の例に比べて、protectedにもアクセスできるようになる。

namespace ProjectB
{
  public class AnotherAssemblyChildClass : ProjectA.PublicClass
  {
    public void AnotherAssemblyChildClassMethod()
    {
      base.PublicMember = "foo";
      //base.InternalMember = "foo"; // コンパイルエラー
      //base.PrivateMember = "foo"; // コンパイルエラー
      base.ProtectedMember = "foo";
      base.ProtectedInternalMember = "foo";
      //base.ProtectedPrivateMember = "foo"; // コンパイルエラー
    }
  }
}

Public Class AnotherAssemblyChildClass
  Inherits ProjectA.PublicClass

  Public Sub AnotherAssemblyChildClassMethod()
    MyBase.PublicMember = "foo"
    'MyBase.InternalMember = "foo" ' コンパイルエラー
    'MyBase.PrivateMember = "foo" ' コンパイルエラー
    MyBase.ProtectedMember = "foo"
    MyBase.ProtectedInternalMember = "foo"
  End Sub
End Class

別のアセンブリ内の子クラスからアクセス(上:C#、下:VB)
ProjectBプロジェクトに、このAnotherAssemblyChildClassクラスを追加した。
ProjectAプロジェクトのPublicClassを継承しているので、publicメンバに加えてprotectedメンバにもアクセスできる(C#の「protected private」を除く)。

まとめ

 本稿では、アクセス修飾子の種類とそれぞれのアクセシビリティーを解説した。実際にコーディングするに当たっては、アクセス修飾子なしで書き始めてから必要に応じてアクセシビリティーを広げていくという戦略を、慣れるまでは採用するとよいだろう。不必要に広いアクセシビリティーを与えてしまうというミスを防ぐことができる。

利用可能バージョン:.NET Framework 1.1以降(一部、C# 7.2以降)
カテゴリ:C# 処理対象:言語構文
カテゴリ:Visual Basic 処理対象:言語構文
関連TIPS:手軽にプロパティを実装するには?[C#、VS 2008、3.5]


更新履歴

【2018/09/25】本記事の一部に以下のような誤りがありました。おわびして訂正させていただくとともに、ご指摘いただいた読者の方に感謝いたします。
誤: クラスと構造体のメンバは、既定ではprivate(他からアクセスできない)。
正: C#:クラスと構造体のメンバは、既定ではprivate(他からアクセスできない)。
VB:クラスのメンバは、既定ではPrivate(他からアクセスできない)。構造体のメンバは、既定でPublic(他からもアクセス可能)。

【2018/01/24】初版公開。


「.NET TIPS」のインデックス

.NET TIPS

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。