给出以下类结构:
class Base {}
class DerivedA: Base {}
class DerivedB: Base {}
class Container
{
public List<Base> Items { get; }
}
随着我的应用程序开发更多功能,Derived对象列表随着时间的推移而增长。我正在尝试:
Container container = ReturnsContainer();
foreach (var item in container.Items)
{
doStuff(item);
}
其中doStuff(item)由派生类型重载。
void doStuff(DerivedA) {}
void doStuff(DerivedB) {}
这不起作用,因为编译器说“无法从'Base'转换为'DerivedA'”和“最好的重载方法匹配doStuff(DerivedA)有一些无效的参数”。我能想出的最佳方法是:
Container container = ReturnsContainer();
foreach (var item in container.Items)
{
if (item is DerivedA)
{
doStuff((DerivedA)item);
}
else if (item is DerivedB)
{
doStuff((DerivedB)item);
}
}
关于如何让这个更清洁的任何想法?由于我的派生类型只会随着时间的推移而增长,所以我必须回过头来添加这个长期的结构以继续使其发挥作用。
我认为container.Items.OfType&lt;&gt;随着派生类型数量的增加,解决方案将转化为不断增长的(并且不必要的)性能损失。
我必须为此使用通用代理吗?对于感觉的东西来说,似乎是一个过于复杂的解决方案,就像它应该是简单的多态性一样。
编辑:为了进一步说明,我们假设Base和Derived类型层次结构位于单独的API层上,并且不可修改(最终类)。解决方案必须使用现有的继承结构,并且不能扩展Base和Derived对象。虽然API层可以随着时间的推移而增长新的派生类。
答案 0 :(得分:3)
您必须使用运行时类型信息(例如,反映类型)来确定要调用的函数,或者您可以使用在基类上声明的抽象方法(doStuff
)并在派生类。在许多情况下,您希望避免使用反射的解决方案,但如果您无法修改所涉及的类,则没有其他选项,并且在其他答案中使用dynamic
是一种实现您想要的非常简单的方法
E.g。实现两个重载:
void doStuff(DerivedA a) {
...
}
void doStuff(DerivedB b) {
...
}
从循环中调用它们:
foreach (dynamic item in container.Items)
doStuff(item);
答案 1 :(得分:2)
这里有几个选项,但它真的归结为如果你将来要添加新的派生类型,doStuuf
如何知道该怎么做?
所以,一个选项是,如果Container
一次只包含一种类型,那么你可以使它成为通用的:
class Container<T> where T: Base
{
public List<T> Items { get; }
}
现在,当您对其进行迭代时,您知道Items
是DerivedA
或DerivedB
Container<DerivedA> container = ReturnsContainerOfDerivedA();
foreach (var item in container.Items)
{
doStuff(item); // item is definately DerivedA
}
这意味着您要么拥有多个doStuff
方法
public void doStuff(DerivedA item){...}
public void doStuff(DerivedB item){...}
或者如果更合适,你可以拥有1作为基础
public void doStuff(Base item){...}
那是简单多态的优势!
如果您使用的是C#3.5,则第二个选项是将doStuff
定义为dynamic
public void doSomething(dynamic item){..}
但我个人会避免这个选择!
答案 2 :(得分:1)
最简单的解决方案(不更改Base
/ DerivedA
等)是将您的对象转换为dynamic
:
foreach (dynamic item in container.Items)
{
doStuff(item);
}
所以调用的实际方法是在运行时确定的。
答案 3 :(得分:1)
Base
对象的用户应该知道它是什么类型的Base
对象。在这种情况下,很明显Base
应该是抽象的,它应该有一个抽象方法来定义doStuff
对象所需的行为Base
。 (如果使Base
抽象不是一个选项,你可以让它定义一个虚拟方法,没有要被覆盖的主体,或者你可以让所有派生对象实现一个接口。)
完成后,每个派生对象都可以覆盖定义的方法,以提供每种类型之间不同的行为。
完成后Container
不需要将对象转换/转换为Base
以外的任何内容,每个Base
已经拥有了您需要的所有信息一个doStuff
方法。