6

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?

end-user
  • 2,845
  • 6
  • 30
  • 56
  • I'm not sure it's clear and I think there is a misunderstanding if `DoMethod` is actualy a method on Base, Level1, Level2? Can you clarify? – Wil P Aug 05 '10 at 20:42
  • @Will P Right, sorry; I'm processing the objects externally, so, no, the DoMethod(s) are not Members of those classes. – end-user Aug 05 '10 at 20:49

8 Answers8

8

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
  • 1,421,763
  • 867
  • 9,128
  • 9,194
3

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
  • 811,555
  • 193
  • 1,581
  • 1,452
3

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
  • 1,622
  • 1
  • 16
  • 18
  • 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 Aug 05 '10 at 20:40
  • @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 Aug 05 '10 at 20:42
  • @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 Aug 05 '10 at 21:03
2

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
  • 922,412
  • 146
  • 1,693
  • 2,536
2

Since C# 7.0 pattern-matching is another option.

For more information see MSDN. Your code would like like:

switch(o)
{
    case Level2 level2: Do(level2); break;
    case Level1 level1: Do(level1); break;
    case Base @base: Do(@base); break;
    default: ...
    case null: ...
}
Patrik
  • 1,355
  • 12
  • 22
  • Thank you Patrik, for your example! – max_cn May 26 '21 at 13:58
  • It helps me in a case when I have a collection of base class references and needs to call for each one method which depends on the real object type. It could be easily done with virtual methods but every instance is used in Object Model and must contain data only. IMHO, Switch...case looks a little bit better than if...else with "is" and "as" keywords. – max_cn May 26 '21 at 14:23
1

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
  • 142,018
  • 20
  • 234
  • 287
1

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
  • 19,391
  • 1
  • 51
  • 53
0

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
  • 340
  • 2
  • 11
  • This would work with base classes as well, but for the OP it might not be appropriate (see above answers for discussion of this). I actually have a similar problem, and in my case it is NOT appropriate - my method will depend on the calling class, so that is where the implementations should be. I may have to use a big switch statement, but it sounds like I should read up on dynamic as I am lucky enough to be coding against C# 4. (ie. for me, this is a good example of Google finding an information question&discussion on StackOverflow!) – winwaed Jan 09 '11 at 22:37