C#を用いたオブジェクト指向プログラミングにおいて、クラスの設計を柔軟かつ安全に行うために欠かせないのが「アクセス修飾子」の存在です。

アクセス修飾子を適切に使い分けることで、プログラムの外部に公開する情報と、内部に隠蔽する情報を明確に区別し、予期せぬ不具合を防ぐことができます。

数あるアクセス修飾子の中でも、継承関係にあるクラス間でのみアクセスを許可する「protected」修飾子は、再利用性の高いコードを書く上で極めて重要な役割を担います。

本記事では、C#の protected 修飾子の基本的な使い方から、他の修飾子との違い、そして設計におけるベストプラクティスまでを詳しく解説します。

C#におけるprotected修飾子の基本概念

C#の protected 修飾子は、クラスのメンバ(フィールド、プロパティ、メソッドなど)に対して、「そのクラス自身」および「そのクラスを継承した派生クラス」からのアクセスを許可するためのものです。

オブジェクト指向の三大要素の一つである「継承」を利用する際、親クラス(基底クラス)で定義した機能を子クラス(派生クラス)で活用したい場面が多々あります。

しかし、public に設定するとクラス外部の全く関係のない場所からも操作が可能になってしまい、カプセル化が損なわれます。

一方で private に設定すると、子クラスからさえもアクセスできなくなり、継承による拡張が困難になります。

このジレンマを解決するのが protected です。

基本的な実装例

まずは、protected 修飾子を使用したもっともシンプルなコード例を見てみましょう。

基底クラスで定義した変数を、派生クラスのメソッド内で操作する様子を確認してください。

C#
using System;

namespace ProtectedSample
{
    // 基底クラス(親クラス)
    public class Animal
    {
        // protected修飾子により、派生クラスからアクセス可能
        protected string speciesName;

        public Animal(string name)
        {
            speciesName = name;
        }
    }

    // 派生クラス(子クラス)
    public class Dog : Animal
    {
        private string breed;

        public Dog(string name, string breed) : base(name)
        {
            this.breed = breed;
        }

        public void PrintDetails()
        {
            // 親クラスのprotectedメンバにアクセスできる
            Console.WriteLine($"種族: {speciesName}, 犬種: {breed}");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Dog myDog = new Dog("イヌ", "柴犬");
            myDog.PrintDetails();

            // 以下の行はコンパイルエラーになります
            // Console.WriteLine(myDog.speciesName); 
            // 理由: speciesNameはprotectedであり、Programクラス(外部)からはアクセス不可
        }
    }
}
実行結果
種族: イヌ, 犬種: 柴犬

この例では、speciesName というフィールドに protected を指定しています。

Dog クラスは Animal クラスを継承しているため、メソッド内で自由にこのフィールドを利用できます。

しかし、Main メソッドのように継承関係のない外部のクラスから myDog.speciesName と呼び出そうとすると、コンパイルエラーが発生します。

これが protected の持つ基本的な制約です。

アクセス修飾子の種類と比較表

C#には複数のアクセス修飾子が存在し、それぞれアクセスできる範囲が厳密に定められています。

protected の立ち位置を正しく理解するために、他の修飾子と比較してみましょう。

修飾子アクセス可能な範囲
public制限なし。どこからでもアクセス可能。
protected所属するクラス、およびその派生クラス内からアクセス可能。
internal同じアセンブリ(プロジェクト)内からアクセス可能。
protected internal同じアセンブリ内、または他アセンブリの派生クラスからアクセス可能。
private protected同じアセンブリ内の派生クラスからのみアクセス可能。
private所属するクラス内からのみアクセス可能。

この表からわかるように、protected は「継承」という縦のつながりに特化したアクセス制限を提供します。

protectedとprivate・internalの決定的な違い

初心者の方が特に混同しやすいのが、privateinternal、そして protected の使い分けです。

それぞれの違いをより深く掘り下げます。

protected と private の違い

private は、そのクラスの内部でしか使用できません。

たとえ継承したクラスであっても、親の private メンバに触れることは不可能です。

これに対し、protected は「自分自身と子供たち」には公開するというニュアンスを持ちます。

設計の指針としては、将来的にサブクラスでカスタマイズしたり、値を参照したりする必要がある場合は protected を選択し、完全に隠蔽して変更を許したくない場合は private を選択します。

protected と internal の違い

internal は「プロジェクト内(アセンブリ内)なら誰でもOK」という横のつながりに対する許可です。

一方で protected は、プロジェクトが異なっても「継承関係があればOK」という性質を持ちます。

例えば、あなたがライブラリ(DLL)を作成している場合、ライブラリを利用するユーザーがあなたのクラスを継承して新しい機能を作るとき、internal ではユーザーから見えませんが、protected であればユーザーのコードからアクセスできるようになります。

複合的なアクセス修飾子:protected internalとprivate protected

C#には、protected と他の修飾子を組み合わせた、より高度な制御方法が2つ存在します。

これらは大規模なライブラリ設計において非常に重要です。

protected internal (OR条件)

