正在使用这个界面吗?

时间:2013-07-19 22:06:45

标签: c# asp.net oop reflection interface

我正在开发一个应用程序,在该应用程序中,将根据数据库中的数据在运行时创建表单。它目前使用reflection来创建控件并将其添加到表单中。

有了这个,我可以在运行时轻松动态创建表单,但接下来我遇到了如何访问当前所选的问题,例如:TextBox.TextDropDownList.SelectedValue。为了“修复”这个问题,我使用GetValue方法创建了一个界面。有了这个,我创建了一个新类,并从各自的控件继承并实现了接口。

现在,我可以轻松地遍历表单控件,看看它们是否实现了接口,然后获取控件的值。

所有这一切的问题是:这是实现这一目标的最佳方法吗?

要注意:我完全期望可用于构建这些表单的控件达到15 +。

班级示例:

public interface IFormField
{
    string GetId();
    object GetValue();
}

public TextBox : System.Web.UI.WebControls.TextBox, IFormField
{
    public string GetId()
    {
        return ID;
    }

    public object GetValue()
    {
        return Text;
    }
}

4 个答案:

答案 0 :(得分:1)

差不多。这是继承/多态有用的主要原因之一。它允许调用代码来处理一个通用集合,这个集合实际上可以有许多派生类型,并且就像处理它们一样处理它们。

如果您只有两种或三种类型,可能更容易跳过此类,但随着您可以操作的类型集的增长,这将成为最佳选择。另外,我想指出我不会非常处理WinForm类型,可能已经有一些支持这种类型的行为(你要重复),我不知道。

答案 1 :(得分:1)

虽然我喜欢接口,但正如“创建新类并从相应控件继承”所述。使用新接​​口需要修改基础类型。对于这种情况,这并不总是实用的。因此,虽然我不会声称这里的接口不合适,但我会提供其他想法。

第一种方法使用一个知道控件的伴随对象,以及如何从控件中获取值。这个类可以使用一个接口,但这里不需要它。它允许延迟提取器(以良好类型的方式),但也要求它是每个伴随实例的显式设置。

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但控件实例的独立),由地图/字典根据控件对象的实际类型进行查找。如果一个人甚至不想手动维护地图/字典,那么程序集反射可以自动加载这些每类型提取器 - 哦,可能性和可能的​​复杂性!

沿着相同的路线但比上述建议更通用的是使用Type Converters这是一个非常完整(如果不是很复杂的)设置来处理转换类型 - 即使这些类型无法修改或扩展。

有几种不同的可能性,虽然扩展控件和添加接口 通常可以工作(它需要控件可以注册为安全并由特定的精炼实现创建),但它仅限于案例其中所述类型可以适应这种变化。


1 好的,对于一般的“无开关”GetValue,这是一个粗略的想法。请注意,它将控件实例与“fetcher”分开。实际上,这样的反转甚至可以用来“获取同伴”以避免显式包装,如第一个例子所示。

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框架可能支持类似的解析功能,然后只需将此维护推送到配置中。再一次 - 许多方法,以及许多方法使它变得复杂。

答案 2 :(得分:1)

你可以这样做(只是陈述明显的...),但我个人不会。当你所做的只是添加一个方法作为接口实现的一部分时,扩展一些控件是一个相当漫长的方法来做到这一点。

我会使用一个辅助方法,它以Control作为输入,并检查控件的类型(通过转换代替更具语言特定的选项),然后将控件的值作为对象返回

接口的目的是建立一个合同而不管实际的实现,所以你没有错误地使用它,你刚刚做了比你真正需要的更多的工作。

答案 3 :(得分:1)

如果满足您的需求并简化生活,您的方法是正确和正确的。

我想展示一种控制值检索的另一种方式,即在ASP.NET Web Forms本身中如何实现它的方式。如果您不想使用继承,并且使用标准输入控件或所有控件都使用ValidationPropertyAttribute进行修饰(如果您希望在自定义中使用标准验证控件,则此方法非常有用)那些)。

要检索任何标准输入控件的值,我们需要使用BaseValidator.GetValidationProperty方法。此方法为验证属性返回PropertyDescriptor实例,该属性保存控件的值(ListItem除外,但这种情况在代码片段中有所介绍)。

因此,值检索的完整代码将是:

public static string GetControlValue(Control c) 
{
    // This code is copied as-is from BaseValidator.GetControlValidationValue method
    PropertyDescriptor prop = BaseValidator.GetValidationProperty(c);
    if (prop == null) { 
        return null;
    } 

    object value = prop.GetValue(c); 
    if (value is ListItem) {
        return((ListItem) value).Value;
    }
    else if (value != null) { 
        return value.ToString();
    } 
    else { 
        return string.Empty;
    } 
}