Top > Programming > .NetFramework > Tips > HideBaseClassPublicProperty
Last-modified: Tue, 26 Nov 2013 08:36:10 JST
Counter:22274 Today:4 Yesterday:14 Online:8
このエントリーをはてなブックマークに追加

基底クラスの public なプロパティを継承クラスで隠す方法

About

C#(.Net) では既定クラスの public で virtual でないプロパティやメソッドを、継承クラスで隠すことができません。ここでは隠すことが出来ないパターンと、いくつかの隠すための手段について解説します。ただし厳密な意味で隠すことはできません。

  • &ref(): File not found: "ConsoleApplication_HideBaseClassPublicProperty.zip" at page "Programming/.NetFramework/Tips/HideBaseClassPublicProperty";
    • VisualStudio 2012
    • .Netframework 4.5

サンプル

サンプルのため、public で virtual でないプロパティが定義される基底クラスを用意します。ここで隠したいプロパティは TestPropertyA とします。

        class BaseClass 
        {
            /// <summary>
            /// 隠したいプロパティです。
            /// </summary>
            public object TestPropertyA { get; set; }

            /// <summary>
            /// 比較のため隠さないプロパティです。
            /// </summary>
            public object TestPropertyB { get; set; }
        }

隠せないパターン

BaseClass を継承する SubClassA を用意しました。SubClassA では隠したいプロパティと同名のプロパティ TestPropertyA を定義して、そのアクセシビリティを private とします。基底クラスの同名のプロパティを意図的に隠すため new キーワードも加えます。これは VisualStudio による補助機能でも示される対応です。

        class SubClassA : BaseClass
        {
            /// <summary>
            /// "意図的に隠す場合は new キーワードを使用してください"となるが、
            /// 残念なことに、new キーワードを指定して居も基底クラスの
            /// public で virtual でないプロパティやメソッドを隠すことはできない。
            /// private で宣言していても public が優先される仕様となっている。
            /// </summary>
            private new object TestPropertyA { get; set; }
        }

一見するとこれで基底クラスのプロパティ TestPropertyA を隠すことに成功し、SubClassA のインスタンスから参照できないように見えますが、残念ながら隠すことはできていません。次のソースコードは動作します。これは、基底クラスの public で virtual でないプロパティないしメソッドを隠すことはできない、という言語の仕様によって発生する問題です。

            //SubClassA は TestPropertyA を隠せません。
            SubClassA testSubClassA = new SubClassA();
            testSubClassA.TestPropertyA = new object();

隠すパターン1

少々強引な方法によって完全に隠してしまう方法があります。それは継承の関係(is-a)を捨てて、包含の関係(has-a)にしてしまうことです。次の SubClassB は BaseClass を継承しませんが、包含します。

        class SubClassB
        {
            //継承関係ではないが、Has-a によって隠してしまう方法。
            //ただし、隠さないプロパティが多いほど実装する手間が増えて、
            //加えて変更に弱い実装になる。
            BaseClass testBaseClass = new BaseClass();

            public object TestPropertyB
            {
                get { return testBaseClass.TestPropertyB; }
                set { testBaseClass.TestPropertyB = value; }
            }
        }

この実装のパターンでは、BaseClass のプロパティ TestPropertyA を完全に隠すことが出来ます。

            SubClassB testSubClassB = new SubClassB();
            //エラーになります。
            //testSubClassB.TestPropertyA = new object();
            testSubClassB.TestPropertyB = new object();

ただし、この実装にはいくつかの問題があります。ここでは大きな問題の1つを解説します。包含するクラスでも公開したい基底クラスのプロパティやメソッドは、それらを全て包含するクラスにも実装する必要がある、という問題です。

例えば、BaseClass に 公開したい public なプロパティが 9 つあり、非公開にしたいプロパティが 1 つあるとします。このとき SubClassB では、9 つの public なプロパティを実装する必要があります。数が増えるほど実装にかかる負担が大きくなるということです。さらに BaseClass に定義されるプロパティ(メソッド)の数や、扱う型の種類が変更されるとき、SubClassB も同じように変更して実装しなおす必要があります。これも数が多いほど負担が大きくなります。

このような実装は例えばGUIコントロールを実装するときに利用されることがあります。汎用的なパネル(クラス)の中に、特殊なコントロール(クラス)を実装して、新しいコントロールとします。この新しいコントロールに必要な操作やプロパティだけをパネルクラスに実装します。

隠すパターン2

VisualStudio などのインテリセンスやプロパティウィンドウ、出力するリファレンスなどの、見かけ上から隠してしまう方法があります。さらにプログラマによって任意に参照されるときには例外を通知するなどして、実質的に対象のプロパティを無効化してしまいます。

SubClassC は BaseClass を継承します。SubClassC には、基底クラスの隠したいプロパティと同名の public なプロパティを定義します。ここで、対象のプロパティの記述に属性が設定されていることに注目してください。BrowsableAttribute 属性は、プロパティウィンドウに表示するかどうかを決定する属性です。EditorBrowsable 属性はエディタ(インテリセンス)に表示するかどうかを決定する属性です。ここでは隠したいのでそれぞれ非表示にする値を設定しています。

        class SubClassC : BaseClass
        {
            /// <summary>
            /// プロパティウィンドウやインテリセンスに表示しないように設定してしまう方法。
            /// 見かけ上から隠れる上に、もし参照しても何の処理も実行されないようにしておく。
            /// 加えて summary などを用いてユーザに警告するなどをしておく方が親切な設計。
            /// </summary>
            [BrowsableAttribute(false), EditorBrowsable(EditorBrowsableState.Never)]
            public new object TestPropertyA
            {
                set
                {
                    //何もしない。必要なら例外を発生させて、ユーザに明示する。
                    //ここでは NullCheck メソッドによって確かに基底クラスの
                    //プロパティが操作されないことを確認するため例外は発生させない。
                    //throw new Exception();
                }

                //getアクセサだけにして、null を返したり、
                //setと同様に例外を発生させるという手もある。
                //get
                //{
                //    return null;
                //    throw new Exception();
                //}
            }

            /// <summary>
            /// 基底クラスのプロパティが確かに操作されていないことを確認する。
            /// </summary>
            public void NullCheck()
            {
                if (base.TestPropertyA == null)
                    Console.WriteLine("TestPropertyA is null.");
            }
        }

BrowsableAttribute 属性などを設定しただけでは、VisualStudio やインテリセンスに表示できないだけで、プログラマが任意に参照するソースコードを書けば、参照して操作することが出来てしまいます。そこで、set ないし get プロパティの実装で、'何もさせない'ようにすることで、実質的に基底クラスのプロパティを参照できないようにします。実装の形式上、継承クラスからは参照させたくないわけですから、その旨をプログラマに通知するために例外を発生するのが最も良いでしょう。

ここでは基底クラスのプロパティが確かに参照されていないことを確認するために例外を発生させません。SubClassC を次のように実装して実行するとき、確かに基底クラスのプロパティが参照されて操作されていないことを確認することが出来ます。

            //記述はできるがインテリセンス(入力補助)や
            //プロパティウィンドウには表示されない。
            SubClassC subClassC = new SubClassC();
            subClassC.TestPropertyA = new object();
            subClassC.NullCheck();