0

I'm struggling with method overload in C#.

I have a few classes inheriting form a generic one

// I can not touch the implementation of these classes
class GenericDataType {}
class ProductDataType : GenericDataType {}
class ContentDataType : GenericDataType {}

And a class that have a method that iterates on a list of these classes. This method should behave differently in base of what is the type of the item that the cycle is currently consuming

class DataTypeParser {
   public List<ParserdType> parseAll(List<GenericDataType> list) {
      var resultList = new List<ParserdType>();
      foreach(var item in list) {
          // if item is a ProductDataType do something
          // if item is a ContentDataType do something else
          // else do some generic stuff...
          // then add to resultList the current processed item
      }
   }
}

I think that the most natural approach is to add a virtual method in GenericDataType and then add an implementation of such method in the sub classes, but I can't touch any class except for DataTypeParser.

I don't want to add a bunch of if else in the code, I would rather use some polymorphic stuff in order to easily handle different implementation of that GenericDataType.

So my solution was to add some overloaded methods in DataTypeParser

class DataTypeParser {
   public List<ParserdType> parseAll(List<GenericDataType> list) {
      var resultList = new List<ParserdType>();
      foreach(var item in list) {
          var parsed = parse(item);
          if(parsed != null){
              resultList.Add(parsed);
          }
      }
   }
   public ParserdType parse(ProductDataType o){
      // do something
      // ...
      return new ParserdTypeProduct();
   }
   public ParserdType parse(ContentDataType o){
      // do something else
      // ...
      return new ParserdTypeContent();
   }
   public ParserdType parse(GenericDataType o){
      return null;
   }
}

But it doesn't work because C# seems to call only the function with generic parameter.

What am I missing?

I'm pretty a noob, particularly for C#, so it's easy that I'm missing something important, even some link may be very helpful.

here is the example on dotnetfiddle

asdru
  • 1,147
  • 10
  • 19
  • 3
    You are looking for "double dispatch", this probably should be duplicate of https://stackoverflow.com/a/45053773/477420, but I don't know if one can make that connection. – Alexei Levenkov Jul 02 '20 at 18:03
  • How different are `do something` and `do something else`? Do they deal with any property of GenericDataType or it's subclasses? – Chetan Jul 02 '20 at 18:29
  • As far as I can see, the obvious choices are as @AlexeiLevenkov mentioned "Double dispatch" using reflection, or implementing an interface where you can use `foreach(var type in IDataType){ type.parse(); }` – Austin T French Jul 02 '20 at 18:31
  • @Chetan Ranpariya the parse method should build a bridge between ```DataType```s and the view that prints the ```ParserdType```s – asdru Jul 02 '20 at 19:16
  • 1
    The C# solution (as shown in the link) is `var parsed = parse((dynamic)item);` or if you don't like cost/magic of it John Wu suggestion is pretty standard way to do that. – Alexei Levenkov Jul 02 '20 at 19:18
  • @Alexei is strange that ```dynamic``` is not working on dotnetfiddle. Anyway the compiler is not complaining and all it is working! so thank you! – asdru Aug 12 '20 at 00:29

2 Answers2

3

Polymorphism is run-time, while the resolution of method overloads is compile-time, so of course your current approach will not work.

If you want to do "polymorphic stuff" you need a virtual method table of some kind, and you can't use the ones built into the classes you have because you can't modify them to add the methods you need.

Instead, you could create another table, e.g.

var methodTable = new Dictionary<Type,Action<GenericDataType>>
{
    { typeof(GenericDataType), x => DoParse(x) },
    { typeof(ProductDataType), x => DoParse(x as ProductDataType) },
    { typeof(ContentDataType), x => DoParse(x as ContentDataType) }
};

Then to iterate over the list and execute:

foreach (var item in list)
{
    methodTable[item.GetType()](item);
}

Because the types are being cast inside the lambda expressions, the appropriate overload will now be selected at runtime.

John Wu
  • 50,556
  • 8
  • 44
  • 80
-1

Possible the best solution would be to extend the GenericDataType with a function that can be overwritten by the ConcreteDataTypes.

class GenericDataType {
  public virtual ParsedType DoParse()
  {
    // e.g. standard stuff that can be done
  }
}
class ProductDataType : GenericDataType {
  public override ParsedType DoParse()
  {
    // Special stuff in ProductDataType
  }
}
class ContentDataType : GenericDataType {
  public override ParsedType DoParse()
  {
    // Special stuff in ContentDataType
  }
}

So the correct function will be called

class DataTypeParser {
   public List<ParserdType> parseAll(List<GenericDataType> datatypes) {
      var resultList = new List<ParserdType>();

      foreach(var item in datatypes) {
        resultList.Add(item.DoParse());
      }

      return resultList;
   }
}

That is also possible with an interface. If you can´t change the base class you can build a second class that inherits from the base class and impelments the DoParse() function.

class GenericParseDataType : GenericDataType {
  public virtual ParsedType DoParse()
  {
    // e.g. standard stuff that can be done
  }
}
class ProductDataType : GenericParseDataType {
  public override ParsedType DoParse()
  {
    // Special stuff in ProductDataType
  }
}
class ContentDataType : GenericParseDataType {
  public override ParsedType DoParse()
  {
    // Special stuff in ContentDataType
  }
}
sunriax
  • 592
  • 4
  • 16
  • This is the obvious answer, but OP said that they cannot touch these classes. So adding a virtual method or an interface seems out of the question. – John Wu Jul 02 '20 at 19:01
  • unfortunately GenericDataType, ProductDataType and ContentDataType are not my stuff and I can't touch them, I have to pick them up as arrives in parseAll as argument. All the stuff I can touch is the DataTypeParser implementation – asdru Jul 02 '20 at 19:08