- PR -

C# 特定のプロパティへの外部からのアクセスを隠蔽したい

投稿者投稿内容
なちゃ
ぬし
会議室デビュー日: 2003/06/11
投稿数: 872
投稿日時: 2004-12-06 15:01
引用:

ひろしさんの書き込み (2004-12-05 23:47) より:

実はわたしも試みたのですが、単に"private new"を定義しても、
うまく隠蔽できないような気がします。皆さんはどうでしょうか。


これはどういう意味合いでうまく隠蔽できないと仰ってますか?
クラスを継承する(という条件を崩さない)以上、(C++とかではちょっぴり違うこともありますが)基底のクラスで定義された公開機能を削除することは不可能です。
ですから、例えば private new なメソッドを定義してインターフェイス上から消す、などの方法しかありません。

現実には基底の型からはそのまま呼び出せますから、そういう意味合いで隠蔽できないという意味であれば、まあ仕方がないところですね。

例外を投げるというやり方なら(つまり実行時に確実に防ぎたいなら)、基底の方でメソッドをオーバーライドし、実体が派生クラスだったら例外を投げるという方法もあるでしょう。
この方法では確実に防げますが、インターフェイスからは消えません。private new なメソッドで隠蔽すれば直接のインターフェイスからも消すことはできますが、さらに基底の型から呼び出すことができるのでちょっと微妙ですね(やらないよりはやった方が良いかもしれませんが)。

--追記--
って書きましたが、単純に実体が派生クラスという判断をすると、基底クラスのコードから呼んでも駄目になりますね…やっぱりこの系統だと強引な方法になりそうな気がしてきました。

[ メッセージ編集済み 編集者: なちゃ 編集日時 2004-12-06 15:36 ]
iStation
大ベテラン
会議室デビュー日: 2003/12/08
投稿数: 158
投稿日時: 2004-12-06 15:53
引用:

ひろしさんの書き込み (2004-12-06 14:21) より:
...
temp = new TextBox();
...
private TextBox temp;
...


tempはTextBoxが持つ特徴を引き継いでいるように思うのですが...
todo
ぬし
会議室デビュー日: 2003/07/23
投稿数: 682
投稿日時: 2004-12-06 17:36
[Obsolete("使用禁止", true)]
public new ..
でコンパイルエラーにするのはどうでしょう。
ひろし
ぬし
会議室デビュー日: 2002/09/16
投稿数: 390
お住まい・勤務地: 兵庫県
投稿日時: 2004-12-06 19:37
private tempを作成するところまではわかりますが、tempが持つ特徴から必要な
ものだけをPinkTextBoxに引き継がせるエレガントな方法が分かりません。
例えば下記のようにしてひたすら全てのメンバー(BackColor以外)をひもづけるしか
無いという理解で正しいしょうか?


protected string Text
{
get
{
return temp.Text;
}
set
{
temp.Text = value;
}
}

[quote]
iStationさんの書き込み (2004-12-06 15:53) より:
[quote]
ひろしさんの書き込み (2004-12-06 14:21) より:
...
temp = new TextBox();
...
private TextBox temp;
...
[/quote]
tempはTextBoxが持つ特徴を引き継いでいるように思うのですが...
[/quote]
Jitta
ぬし
会議室デビュー日: 2002/07/05
投稿数: 6267
お住まい・勤務地: 兵庫県・海手
投稿日時: 2004-12-06 19:43
> 実はわたしも試みたのですが、単に"private new"を定義しても、
> うまく隠蔽できないような気がします。皆さんはどうでしょうか。

 どういうことを試して、どういう結果が得られたので、『うまく隠蔽できない』とお考えですか?

 私が試した結果では、「インテリセンスがうまく隠蔽していない」ように思います。または、「インテリセンスおよびコンパイラが拡大解釈して候補を一覧している」か。

以下のコードで試してみました。
コード:
public class MyTextBox : System.Windows.Forms.TextBox
{
	public MyTextBox()
	{
	}

	protected new string Text {
		get {
			return "+" + base.Text;
		}
		set {
			base.Text = value;
		}
	}

