使用重载函数迭代List <base />

时间:2012-11-28 15:12:17

标签: c# generics overloading variance

给出以下类结构:

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层可以随着时间的推移而增长新的派生类。

4 个答案:

答案 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; }
}

现在,当您对其进行迭代时,您知道ItemsDerivedADerivedB

的列表
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方法。