protectedとinternalについて

protectedとinternalについて

おはようございます。最近 C#を使った開発が増えてきましたが、今回は protected と internal について調べました。

C#を使って開発をして日が浅い方の参考にもなれば幸いです。

環境

OS:Windows11

エディタ:Visual Studio2022

フレームワーク:.NET6

プロジェクト名:WPF_Test_App

protected

protected は自分のクラス(基底クラス)と派生クラスからのみ参照することができます。派生クラスは継承先のクラスのことです。

実際にどのような動きになるのかコードで見ていきます。

using System;
using System.Diagnostics;
using System.Windows;

namespace WPF_Test_App
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            Test();
        }

        public void Test()
        {
            var b = new B();
            b.Main();
        }
    }

    // 基底クラス
    class A
    {
        // 派生クラスでも利用できるフィールド
        protected string say = "Hello World";

        public void Say()
        {
            Console.WriteLine(say);
        }

        protected void ProtectedSay()
        {
            Console.WriteLine(say);
        }

        private void PrivateSay()
        {
            Console.WriteLine(say);
        }

        protected internal void ProtectedInternalSay()
        {
            Console.WriteLine(say);
        }

        internal void InternalSay()
        {
            Console.WriteLine(say);
        }

    }

    // 派生クラス(クラスAを継承した継承先のクラス)
    class B : A
    {

        public void Main()
        {
            var a = new A(); // 基底クラスをインスタンス化
            var b = new B(); // 派生クラスをインスタンス化

            // A型の修飾子をとおしてプロテクトメンバーA.sayにアクセスすることはできません。
            // 修飾子はB型、またはそれから派生したものでなければなりません
            // 基底クラスでprotectedのものは、異なるクラスからのアクセスはprivateと同等となるのでエラーになる
            //a.say = "World"; // Error CS1540

            // publicは派生クラスからでもアクセスできる
            // 基底クラスのメソッド経由でフィールドにアクセスしている
            a.Say(); // Hello World

            // baseキーワードで派生クラスから基底クラス内のメソッドなどにアクセス出来る
            base.ProtectedSay();
            base.InternalSay();
            base.ProtectedInternalSay();

            // privateはアクセスできない
            //base.PrivateSay();

            Debug.WriteLine(b.say); // Hello World

            // 基底クラスのフィールドを書き換える
            b.say = "World";

            Debug.WriteLine(b.say); // World
        }
    }
}

70 行目にa.say = "World"; // Error CS1540とありますが、Visual Studio では以下のようなエラーメッセージが出力されています。

A 型の修飾子をとおしてプロテクトメンバー A.say にアクセスすることはできません。修飾子は B 型、またはそれから派生したものでなければなりません

これは、基底クラスを直接インスタンス化して protected のフィールドにアクセスしている為に発生しています。このような使い方をするのは特殊だと思いますが、今回は修飾子の動作確認として使用しています。

基底クラスで protected のものは、異なるクラスからのアクセスは private と同等となるのでエラーになります。

internal

これは、同一のプログラム(アセンブリ)内からのアクセスを許可します。アセンブリとは exe ファイルや dll ファイルのことを指しますが、Visual Studio 内では同じプロジェクト内という意味になります。(別のプロジェクトからはアクセスできない)

先程のコードでクラス A と B にはアクセス修飾子がついていませんが、何もつけない場合は internal が付与されているものとみなされます。

因みに Visual Studio2022 から、プロジェクトに新しくクラスを作成した場合はデフォルトで internal が付与されています。

namespace WPF_Test_App
{
    internal class Class1
    {
    }

    // internal が付与されているのと同じ
    class Class2
    {
    }
}

異なるプロジェクトを作成する

実際に異なるプロジェクトを作成して動きを確認していきます。

ソリューションエクスプローラーを右クリックして、「追加」「新しいプロジェクト」と進みます。

その後、適当なプロジェクト名を指定して新しく作成します。今回プロジェクト名は WPFInternal としました。

2021-09-22_01.png

次に、プロジェクトに WPFInternalClass1 クラスを追加します。

2021-09-22_02.png

2021-09-22_03.png

クラスのアクセス修飾子がデフォルトで internal になっていると思いますので、一旦 public に変更しておきます。

namespace WPFInternal
{
    public class WPFInternalClass1
    {
        internal string hello = "world";
    }
}

それでは WPF_Test_App から参照できるように設定します。

WPF_Test_App の「依存関係」を右クリック、「プロジェクト参照の追加」を選択

2021-09-22_04.png

参照に追加するプロジェクトにチェックをいてれ「OK ボタン」をクリック

2021-09-22_05.png

これで WPF_Test_App から WPFInternal プロジェクトを参照できるようになりました。

WPF_Test_App の MainWindow.xaml.cs に WPFInternalClass1 を参照するような処理を追加します。

amespace WPF_Test_App
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        public void Test1()
        {
            var c = new Class1();

            MessageBox.Show(c.hoge);
        }

        public void Test2()
        {
            var c = new WPFInternal.WPFInternalClass1();

            MessageBox.Show(c.hello); // エラーコード CS0122
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            //Test1();
            Test2();
        }
    }
}

アクセスできない保護レベルであると出力されてエラーになります。

アクセス修飾子を指定しないと、保護レベルが internal になると述べましたが WPFInternalClass1 を以下のように修正して確認してみましょう。

namespace WPFInternal
{
    class WPFInternalClass1
    {
        internal string hello = "world";
    }
}

すると、インスタンス化する行のコードでもエラーが発生していることがわかります。

public void Test2()
{
   // WPFInternalClass1 クラスの public を外すと エラーコード CS0122 が発生する
   //var c = new WPFInternal.WPFInternalClass1();

   //MessageBox.Show(c.hello); // エラーコード CS0122
}

最後に

今回は protected と internal に絞って見ていきましたが、サンプルコードをちょっと書き換えたり色々なパターンを試すと理解が深まると思います。

今回動作確認に使用したプロジェクトは Github に上げていますので、ちょっと動きを確認したいなという場面で使用してみてください。

参考