	public string GetText() {
		return this.Text;
	}
}

〜〜〜〜〜

private void button1_Click(object sender, System.EventArgs e) {
	foreach (System.Windows.Forms.Control cntrl in this.Controls) {
		if (cntrl.Name.CompareTo("myTextBox1") == 0) {
			MyTextBox my = (MyTextBox) cntrl;
			this.label1.Text = my.GetText();
			this.Text = my.Text;
			break;
		}
	}
}


このとき、button1_Clickメソッドの逆アセンブルの一部が、次のようになります。
コード:
.try
{
  IL_000c:  br.s       IL_0052
  IL_000e:  ldloc.2
  IL_000f:  callvirt   instance object [mscorlib]System.Collections.IEnumerator::get_Current()
  IL_0014:  castclass  [System.Windows.Forms]System.Windows.Forms.Control
  IL_0019:  stloc.0
  IL_001a:  ldloc.0
  IL_001b:  callvirt   instance string [System.Windows.Forms]System.Windows.Forms.Control::get_Name()
  IL_0020:  ldstr      "myTextBox1"
  IL_0025:  callvirt   instance int32 [mscorlib]System.String::CompareTo(string)
  IL_002a:  brtrue.s   IL_0052
  IL_002c:  ldloc.0
  IL_002d:  castclass  WindowsApplication1.MyTextBox
  IL_0032:  stloc.1
  IL_0033:  ldarg.0
  IL_0034:  ldfld      class [System.Windows.Forms]System.Windows.Forms.Label WindowsApplication1.Form1::label1
  IL_0039:  ldloc.1
  IL_003a:  callvirt   instance string WindowsApplication1.MyTextBox::GetText()
  IL_003f:  callvirt   instance void [System.Windows.Forms]System.Windows.Forms.Control::set_Text(string)
  IL_0044:  ldarg.0
  IL_0045:  ldloc.1
  IL_0046:  callvirt   instance string [System.Windows.Forms]System.Windows.Forms.Control::get_Text()
  IL_004b:  callvirt   instance void [System.Windows.Forms]System.Windows.Forms.Control::set_Text(string)
  IL_0050:  br.s       IL_005a
  IL_0052:  ldloc.2
  IL_0053:  callvirt   instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
  IL_0058:  brtrue.s   IL_000e
  IL_005a:  leave.s    IL_006d
}  // end .try


注目は、
・protectedアクセスに変更したTextプロパティに、Formクラスからアクセスできていること
です。ところが、実行するとわかりますが、実際にはMyTextBox.Textプロパティ(のGetアクセサ)にはアクセスできていません。IL_0046を見ると、System.Windows.Forms.Control::get_Text()メソッドにアクセスしています。これが「拡大解釈して」の部分です。つまり、「this.Text = my.Text;」は、「this.Text = ((System.Windows.Forms.Control) my).Text;」に置き換えられているようです。
#ただ、デバッガで止めて「My.Text」をポイントすると、
#「MyTextBox.Textプロパティ」のアクセス結果が表示されます。
# これは、どうなんだろう?

おそくてすまん。。。
_________________
mei
大ベテラン
会議室デビュー日: 2003/04/08
投稿数: 114
投稿日時: 2004-12-06 21:50
こんばんは、meiです。

引用:

ひろしさんの書き込み (2004-12-06 19:37) より:
private tempを作成するところまではわかりますが、tempが持つ特徴から必要な
ものだけをPinkTextBoxに引き継がせるエレガントな方法が分かりません。
例えば下記のようにしてひたすら全てのメンバー(BackColor以外)をひもづけるしか
無いという理解で正しいしょうか?



ええ、tempへの委譲処理を書くことになります。
TextBoxのプロパティが多いですが、作っているアプリケーションですべてのプロパティを必要とする訳ではないので、実際はそれほど多くないと思います。

あと、質問なのですが、ここで言っている隠蔽とは、
コード:

MyTextBox tb = new MyTextBox();
tb.Text = "ABC";


このようなコードをコンパイルエラーにすることでしょうか?
もし、これが狙いでしたら継承は使えません。

