虽然我喜欢接口,但正如“创建一个新类并从相应的控件继承”所指出的那样……使用新接口需要修改基础类型。对于这种情况,这并不总是可行的。所以,虽然我不会声称接口在这里不合适,但我会提供替代的想法。
第一种方法使用一个伴随对象,它知道控件以及如何从控件中获取值。此类可以使用接口,但此处不需要。它允许延迟 fetcher(以良好类型的方式),但也要求它为每个伴随实例显式设置。
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);
另一种选择是创建一个知道如何提取值的函数。因为这些控件是“动态创建的”(例如 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;
}
当然,上述两种方法可以组合1 - 例如,GetValue 函数使用每个类型的提取器(类似于 ControlCompanion,但独立于控件实例),由基于控件对象的实际类型的映射/字典查找. 如果一个人甚至不想手动维护地图/字典,程序集反射可以自动加载这些每种类型的提取器 - 哦,可能性和可能的复杂性!
与上述建议相同但比上述建议更通用的是使用类型转换器,这是一个非常完整(如果不复杂)的设置来处理转换类型 - 即使这些类型无法修改或扩展。
有几种不同的可能性,虽然扩展控件和添加接口通常确实有效(它要求控件可以注册为安全并由特定的改进实现创建),但仅限于所述类型可以适应此类更改的情况。
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 框架可能支持类似的解析功能,然后只需将此维护推送到配置中。再一次 - 很多方法,很多方法使它变得复杂。