方法重载和多态性
我正在编写一个 .NET Web 应用程序,管理员可以在其中自定义呈现给用户的各种数据输入表单。 管理员可以创建和自定义大约六种不同的字段类型(即文本、数字、下拉列表、文件上传)。 所有字段共享一组基本属性/行为(该字段是必需的吗?它会有默认字段值吗?)。 还有一系列特定于字段的属性/行为(即下拉列表具有数据源属性,但文本字段没有)。 为了简单起见,我省略了问题域的许多其他特征。
类层次结构很简单:一个封装常见行为/属性的抽象超类和大约六个处理特定领域内容的具体子类。
每个字段类型都呈现(即映射到)为特定类型的 .NET 服务器控件,所有这些控件均派生自 System.Web.UI.Control。
我创建了以下代码来映射字段域对象与其相应的 UI 控件之间的值:
public static void Bind(Control control, IList<DocumentFieldBase> fieldBaseList)
foreach (DocumentFieldBase fieldBase in fields){
if (typeof (DocumentFieldText).IsInstanceOfType(fieldBase)){
TextBox textbox = (TextBox) control;
textbox.Text = (fieldBase as DocumentFieldText).GetValue();
}
if (typeof (DocumentFieldDropDown).IsInstanceOfType(fieldBase)){
DropDown dropDown= (DropDown) control;
dropDown.Text = (fieldBase as DocumentFieldSelectOne).GetValue().Text;
dropDown.DataSource= (fieldBase as DocumentFieldSelectOne).DataSource;
dropDown.Id= (fieldBase as DocumentFieldSelectOne).GetValue().Id;
}
//more if statements left out for brevity
}
}
我想放弃那些执行类型检查的不合时宜的 if 语句。 我所采用的方法是使用子类类型为字段/控件的每个组合创建方法重载。 例如:
public static void Bind(TextBox control, DocumentFieldText fieldText){
//some implementation code
}
public static void Bind(DropDown control, DocumentFieldDropDown fieldDropDown){
//some implementation code
}
我希望我可以依靠 .NET 使用正在使用的特定子类在运行时调用适当的重载:例如:
foreach (DocumentFieldBase field in fields){
Control control = FindControl(field.Identifier);
Bind(control, field)
}
不幸的是,当我尝试这样做时,编译器会卡住: 参数“1”:无法从“System.Web.UI.Control”转换为“TextBox”。
如果我必须将第一个参数强制转换为 TextBox,那么我将返回自己执行类型检查,从而违背了本练习的全部目的。
我想要实现的a)可能和b)是个好主意吗?
I'm writing a .NET web application in which administrators can customize the various data entry forms presented to their users. There are about half a dozen different field types that admins can create and customize (i.e. text, numeric, dropdown, file upload). All fields share a set of base attributes/behaviors (is the field required? Will it have a default field value?). There are also a series of field specific attributes/behaviors (i.e dropdown has a data source attribute, but text field does not). I'm leaving out many other characteristics of the problem domain for simplicity's sake.
The class hierarchy is straightforward: An abstract superclass that encapsulates common behaviors/attributes and about half a dozen concrete subclasses that deal with field specific stuff.
Each field type is rendered (i.e. mapped to) as a specific type of .NET server control, all of which derive from System.Web.UI.Control.
I created the following code to map values between the field domain objects and their corresponding UI control:
public static void Bind(Control control, IList<DocumentFieldBase> fieldBaseList)
foreach (DocumentFieldBase fieldBase in fields){
if (typeof (DocumentFieldText).IsInstanceOfType(fieldBase)){
TextBox textbox = (TextBox) control;
textbox.Text = (fieldBase as DocumentFieldText).GetValue();
}
if (typeof (DocumentFieldDropDown).IsInstanceOfType(fieldBase)){
DropDown dropDown= (DropDown) control;
dropDown.Text = (fieldBase as DocumentFieldSelectOne).GetValue().Text;
dropDown.DataSource= (fieldBase as DocumentFieldSelectOne).DataSource;
dropDown.Id= (fieldBase as DocumentFieldSelectOne).GetValue().Id;
}
//more if statements left out for brevity
}
}
I want to ditch those ungodly if statements that perform type checking. The approach I was shooting for was to create a method overload for each combination of field/control using subclass typing. For example:
public static void Bind(TextBox control, DocumentFieldText fieldText){
//some implementation code
}
public static void Bind(DropDown control, DocumentFieldDropDown fieldDropDown){
//some implementation code
}
I was hoping that I could then rely on .NET to call the appropriate overload at runtime using the specific subclass being used: For example:
foreach (DocumentFieldBase field in fields){
Control control = FindControl(field.Identifier);
Bind(control, field)
}
Unfortunately, the compiler chokes when I try this:
Argument '1': cannot convert from 'System.Web.UI.Control' to 'TextBox'.
If I have to cast the first argument to TextBox, I'm back to performing type checking myself and defeats the whole purpose of this exercise.
Is what I'm trying to achieve a) possible and b) a good idea?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
难道你不能在 DocumentFieldBase 中有一个抽象的、非静态的
Bind()
方法,然后在每个具体类的实现中进行向下转换吗? 每个DocumentFieldBase
类都知道它所获取的Control
类型,不是吗?Couldn't you have an abstract, non-static
Bind()
method in DocumentFieldBase, then do the downcasting inside each concrete class's implementation of it? EachDocumentFieldBase
class knows what type ofControl
it's getting, doesn't it?这个问题上的“调度”标签非常合适:您想要的称为“多重调度”。 C#(像大多数主流语言一样)仅支持“单分派”,其中要执行的方法仅根据调用该方法的对象的(运行时)类型来选择,而不是根据其参数的(运行时)类型来选择。
访问者模式通常可以用来解决这个问题。 这个想法是,您为
DocumentFieldBase
提供一个方法(您在具体子类中重写),该方法调用Control
上的一个方法(也在具体子类中重写)来执行实际工作。不幸的是,
Control
类的源代码可能不在您的控制之下*...因此您将不得不诉诸更黑客的解决方案。 这个问题的公认答案提供了使用反射的答案。*扩展方法只是静态方法的语法糖,因此在编译时解析,在这种情况下没有用处。
The "dispatch" tag on this question is quite appropriate: what you want is called "multiple dispatch". C# (like most mainstream languages) only supports "single dispatch", where the method to be executed is selected solely on the (runtime) type of the object you call the method on, not on the (runtime) type of its arguments.
The visitor pattern can often be used to work around this. The idea is that you give
DocumentFieldBase
a method (that you override in concrete subclasses) which calls a method onControl
(also overridden in concrete subclasses) that does the actual work.Unfortunately, the source code of the
Control
class is probably not under your control*... so you'll have to resort to an even more hackish solution. The accepted answer to this question provides one that uses reflection.*Extension methods are just syntactic sugar for static methods, and are thus resolved at compile time and of no use in this scenario.
在 C# 4 之前,所有重载都是在编译时完成的。 您必须使用双重分派或访问者模式才能在执行时有效地重载,这很快就会变得混乱。
在 C# 4 中,您可以将变量声明为动态变量,并在执行时将其全部整理出来:
显然,目前这没有多大帮助(除非您使用的是 VS2010b1)。
一种选择是使用从
Type
到Action
Prior to C# 4, all overloading is done at compile time. You have to use double dispatch or the visitor pattern to effectively overload at execution time, and that gets messy quickly.
In C# 4, you could declare a variable as dynamic and let it all get sorted out at execution time:
Obviously that's not much help at the moment though (unless you're using VS2010b1).
One option is to use a map from
Type
toAction<object>
but then you get inheritance issues... (you'd potentially have to keep working up the type hierarchy from the concrete type up to object until you found an entry in the map). You'd also still need to cast to the right type within the action :(