ゴミ箱.net

汚物は消毒

.NETのインターフェース継承の闇は深い

.NET FrameworkのComboBoxには、プロパティDisplayNameを設定することで、プロパティDataSourceに設定したリストの要素の指定したプロパティを勝手に表示してくれるという便利機能がある。リストの要素を表示文字列に読み替えるためのコードをわざわざ書かなくてすむので重宝する。(ちなみに、複雑な読み替えの場合に自前でコードを書くのであれば、イベントFormatのイベントハンドラを定義すればよい。)

ところがこの便利機能にはちょっとした罠がある。DataSourceに設定したリストがSystem.Collections.Generic.List<~>型で、かつ要素の型としてインターフェースが指定されている場合に、プロパティを表示してくれないことがあるのだ。


以下に例を示す。
Visual Studioを起動してC#のWindowsフォームアプリケーションのプロジェクトを作成する。
自分は貧民なのでVisual C# 2010 Expressで試した。また.NET Frameworkのバージョンは4とした。

以下のようなインターフェースおよびクラスを定義する。


public interface IFoo
{
string Hoge { get; }
}

public interface IBar : IFoo
{
string Piyo { get; }
}

public class Baz { }

public class Qux : Baz, IBar
{
public string Piyo
{
get { return "ぴよ"; }
}

public string Hoge
{
get { return "ほげ"; }
}
}

public class Quux : Qux { }


フォームを作成してComboBoxを6個配置する。
名称はcomboBox1~comboBox6。プロパティDropDownStyleはDropDownList、DisplayMemberは"Hoge"に設定する。フォームデザイナで設定してもよいが、コードで設定するなら以下のとおり。コードを書く場所は自動生成されたコンストラクタ中のInitializeComponent()の後ろでいいだろう。

foreach (var comboBox in new ComboBox[] {
comboBox1, comboBox2, comboBox3, comboBox4, comboBox5, comboBox6
})
{
comboBox.DisplayMember = "Piyo";
comboBox.DropDownStyle = ComboBoxStyle.DropDownList;
}

その状態でcomboBox1~comboBox6のDataSourceを以下のとおり設定する。コードを書く場所は同じく自動生成されたコンストラクタ中のInitializeComponent()の後ろでいいだろう。

comboBox1.DataSource = new List<object>() { new Quux() };
comboBox2.DataSource = new List<IFoo>() { new Quux() };
comboBox3.DataSource = new List<IBar>() { new Quux() };
comboBox4.DataSource = new List<Baz>() { new Quux() };
comboBox5.DataSource = new List<Qux>() { new Quux() };
comboBox6.DataSource = new List<Quux>() { new Quux() };

どのComboBoxも、リストの要素はクラスQuuxのインスタンスなので、プロパティHogeを持つ。またプロパティDisplayMemberが"Hoge"に設定されている。なので、プロパティHogeの値である「ほげ」がすべてのコンボボックスに表示されるはずだ。

一体いつから――DisplayMemberで指定したプロパティが表示されると錯覚していた?


実際には以下のとおり、comboBox3と4が意図したとおりに動作しない。
.NETの闇は深い
意外だが、DataSourceに指定したList<T>の要素の型Tに何を指定したかによって挙動が変わるのだ。
インターフェースIFooやクラスQuxは普通にプロパティHogeを定義してあるので、プロパティHogeを取得できて当然だ。
クラスobjectの場合は、当然プロパティHogeの定義などないわけだが、普通にプロパティを取得してくれる。リフレクションを使ってるのだろう。
ところがインターフェースIBarやクラスBazを指定したとたんにプロパティが取得できなくなる。百歩譲って、クラスBazにはプロパティを定義していないのでプロパティを取得できなくてもまあ許そう。だがIBarはIFooを継承しているのでプロパティHogeも取得できるるはずだろ常識的に考えて(実際、クラスQuuxの場合は基底クラスQuuで定義されたプロパティをきちんと取得できている)。しかしM$にその常識は通用しない。どうやらインターフェースを指定したときに限り、そのインターフェースで直接定義されているプロパティしか認識してくれないようだ。

ちなみにComboBoxのプロパティDisplayMemberに"Piyo"を指定すると以下のようになる。
.NETの闇は深い
今度は別のComboBoxがプロパティを表示してくれない。
やはり、インターフェースIFooを指定するとそこに定義のないプロパティPiyoが認識されなくなるようだ。

まとめると、
* List<T>の要素の型に基底クラスを指定すると、基底クラスにないプロパティは無視される
* List<T>の要素の型に派生クラスを指定すると、派生クラスで直接定義されていないプロパティは認識される
* List<T>の要素の型にインターフェースを指定すると、インターフェースで直接定義されていないプロパティは無視される
* List<T>の要素の型にobjectを指定すると、例外的にプロパティは認識される
ということらしい?正直闇が深すぎて何がなんだか。

もし、ComboBoxのDisplayMemberのようにプロパティ名を指定するような仕組みがあってうまく動かなかったら、ジェネリッククラスを使っていないか確認し、使っていたら型パラメータをobjectにしてみよう。そうすれば希望が見えてくるかもしれない。
スポンサーサイト

PageTop

コメント


管理者にだけ表示を許可する