protected internal は、protected 「または」 internal という意味になります。

つまり、同じプロジェクト内にいるクラスか、あるいは別プロジェクトであっても継承関係にあるクラスであればアクセス可能です。

これは「同じパッケージ内では便利に使い回したいが、パッケージ外でも継承して拡張する人には公開したい」というケースに適しています。

private protected (AND条件)

C# 7.2で導入された比較的新しい修飾子である private protected は、protected 「かつ」 internal という意味になります。

すなわち、同じプロジェクト内にあり、かつ継承関係にあるクラスからのみアクセスを許可します。

別のアセンブリで継承された場合にはアクセスさせたくない、という極めて限定的なスコープを定義する際に利用されます。

protected修飾子を効果的に活用する設計パターン

protected をただの変数隠蔽の手段として使うだけでなく、デザインパターンと組み合わせることで、より堅牢なプログラムを構築できます。

テンプレートメソッドパターンでの利用

protected の最も一般的な用途の一つが、基底クラスで大まかな処理の流れを定義し、細部の実装を派生クラスに任せる「Template Method パターン」です。

C#
using System;

namespace DesignPatternSample
{
    public abstract class DocumentProcessor
    {
        // 外部にはこのメソッドだけを公開
        public void Process()
        {
            Open();
            Execute(); // 派生クラスで異なる処理
            Close();
        }

        private void Open() => Console.WriteLine("ドキュメントを開きます。");
        private void Close() => Console.WriteLine("ドキュメントを閉じます。");

        // 派生クラスでのみ実装・利用させたいメソッドをprotectedにする
        protected abstract void Execute();
    }

    public class PdfProcessor : DocumentProcessor
    {
        protected override void Execute()
        {
            Console.WriteLine("PDF形式として解析・変換を行います。");
        }
    }

    class Program
    {
        static void Main()
        {
            DocumentProcessor processor = new PdfProcessor();
            processor.Process();
            
            // processor.Execute(); // 外部からは呼び出せないため安全
        }
    }
}
実行結果
ドキュメントを開きます。
PDF形式として解析・変換を行います。
ドキュメントを閉じます。

このパターンでは、Execute メソッドを protected にすることで、利用者が誤って直接実行することを防ぎつつ、継承先でのカスタマイズを可能にしています。

プロパティにおけるアクセスの非対称性

プロパティの get は公開し、set だけを継承先に限定したい場合にも protected が役立ちます。

C#
public class Player
{
    // 読み取りはどこからでもできるが、書き込みは自分か子クラスのみ
    public int Health { get; protected set; } = 100;

    public void TakeDamage(int damage)
    {
        Health -= damage;
    }
}

public class BossPlayer : Player
{
    public void Heal()
    {
        // 子クラスなのでsetにアクセス可能
        Health += 50; 
    }
}

このように、public get; protected set; という記述は、「状態の参照は自由だが、状態の変更権限は一族に限定する」という明確な意思表示になります。

実装時の注意点とよくある誤解

protected を使用する際には、いくつかテクニカルな注意点があります。

インスタンスを通じたアクセス制限

protected メンバは派生クラス内からアクセスできますが、それは「自分自身のインスタンス」を操作する場合に限られます。

例えば、派生クラス Derived の中で、基底クラス Base 型の別のインスタンスを受け取り、その protected メンバを操作しようとするとコンパイルエラーになる場合があります。

C#
public class Base
{
    protected int value;
}

public class Derived : Base
{
    public void Compare(Base other)
    {
        // this.value はOK
        // other.value はNG(otherが自分と同じDerived型である保証がないため)
    }
}

これは C# の言語仕様による安全策です。

派生クラスは「自分が継承した部分」については詳しく知っていますが、「親クラスの他のインスタンス」がどのような状態であるか、あるいは別の派生クラス(例:Derived2)である可能性を考慮し、アクセスを制限しています。

密結合の回避

protected を多用しすぎると、基底クラスと派生クラスが「密結合」の状態になり、基底クラスの変更が多くの派生クラスに甚大な影響を与えるリスクが生じます。

「何でもかんでも protected にする」のではなく、可能な限り private で隠蔽し、どうしても必要な部分だけを protected で公開するという「最小権限の原則」を意識することが、長期的なメンテナンス性を保つ秘訣です。

まとめ

C#の protected 修飾子は、継承を駆使したオブジェクト指向設計において、「カプセル化」と「拡張性」のバランスを取るための極めて強力なツールです。

  • protected: 自クラスと派生クラスからアクセス可能。
  • private との違い: 継承先での再利用・カスタマイズを認めるかどうか。
  • internal との違い: 同一プロジェクト内か、継承関係の縦ラインか。
  • 設計のコツ: テンプレートメソッドパターンや、プロパティの setter 限定公開などで活用する。

適切なアクセス修飾子の選択は、ソースコードの可読性を高めるだけでなく、チーム開発におけるバグの混入を防ぎ、システムの堅牢性を引き上げます。

今回学んだ protected の特性を活かし、より洗練されたC#プログラムの設計に挑戦してみてください。