views:

82

answers:

7

Ok, I have an some different objects that are derived from a base class and I've put a bunch of them in a list. I want to loop through the list and push each to a method. I have separate methods with each one's type signature, but the compiler is complaining. Can someone explain why? Is this an opportunity to use Generics, and if so, how?

class Base { }
class Level1 : Base { }
class Level2 : Level1 { }

...

List<Base> oList = new List<Base>();
oList.Add(new Level1());
oList.Add(new Level2());

...

...
foreach(Base o in oList)
{
   DoMethod(o);
}

...

void DoMethod(Level1 item) { }
void DoMethod(Level2 item) { }

What am I doing wrong?

+2  A: 

Overloads are resolved at compile-time - and you don't have a DoMethod(Base item) method - so it can't resolve the call. Leaving the list and the loop out of things, you're effectively writing:

Base o = GetBaseFromSomewhere();
DoMethod(o);

The compiler has to find a method called DoMethod which is applicable for a single argument of type Base. There is no such method, hence the failure.

There are a few options here:

  • As Markos says, you can use dynamic typing in C# 4 to make the C# compiler apply overloading at execution time using the actual type of object that o refers to.
  • You can use the Visitor Pattern to effectively get double dispatch (I'm never really fond of this)
  • You can use as or is:

    Level1 x = o as Level2;
    if (x != null)
    {
        DoMethod(x); // Resolves to DoMethod(Level1)
    } 
    else
    {
        Level2 y = o as Level2;
        if (y != null)
        {
            DoMethod(y); // Resolves to DoMethod(Level2)
        }
    }
    

    Again, this is pretty ugly

  • Redesign what you're doing to be able to use normal inheritance, if possible
Jon Skeet
+2  A: 

Overloading a method uses the static type of the variable not the run time type.

You want to use inheritence and overriding.

class Base { public virtual void DoMethod() { /* ... */  } }
class Level1 : Base { public override void DoMethod() { /* ... */ } }
class Level2 : Level1 { public override void DoMethod() { /* ... */ } }
Mark Byers
+1  A: 

Which method is called is determined in Compile time not runtime, so the compiler cannot know which one to call. You have 2 options: Switch over type of the object and call apropriate method, or if you are using .NET 4, use type dynamic.

foreach(dynamic o in oList)
{
   DoMethod(o);
}
Markos
Using `dynamic` would have some impact on performance. More to the point, I think this is a place for good, old-fashioned polymorphism.
Steven Sudit
@Steven: We don't know that for sure. It may be completely inappropriate to put the logic of `DoMethod` in `Level1` and `Level2`.
Jon Skeet
@Jon: Sure, one of the possibilities I considered is that this may require some form of double-dispatch. Still, we should be able to pull this off without `switch` statements.
Steven Sudit
+1  A: 

You don't have a DoMethod(Base item) method. Overloading is not polymorphic. This is normally done by using a virtual method:

class Base {
    public virtual void DoMethod() {...}
}
class Level1 : Base {
    public override void DoMethod() {...}
}
// etc..

foreach(Base o in oList)
{
    o.DoMethod();
}
Hans Passant
+1  A: 

In your foreach loop, o has type Base and neither of the DoMethod overloads take a Base instance. If possible you should move DoMethod to Base and override it in the two subclasses:

public class Base
{
    public virtual void DoMethod() { ... }
}
Lee
+1  A: 

To expand on Mark's answer, DoMethod should be a virtual method in Base, which you invoke on each item in the list.

Steven Sudit
Hans and Mark beat me to this by a couple of minutes, so credit goes to them. I also tossed them upvotes.
Steven Sudit
A: 

I don't know all the details, but if it's a situation where it really isn't appropriate to inherit you could use interfaces instead.

Declare the interface, implement it on each of your classes, then you would be able to cast directly to the interface and run the function from there. My C# is a little shaky, but something like,

Interface IMethodizable
{
   void DoMethod();
}

class Level1 : IMethodizable {
  void DoMethod(){
    //insert code here
  }
}

class Level2 : IMethodizable {
  void DoMethod(){
    //insert code here
  }
}

This works particularly well if the only thing the classes have in common are that method. This is very similar to having a virtualized method int he base class and overriding it. So this pattern is only better if you shouldn't be inheriting, or the DoMethod will also have to run on other objects not inheriting from base, et al.

Mike Cellini