そうではなく、単に親クラス(TextBox)のプロパティを書き換えられたくないのでしたら、overrideして、set {...}の処理をつぶして、クラス内部からはbase.Textのように親クラスのプロパティを呼べは良いです。そのほかだと、xxxChangedイベントで強引に値を戻してやることも出来ます。(お勧めしませんけど・・・)

どうしてもコンパイル時に隠蔽したいのなら、ラッパークラスを作ってひたすら委譲するしかありません。力技が好ましくない場合は、リフレクションを使って雛形を自動生成させたり、RealProxy/ContextBoundを使ってメソッド呼び出しに割り込むといった手も取れますが、実現したい内容に比べて処理が難しくになるのでお勧めしません。

http://www.ne.jp/asahi/nami/mei/cstips/adapter.html
↑昔遊びで作ったRealProxyのサンプルです。参考までにどうぞ。


[ メッセージ編集済み 編集者: mei 編集日時 2004-12-06 21:54 ]

[ メッセージ編集済み 編集者: mei 編集日時 2004-12-06 22:01 ]
ひろし
ぬし
会議室デビュー日: 2002/09/16
投稿数: 390
お住まい・勤務地: 兵庫県
投稿日時: 2004-12-06 21:56
Jittaさん ご回答ありがとうございます。

> どういうことを試して、どういう結果が得られたので、『うまく隠蔽できない』とお考えですか?

わたしが試みた内容です。
なぜYellowTextBoxの背景がピンク色に書き換えることができるか分かりません。
わたしはどこをどう勘違いしているのでしょうか?

//
// ***** Formの記述 *****
// (WindowsFormにtextBoxでテンプレートを作成し、YellowTextBoxに置換)
//
YellowTextBox textBox1;

this.textBox1 = new YellowTextBox();

//
// textBox1
//
this.textBox1.Location = new System.Drawing.Point(56, 224);
this.textBox1.Multiline = true;
this.textBox1.Name = "textBox1";
this.textBox1.Size = new System.Drawing.Size(496, 256);
this.textBox1.TabIndex = 3;
this.textBox1.Text = "textBox1";
//
// 隠蔽したはずのBackColorにすんなり代入できてしまう!
// (実際、画面にピンク色の背景が表示されてしまう)
// ↓
this.textBox1.BackColor = System.Drawing.Color.Pink;



//
// ***** YellowTextBoxの記述 *****
//
public class YellowTextBox : System.Windows.Forms.TextBox
{
public YellowTextBox()
{
base.BackColor = yellow;
}
//
// BackColorをprivate宣言して隠蔽を試みる
//
private new System.Drawing.Color BackColor
{
get
{
return base.BackColor;
}
}
private readonly System.Drawing.Color yellow = System.Drawing.Color.Yellow;
}





[quote]
Jittaさんの書き込み (2004-12-06 19:43) より:
> 実はわたしも試みたのですが、単に"private new"を定義しても、
> うまく隠蔽できないような気がします。皆さんはどうでしょうか。

 どういうことを試して、どういう結果が得られたので、『うまく隠蔽できない』とお考えですか?

 私が試した結果では、「インテリセンスがうまく隠蔽していない」ように思います。または、「インテリセンスおよびコンパイラが拡大解釈して候補を一覧している」か。

以下のコードで試してみました。
[code]
public class MyTextBox : System.Windows.Forms.TextBox
{
public MyTextBox()
{
}

protected new string Text {
get {
return "+" + base.Text;
}
set {
base.Text = value;
}
}

public string GetText() {
return this.Text;
}
}

〜〜〜〜〜

