Search code examples
c#dynamicdispatchcontravariance

Dispatch co-variant list elements according to subclass


I have classes B and C, inheriting from class SuperA. If I have a list of SuperA containing various implementations of SuperA, how can I call method taking B and C argument according to the actual implementation of each element in the list, without having to test the type of each element (I would prefer to avoid if(item is B) stuff for open/closed principle reasons).

public class Test
{
    public void TestMethod()
    {
        var list = new List<SuperA> {new B(), new C()};

        var factory = new OutputFactory();

        foreach (SuperA item in list)
        {
            DoSomething(factory.GenerateOutput(item)); // doesn't compile as there is no GenerateOutput(SuperA foo) signature in OutputFactory.
        }
    }

    private static void DoSomething(OutputB b)
    {
        Console.WriteLine(b.ToString());
    }

    private static void DoSomething(OutputC c)
    {
        Console.WriteLine(c.ToString());
    }

    public class SuperA
    {
    }

    public class B : SuperA
    {
    }

    public class C : SuperA
    {
    }


    public class OutputB
    {
        public override string ToString()
        {
            return "B";
        }
    }

    public class OutputC
    {
        public override string ToString()
        {
            return "C";
        }
    }

    public class OutputFactory
    {
        public OutputB GenerateOutput(B foo)
        {
            return new OutputB();
        }

        public OutputC GenerateOutput(C foo)
        {
            return new OutputC();
        }
    }
}

In the above code, I wish to print :

B

C

EDIT : A working solution I found could be changing the item type to dynamic

foreach (dynamic item in list)
{
    DoSomething(factory.GenerateOutput(item));
}

I'm open to any better idea however. As pointed out in answer, the risk of runtime error after an evolution is great.


Solution

  • The compiler complains about your code because, as you pointed out, threre is no GenerateOutput(SuperA) in OutputFactory class and method call resolution happens at compile type, not at runtime, and therefore is based on the type of the reference (item is a reference with type SuperA) and not on the type of the runtime instance.

    You can try with different approaches:

    1. if you find it makes sense, you can try to move polymorphic behaviour (the output text to generate) into SuperA class hierarchy, adding an abstract method or property to SuperA and implementing it differently in SuperA's subclasses
    class SuperA {
      public abstract string Content { get; }
    }
    
    class B : SuperA {
      public string Content => "B";
    }
    
    class C : SuperA {
      public string Content => "C";
    }
    
    class Test {
      public void TestMethod() {
        // ...
        foreach (SuperA item in list) {
          Console.WriteLine(item.Content);
        }
    }
    

    Very simple but it does not work very well when SuperA,B, andCclasses are out of your control or when the different desired behaviours you should provide forAandBclasses does not belong toBandC` classes.

    1. you can use an approach I like to call set of responsibility: it's something ilke the GoF's pattern chain of responsibility but without chain ;-); you can rewrite your TestMethod as follows:
    public void TestMethod() {
        var list = new List<SuperA> {new B(), new C()};
    
        var compositeHandler = new CompositeHandler(new Handler[]  {
            new BHandler(),
            new CHandler()
        });
    
        foreach (SuperA item in list) {
          compositeHandler.Handle(item);
        }
    }
    

    So you need define a Handler interface and its implementations like follows:

    interface Handler {
      bool CanHandle(SuperA item);
    
      void Handle(SuperA item);
    }
    
    class BHandler : Handler {
      bool CanHandle(SuperA item) => item is B;
    
      void Handle(SuperA item) {
       var b = (B)item; // cast here is safe due to previous check in `CanHandle()`
       DoSomethingUsingB(b);
      }
    }
    
    class CHandler : Handler {
      bool CanHandle(SuperA item) => item is C;
    
      void Handle(SuperA item) {
       var c = (C)item; // cast here is safe due to previous check in `CanHandle()`
       DoSomethingUsingC(c);
      }
    }
    
    class CompositeHandler {
      private readonly IEnumerable<handler> handlers;
      public CompositeHandler(IEnumerable<handler> handlers) {
        this.handlers = handlers;
      }
    
      public void Handle(SuperA item) {
        handlers.FirstOrDefault(h => h.CanHandle(item))?.Handle(item);
      }
    }
    

    This approach uses type checks (item is B), but hides them behind an interface (specifically, each implementation of the interface should provide a type check in order to select instances it can handle): if you need to add a third D extends SuperA subclass of your hierarchy root class you need only add a third DHandler : Handler implementation of Handler interface, without modify neither already provided implementations nor CompositeHelper class; the only change you should apply to existing code is the registration of the new handler's implementation in the list you provide to CompositeHelper's constructor, but this can easily be moved to you IoC container configuration or to an external configuration file. I like this approach because it makes possibile to turn a type check based algorithm into a polymorphical one.

    I wrote about this topic in a recent post on my technical blog: https://javapeanuts.blogspot.com/2018/10/set-of-responsibility.html.

    1. You can approach the question through the GoF's visitor pattern, that is a bit more complicated than my suggested approach but was conceived precisely for such cases
    2. You can adopt a dynamic based approach, as suggested in another response

    I hope this can help you!