1

Imagine there is some kind of factory method called GetInstance() that builds a class and returns a base instance;

abstract class BaseClass { }

class Type1 : BaseClass { }

class Type2 : BaseClass { }

BaseClass GetInstance()
{
    // returns either Type1 or Type2 instance
}

I want to execute one of two methods that takes the concrete class instance as a parameter, not the base class

void DoSomething(Type1 instance)
{
    // do something with type 1 instance
}

void DoSomething(Type2 instance)
{
    // do something with type 2 instance
}

The ugly way of doing it, that clearly breaks the open-closed principle, is to just iterate through all the possible types, calling the appropriate method

void GetAndInvokeBad()
{
    BaseClass instance = GetInstance();
    
    // Sucky way of selecting the right polymorphic method
    switch (instance)
    {
        case Type1 t1instance:
            DoSomething(t1instance);
            break;
        case Type2 t2instance:
            DoSomething(t2instance);
            break;
    }
}

A more generic way of doing it would be to use reflection to look up all the methods that can take the class instance physical type

void GetAndInvokeGood()
{
    BaseClass instance = GetInstance();

    var method = GetType().GetMethods()
        .Where(m => m.Name == "DoSomething")
        .First(m => m.GetParameters()[0].ParameterType == instance.GetType());

    method.Invoke(this, new object[] {instance});
}

I'm just wondering, is this a good pattern? Is there a better and more recognised way of switching on the child type rather than using reflection?

Steztric
  • 2,832
  • 2
  • 24
  • 43
  • Why not overload `GetInstance` to return the specific types? – Johnathan Barclay Aug 19 '20 at 15:20
  • Look for "Double Dispatch in C#" – haim770 Aug 19 '20 at 15:24
  • The strategy pattern lets you switch between cases without breaking the open/close principle. – Kieran Devlin Aug 19 '20 at 15:25
  • @JohnathanBarclay C# does not support return type covariance - https://stackoverflow.com/a/5709191/1069178 – Steztric Aug 19 '20 at 19:43
  • @Steztric Overload not override. It was a poor choice of words though; I simply meant use separate methods returning the specific types, overloaded or otherwise. – Johnathan Barclay Aug 19 '20 at 21:34
  • @JohnathanBarclay understood. If I did that it would fail to be a factory pattern because the caller would need to know which subclass to instantiate and call the appropriate method. It would also require switching on types, which is exactly what I wanted to avoid. – Steztric Aug 20 '20 at 17:37

2 Answers2

2

You could also get really dirty and use dynamic. Not everyone is a fan of it; I avoid it unless it's too useful for a certain scenario, and then comment it heavily. Here, it makes for a quite clean implementation.

Using dynamic prevents code analyzers from inspecting that code and finding issues, so just be aware of that when considering its use.

Lastly, here's an SO discussion about using dynamic for reference.

using System;

namespace SomeNamespace
{
    public class Program
    {
        static void Main()
        {
            dynamic instance1 = GetInstance(true); //gets Type1
            dynamic instance2 = GetInstance(false); //gets Type2

            DoSomething(instance1); //prints "Type1 did something"
            DoSomething(instance2); //prints "Type2 did something"
        }

        static BaseClass GetInstance(bool type1)
        {
            // returns either Type1 or Type2 instance
            return type1 ? (BaseClass)(new Type1()) : (BaseClass)(new Type2());
        }

        static void DoSomething(Type1 instance)
        {
            Console.WriteLine("Type1 did something");
        }

        static void DoSomething(Type2 instance)
        {
            Console.WriteLine("Type2 did something");
        }
    }

    abstract class BaseClass { }

    class Type1 : BaseClass { }

    class Type2 : BaseClass { }
}

If you don't want to pass around variables as dynamic, you can also cast to dynamic at the last mile, such as:

DoSomething((dynamic)instance);
Sean Skelly
  • 1,229
  • 7
  • 13
1

One solution is to use the strategy pattern. (don't take this example as a literal example as I wrote it from the top of my head and I might have some of the syntax wrong without checking it in an IDE, it gives you the gist though.)

public class InstanceHandler {
    //Dependency inject or load at runtime.
    private IEnumerable<BaseClass> _declaredTypes;

    public void DoSomething(object obj) {
        var result = _delcaredTypes.SingleOrDefault(x => x.IsType(obj));
        if(result == null) {
            throw new NotSupportedException($"The type '{obj}' is not supported.");
        }
        result.DoSomething(obj);
    }
}

public abstract class BaseInstance() {
    public abstract bool IsType(object type);
    public abstract void DoSomething(object type);
}

public class A : BaseInstance {
    public override bool IsType(object type) {
        return true; //Logic to check if type is matching instance.
    }

    public override void DoSomething(object type) {
        var castType = (ExpectedType) type;
        //Do Something.
    }
}
Kieran Devlin
  • 1,373
  • 1
  • 12
  • 28
  • I see! That's great. Is it possible to make this contravariant? For example, define `BaseInstance where T is BaseClass`, then the abstract method can be `public abstract void DoSomething(T type)` and then A could be `class A : BaseInstance` and the method can be `public override void DoSomething(Type1 type)`. This would overcome the need to cast in the method. – Steztric Aug 19 '20 at 17:10
  • @Steztric It might be possible if you have a play around with it however, as I see it, you'd have an issue storing the declared types with different generic types within the same collection. Maybe you can create a wrapper around the types to store them within the same collection. – Kieran Devlin Aug 19 '20 at 18:11
  • good point. If I did that then I can't store these handlers in `_declaredTypes` – Steztric Aug 19 '20 at 19:29