private void button1_Click(object sender, System.EventArgs e) {
foreach (System.Windows.Forms.Control cntrl in this.Controls) {
if (cntrl.Name.CompareTo("myTextBox1") == 0) {
MyTextBox my = (MyTextBox) cntrl;
this.label1.Text = my.GetText();
this.Text = my.Text;
break;
}
}
}
[/code]
このとき、button1_Clickメソッドの逆アセンブルの一部が、次のようになります。
[code]
.try
{
IL_000c: br.s IL_0052
IL_000e: ldloc.2
IL_000f: callvirt instance object [mscorlib]System.Collections.IEnumerator::get_Current()
IL_0014: castclass [System.Windows.Forms]System.Windows.Forms.Control
IL_0019: stloc.0
IL_001a: ldloc.0
IL_001b: callvirt instance string [System.Windows.Forms]System.Windows.Forms.Control::get_Name()
IL_0020: ldstr "myTextBox1"
IL_0025: callvirt instance int32 [mscorlib]System.String::CompareTo(string)
IL_002a: brtrue.s IL_0052
IL_002c: ldloc.0
IL_002d: castclass WindowsApplication1.MyTextBox
IL_0032: stloc.1
IL_0033: ldarg.0
IL_0034: ldfld class [System.Windows.Forms]System.Windows.Forms.Label WindowsApplication1.Form1::label1
IL_0039: ldloc.1
IL_003a: callvirt instance string WindowsApplication1.MyTextBox::GetText()
IL_003f: callvirt instance void [System.Windows.Forms]System.Windows.Forms.Control::set_Text(string)
IL_0044: ldarg.0
IL_0045: ldloc.1
IL_0046: callvirt instance string [System.Windows.Forms]System.Windows.Forms.Control::get_Text()
IL_004b: callvirt instance void [System.Windows.Forms]System.Windows.Forms.Control::set_Text(string)
IL_0050: br.s IL_005a
IL_0052: ldloc.2
IL_0053: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
IL_0058: brtrue.s IL_000e
IL_005a: leave.s IL_006d
} // end .try
[/code]
注目は、
・protectedアクセスに変更したTextプロパティに、Formクラスからアクセスできていること
です。ところが、実行するとわかりますが、実際にはMyTextBox.Textプロパティ(のGetアクセサ)にはアクセスできていません。IL_0046を見ると、System.Windows.Forms.Control::get_Text()メソッドにアクセスしています。これが「拡大解釈して」の部分です。つまり、「this.Text = my.Text;」は、「this.Text = ((System.Windows.Forms.Control) my).Text;」に置き換えられているようです。
#ただ、デバッガで止めて「My.Text」をポイントすると、
#「MyTextBox.Textプロパティ」のアクセス結果が表示されます。
# これは、どうなんだろう?

おそくてすまん。。。

[/quote]
Jitta
ぬし
会議室デビュー日: 2002/07/05
投稿数: 6267
お住まい・勤務地: 兵庫県・海手
投稿日時: 2004-12-06 22:29
引用:

ひろしさんの書き込み (2004-12-06 21:56) より:

なぜYellowTextBoxの背景がピンク色に書き換えることができるか分かりません。
わたしはどこをどう勘違いしているのでしょうか?


 つまり、私がやったことと一緒ですね。なちゃさんやmeiさんが書かれているとおりで、継承によってpublicで公開されたメソッドを隠蔽、見えなくすることはできません。ん〜っと、publicで宣言されたメソッドを、private、publicで宣言し直すことはできません。暗黙的に、継承元のクラスにキャストされます。私は元々C++なので、C++的には「おかしい」のですが、C#やVB.NETでは仕様のようです。

 したがって、
this.textBox1.BackColor = System.Drawing.Color.Pink;
このコードは、
((System.Windows.Forms.TextBox)this.textBox1).BackColor = Color.Pink;
として解釈され、またILにもそう訳されます。第一、このコードがコンパイルエラーにならないのがおかしいと思いませんか?

 ユーザ入力を受け付けなくするには、public overrideで継承して、setアクセスメソッド内で握りつぶすしかないようです。
コード:
class YellowTextBox : TextBox {
    public override BackColor {
        get {
            return base.BackColor;
        }
        set { // 黄色にしかならない
            base.BackColor = Color.Yellow;
        }
    }
}


コード:
class YellowTextBox : TextBox {
    public new BackColor {
        get {
            return base.BackColor;
        }
        set { // 上位クラスにキャストすれば、黄色以外も可能
            base.BackColor = Color.Yellow;
        }
    }
}


_________________

スキルアップ/キャリアアップ(JOB@IT)