WisdomSoft - for your serial experiences.

アクセス修飾子

クラスのメンバに対し、クラスの外からアクセスできるかどうかを修飾子で設定できます。内部でのみ使う機能は外部には公開せず、外部との対話に必要な機能のみを公開することで、クラスの再利用性や保守性を高めることができます。

メンバの可視性を制御する

メンバ宣言では、冒頭に修飾子を指定することで外部からメンバに対するアクセスを制御できます。例えば、フィールドのようなクラスの振る舞いに重要な影響を与えるメンバは、外部から無秩序に変更されるべきではありません。このようなメンバは外部からアクセスできないように設定できます。このような考え方をメンバのアクセシビリティ(accessibility)と呼びます。

メンバがアクセス可能か、もしくはアクセス不可能かどうかは、アクセシビリティを設定する修飾子によって決定されます。フィールドやメソッドといったクラスのメンバ宣言では、アクセシビリティを設定する以下のような修飾子を指定できます。

  • public - 公開
  • protected - 保護
  • internal - 内部
  • private - 非公開

これらを総称してアクセス修飾子(access modifiers)とも呼びます。アクセス修飾子を省略したメンバは、既定で private となります。

公開(パブリック)

これまでも使ってきたように、クラスのメンバを外部に公開する場合は public 修飾子(public modifier)を指定します。public 修飾子を持つメンバは、あらゆる場所からアクセスできます。そのクラス自身の他のメンバや派生クラスのメンバからアクセスできるのはもちろん、無関係の任意のクラス内からもアクセスできます。また、プログラムがライブラリ(通常は DLL ファイル)としてビルドされている場合、別のプログラムからもアクセスできることを表します。

class A
{
	public void AM() { }
}

class B : A
{
	public void BM() { AM(); } //OK
}

class X
{
	public void XM()
	{
		A obj = new A();
		obj.AM();		//OK
	}
}

すなわち public なメンバは、メンバを含む型を参照できる位置からであればクラスの関係にかかわらず呼び出すことができます。

コード1
class MagicalGirl
{
    public string Name { get; set; }
}

class Sample
{
    static void Main(string[] args)
    {
        MagicalGirl nanoha = new MagicalGirl();
        nanoha.Name = "高町なのは";
        System.Console.WriteLine("Name=" + nanoha.Name);
    }
}
実行結果
コード1 実行結果

コード1の MagicarGirl クラスの Name プロパティは public 修飾子を指定しているため、クラスの外部からアクセスできます。Sample クラスは MagicalGirl クラスとは継承関係も何もありませんが、Name プロパティのアクセシビリティは public なのでアクセスできます。 

非公開(プライベート)

対象のメンバがどこからでもアクセス可能であることを宣言する public 修飾子に対し、クラス外からはアクセスできない、クラス内でのみ利用できる非公開メンバを private 修飾子(private modifier)で宣言できます。

private で宣言されたメンバは、その宣言されたクラス内でのみ利用できます。クラスの外部はもちろん、派生クラスからもアクセスできません。メンバを宣言したクラス(メンバを含んでいる型)のみがアクセスできます。外部から利用する必要のない一時的な、内部処理用のメンバ宣言に用いられます。

class A
{
	private void AM() {}
	public void CallAM() { AM(); } //OK
}

class B : A
{
	public void BM() { AM(); } //エラー
}

class X
{
	public void XM()
	{
		A obj = new A();
		obj.AM();		//エラー
	}
}

クラスのメンバの何を外部に公開し、何を非公開にするかは、クラスの設計で極めて重要です。基本的に、対象のメンバの呼び出しがクラスのインスタンスに何らかの副作用を及ぼすような操作に対しては公開に慎重になるべきです。多くの場合、クラスの状態を保持するフィールドは private にします。また、内部的なデータの整理を行うメソッドなど、外部から呼び出す必要がないメンバも private にしましょう。

フィールドやメソッドだけではなく、コンストラクタも必要に応じて private に設定することができます。外部からインスタンスの生成を禁止し、自己のインスタンス生成を管理するといった設計手法に応用されます。

コード2
class MagicalGirl
{
    private string name;
    public MagicalGirl(string name)
    {
        this.name = name;   //OK
    }
    public void Write()
    {
        System.Console.WriteLine("魔法少女 " + name);  //OK
    }
}

class Sample
{
    static void Main(string[] args)
    {
        MagicalGirl nanoha = new MagicalGirl("高町なのは");
        //System.Console.WriteLine(nanoha.name); //エラー
       nanoha.Write();
    }
}
実行結果
コード2 実行結果

コード2の MagicalGirl クラスは private 修飾子で宣言された name フィールドを持ちます。このフィールドは同じクラスのコンストラクタで初期化され、 Write() メソッドで name フィールドを出力されます。どちらもメンバを宣言しているクラス内の処理なので、問題なくアクセスできます。

一方、クラスの外部から非公開のメンバにアクセスできないため、Sample クラスの Main() メソッドから name フィールドにアクセスするとエラーが発生してしまいます。

保護(プロテクト)

クラスの外部からアクセスする必要のないメンバは private 修飾子で非公開にするべきです。しかし、メンバがクラスの動作に関わる重要な機能の一部である場合、そのメンバが派生クラスで必要になる可能性があります。つまり、無関係な外部のクラスには非公開にしつつ、自分自身の機能を拡張する派生クラスには公開したいメンバです。

