「新しいクラスを作成し、それぞれのコントロールから継承する」で述べたように、私はインターフェイスが好きですが、新しいインターフェイスを使用するには、基になる型を変更する必要があります。これは、このケースでは必ずしも実用的ではありません。したがって、ここでインターフェイスが適切でないと主張するつもりはありませんが、別のアイデアを提供します。
この最初のアプローチでは、コントロールについて認識しているコンパニオン オブジェクトと、コントロールから値を取得する方法を使用します。このクラスはインターフェイスを使用できますが、ここでは必須ではありません。フェッチャーの遅延を (適切に型付けされた方法で) 許可しますが、コンパニオン インスタンスごとに明示的に設定する必要もあります。
interface IWithValue {
string Value { get; }
}
class ControlCompanion<T>: IWithValue where T: Control {
IFunc<Control, string> readValue;
public T Control { get; private set; }
public string Value { get { return readValue(Control); } }
public ControlCompanion (T control, IFunc<T, string> readValue) {
Control = control;
this.readValue = readValue;
}
}
// this is typed narrowly here, but it could be typed wider to
// the actual ControlCompanion if needing additional information
// or actions wrt. the particular control
var valueAccessors = new List<IWithValue>();
var textBox = new TextBox();
valueAccessors.Add(new ControlCompanion(textBox, (c) => c.Text));
var comboBox = new ComboBox();
valueAccessors.Add(new ControlCompanion(comboBox, (c) => c.SelectedValue));
var allValues = valueAccessors.Select(v => v.Value);
もう 1 つの方法は、値を抽出する方法を知っている関数を作成することです。これらのコントロールは「動的に作成される」ため (例: Control 型)、メソッドのオーバーロードを直接使用することはできません。したがって、より一般的な型を受け入れ、なんらかの形式のリフレクションまたは型の改良を使用する必要があります。
string GetValue(Control c) {
// using this form will allow invalid path detection
TextBox tb;
ComboBox cb;
if ((tb = c as TextBox) != null) {
return tb.Text;
} else if ((cb = c as ComboBox) != null) {
return GetValue(cb);
} else {
throw new Exception("Unsupported control");
}
}
// but we could use overloading once refined ..
string GetValue(ComboBox cb) {
return cb.SelectedValue;
}
もちろん、上記の 2 つのアプローチは組み合わせることができます1 - たとえば、コントロール オブジェクトの実際のタイプに基づいてマップ/ディクショナリによってルックアップされるタイプごとのエクストラクタ (ControlCompanion に似ていますが、コントロール インスタンスとは無関係) を使用する GetValue 関数. マップ/ディクショナリを手動で維持したくない場合でも、アセンブリ リフレクションはこれらの型ごとのエクストラクタを自動的にロードできます。
同じ方向ですが、上記の提案よりも一般的なのは、タイプの変換を処理するための完全な (複雑ではないにしても) セットアップであるタイプ コンバーターを使用することです。これらのタイプを変更または拡張できない場合でも同様です。
いくつかの異なる可能性があり、コントロールの拡張とインターフェイスの追加は通常は機能しますが (コントロールを安全なものとして登録し、特定の洗練された実装によって作成できる必要があります)、上記の型がそのような変更に対応できる場合に限られます。
1さて、これは一般的な「スイッチレス」GetValue の大まかなアイデアです。コントロールインスタンスを「フェッチャー」から分離することに注意してください。実際、このような反転は、最初の例のように明示的なラッピングを回避するために「コンパニオンを取得」するために使用することもできます。
interface IFetchValue {
string FetchValue(Control c);
}
abstract class Fetcher<T>: IFetchValue where T : Control {
abstract protected FetchControlValue(T c);
public string FetchValue (Control c) {
return FetchControlValue((T)c);
}
}
class TextBoxFetcher: Fetcher<TextBox> {
protected string FetchControlValue (TextBox tb) {
return tb.Value;
}
}
class ComboBoxFetcher: Fetcher<ComboBox> {
protected string FetchControlValue (ComboBox cb) {
return cb.SelectedValue;
}
}
// This could be initialized via reflection of all
// Fetcher<T>/IFetchValue types with a bit more work.
IDictionary<Type, IFetchValue> map = new Dictionary<Type, IFetchValue> {
{ typeof(TextBox), new TextBoxFetcher() },
{ typeof(ComboBox), new ComboBoxFetcher() },
};
string GetValue(Control c) {
IFetchValue fetcher;
// This should be smarter to also try parent types or
// check general assignability.
if (c != null && map.TryGetValue(c.GetType(), out fetcher)) {
return fetcher(c);
} else {
throw new Exception("Whoops!");
}
}
さらに、お気に入りの DI/IoC フレームワークが同様の解決機能をサポートしている可能性があり、このメンテナンスを構成にプッシュするだけです。繰り返しますが、多くの方法があり、それを複雑にする多くの方法があります。