質問

Given the following class structure:

class Base {}
class DerivedA: Base {}
class DerivedB: Base {}
class Container
{
    public List<Base> Items { get; }
}

where the list of Derived objects grows over time as my application develops more functionality. I'm trying to do:

Container container = ReturnsContainer();
foreach (var item in container.Items)
{
    doStuff(item);
}

where doStuff(item) is overloaded by derived type.

void doStuff(DerivedA) {}
void doStuff(DerivedB) {}

That doesn't work as the compiler says "cannot convert from 'Base' to 'DerivedA'" and "The best overloaded method match doStuff(DerivedA) has some invalid arguments". The best approach I can come up with is:

Container container = ReturnsContainer();
foreach (var item in container.Items)
{
    if (item is DerivedA)
    {
        doStuff((DerivedA)item);
    }
    else if (item is DerivedB)
    {
        doStuff((DerivedB)item);
    }
}

Any thoughts on how I can make this cleaner? Since my Derived types will only grow over time, I would have to go back and add to this long if structure to continue to make this work.

I think the container.Items.OfType<> solution would translate to a growing (and unnecessary) performance hit as the number of Derived types increases.

Do I have to use generic delegates for this? Seems like an overly complex solution for something that feels like it should be simple polymorphism.

Edit: For further clarity, let's say the Base and Derived type hierarchy is on a separate API layer and is not modifiable (final classes). The solution has to work with the existing inheritance structure and cannot extend the Base and Derived objects. Though the API layer can grow new Derived classes as time goes on.

役に立ちましたか?

解決 2

There are a couple of options here, but it really boils down to how can doStuuf know what to do if you're going to add new derived types in the future?

So, one option is if Container really does only contain 1 type at a time then you could make it generic:

class Container<T> where T: Base
{
    public List<T> Items { get; }
}

Now when you iterate it, you know that Items is a List of DerivedA or DerivedB

Container<DerivedA> container = ReturnsContainerOfDerivedA();
foreach (var item in container.Items)
{
    doStuff(item); // item is definately DerivedA
}

This would mean you either have multiple doStuff methods

public void doStuff(DerivedA item){...}
public void doStuff(DerivedB item){...}

or if more appropriate you can have 1 which takes the base

public void doStuff(Base item){...}

That is the advantage of simple polymorhism!


The second option is to define doStuff as taking a dynamic if you're using C#3.5

public void doSomething(dynamic item){..}

But I would personally avoid this option!

他のヒント

Either you will have to use run-time type information (e.g. reflecting over the type) to determine which function to call or you can use an abstract method (doStuff) declared on the base class and implemented in the derived classes. In many cases you would like to avoid a solution using reflection but if you cannot modify the classes involved you have no other option, and using dynamic as has been proposed in other answers is a very simple way of achieving what you want.

E.g. implement the two overloads:

void doStuff(DerivedA a) {
  ...
}

void doStuff(DerivedB b) {
  ...
}

And call them from the loop:

foreach (dynamic item in container.Items)
  doStuff(item);

The user of the Base object should need to know what type of Base object it is. In a case such as this it is pretty clear that Base should probably be abstract, and it should have an abstract method defining whatever behavior doStuff needs from a Base object. (If making Base abstract isn't an option you could have it define a virtual method with no body that's intended to be overridden, or you could have all of the derived objects implement an interface.)

Once you've done that each derived object can override the defined method to provide whatever behavior differs between each type.

Once you've done that Container doesn't need to cast/convert the object to anything other than Base, each Base will already have all of the information you need for a single doStuff method.

The simplest solution (without changing Base/DerivedA etc.) is to cast your objects to dynamic:

foreach (dynamic item in container.Items)
{
    doStuff(item);
}

so the actual method to call is determined at runtime.

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top