例えば、クラスの状態を変更する処理を行った後、最後に何らかのデータ整合処理(いわゆる Validation)を行わなければならないという規約があるとします。このような内部で解決できる規約は外部に公開する必要はない(してはいけない)ので非公開にするべきですが、一方で派生クラスでは規約を引き継いで機能を拡張するため、基本クラスのメンバを呼び出す必要が出てくるでしょう。

このような、派生クラスからのアクセスを許可するメンバは protected 修飾子(protected modifier)で宣言できます。protected 修飾子で宣言されたメンバは、メンバを宣言したクラスと、そのクラスの派生クラスからのみアクセスできます。

class A
{
	protected void AM() {}
}

class B : A
{
	public void BM() { AM(); } //OK
}

class X
{
	public void XM()
	{
		A obj = new A();
		obj.AM();		//エラー
	}
}

public 修飾子や private 修飾子に比べると利用頻度は少なくなりますが、再利用可能な基本クラスを作成するときに protected 修飾子を持つメソッドを応用することが多いでしょう。代表的な例は、基本クラスで protected な仮想メソッドを用意し、派生クラスでオーバーライドすることで機能をカスタマイズできるという設計です。

コード3
class MagicalGirl
{
    protected string Name { get; set; }
    protected string Magic { get; set; }
    public void UseMagic()
    {
        System.Console.WriteLine(Name + "「" + Magic + "」");
    }
}

class Nanoha : MagicalGirl
{
    public Nanoha()
    {
        Name = "高町なのは";
        Magic = "全力全開 スターライトブレイカー!";
    }
}

class Fate : MagicalGirl
{
    public Fate()
    {
        Name = "フェイト・テスタロッサ";
        Magic = "雷光一閃 プラズマザンバー!";
    }
}

class Sample
{
    static void Main(string[] args)
    {
        MagicalGirl nanoha = new Nanoha();
        nanoha.UseMagic();

        MagicalGirl fate = new Fate();
        fate.UseMagic();
    }
}
実行結果
コード3 実行結果

コード3の MagicalGirl クラスは protected 修飾子で宣言された Name プロパティと Magic プロパティを持ちます。これらは protected 修飾子で保護されているため、外部のクラスからアクセスすることはできません。ですが、MagicalGirl クラスを継承した派生クラスからであれば protected 修飾子で保護されたメンバにアクセスできます。

このプログラムでは Name プロパティと Magic プロパティが派生クラスで初期化されることを期待しており、MagicalGirl クラスでは初期化していません。MagicalGirl クラスを継承する Nanoha クラスと Fate クラスは、それぞれのコンストラクタ内で Name プロパティと Magic プロパティに文字列を設定しています。MagicalGirl クラスの UseMagic() メソッドを呼び出すと、派生クラスで設定された Name プロパティと Magic プロパティの値を出力します。

このように、派生クラスから読み書きでき、しかし外部からはアクセスできないメンバを protected 修飾子で宣言できます。

内部

基本的に、クラスの内部で完結している機能を外部に公開するべきではありません。クラスをプログラムの部品として考えたとき、部品として再利用するために必要な機能のみが公開されていれば十分です。クラスの内部処理に関わる機能が、クラスの内部設計を理解していない外部の開発者から無秩序に呼び出されるのを防ぐことができます。

しかし、異なるクラスであっても同一のソースコードや同一のコンパイル単位(プロジェクト)であれば、それらは同じ開発者によって管理されていると考えることができます。同一の開発者によって管理されているクラス間であれば、内部の設計も理解されているはずです。そこで、同一のプログラムとしてコンパイルされている場合はアクセスを許可する内部アクセシビリティが用意されています。内部アクセシビリティのメンバは internal 修飾子(internal modifier)で宣言できます。

C# で開発する .NET Framework を基盤とするプログラムには、アプリケーションとして実行する EXE ファイルだけではなく、他のアプリケーションに再利用可能なプログラムの部品を提供する DLL ファイルなどが存在します。例えば、プログラムを DLL ファイルとしてビルドし、他のアプリケーションから DLL ファイルを参照してクラスなどを利用できます。このとき、再利用可能なクラスを集めたライブラリを DLL ファイルとしてコンパイルすれば internal 修飾子のメンバは DLL ファイルを参照する外部のプログラムからアクセスできません。

本来はクラスの外部に公開したくない機能でも、一部の処理においてパフォーマンスの最適化などのために呼び出せると便利な場合があります。そのような場合に internal 修飾子による内部アクセシビリティが役にたつでしょう。

コード4
public class MagicalGirl
{
    internal string Name;
}

class Sample
{
    static void Main(string[] args)
    {
        MagicalGirl nanoha = new MagicalGirl();
        nanoha.Name = "高町なのは";
        System.Console.WriteLine("Name=" + nanoha.Name);
    }
}
実行結果
コード4 実行結果

コード4では MagicalGirl クラスの Name プロパティが internal 修飾子で宣言されています。このプロパティは内部アクセシビリティとなるため、外部のプログラムからアクセスすることはできません。しかし、コード4は同じソースコード内であり、同じコンパイル単位なので、異なるクラスからもアクセスできます。

通常、アクセス修飾子を組み合わせて宣言することはできません。例えば public private や internal private といった組み合わせは無効です。ただし、唯一 internal 修飾子と protected 修飾子を組み合わせた protected internal 修飾子は有効です。これは、見ての通り保護アクセシビリティまたは内部アクセシビリティを表し、同一のプログラムからは自由にアクセスでき、加えて外部のプログラムでも派生クラスであればアクセスできるというアクセシビリティです。

internal 修飾子と protected internal 修飾子のアクセシビリティに関する詳細は、名前空間とクラスのアクセシビリティの解説の後に改めて紹介します。