【问题标题】:C# - Polymorphic method selection from base typeC# - 从基类型中选择多态方法
【发布时间】:2020-12-08 21:52:21
【问题描述】:

想象有某种工厂方法称为GetInstance(),它构建一个类并返回一个基实例;

abstract class BaseClass { }

class Type1 : BaseClass { }

class Type2 : BaseClass { }

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

我想执行将具体类实例而不是基类作为参数的两种方法之一

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

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

这样做的丑陋方式显然打破了开闭原则,就是遍历所有可能的类型,调用适当的方法

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;
    }
}

一种更通用的方法是使用反射来查找可以采用类实例物理类型的所有方法

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});
}

我只是想知道,这是一个好的模式吗?有没有比使用反射更好、更受认可的方式来打开子类型?

【问题讨论】:

  • 为什么不重载GetInstance 以返回特定类型?
  • 寻找“C#中的双重调度”
  • 策略模式可以让你在不破坏开/关原则的情况下切换案例。
  • @JohnathanBarclay C# 不支持返回类型协方差 - stackoverflow.com/a/5709191/1069178
  • @Steztric 重载不覆盖。不过,这是一个糟糕的选择。我的意思是使用单独的方法返回特定类型,重载或其他。

标签: c# reflection polymorphism


【解决方案1】:

一种解决方案是使用策略模式。 (不要把这个例子当作文字例子,因为我是从头顶写的,如果没有在 IDE 中检查它,我可能有一些语法错误,但它给了你要点。)

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.
    }
}

【讨论】:

  • 我明白了!那太棒了。是否有可能使这个逆变器?比如定义BaseInstance&lt;in T&gt; where T is BaseClass,那么抽象方法可以是public abstract void DoSomething&lt;T&gt;(T type),那么A可以是class A : BaseInstance&lt;Type1&gt;,方法可以是public override void DoSomething(Type1 type)。这将克服在方法中强制转换的需要。
  • @Steztric 如果您可以尝试使用它,那么它可能是可能的,但是,正如我所看到的,在同一个集合中存储具有不同泛型类型的声明类型时会遇到问题。也许您可以围绕这些类型创建一个包装器,以将它们存储在同一个集合中。
  • 好点。如果我这样做了,那么我无法将这些处理程序存储在 _declaredTypes
【解决方案2】:

您也可能会变得非常肮脏并使用dynamic。不是每个人都喜欢它。我会避免使用它,除非它对某个场景太有用,然后对其进行大量评论。在这里,它实现了一个非常干净的实现。

使用dynamic 会阻止代码分析器检查该代码并发现问题,因此在考虑使用它时请注意这一点。

最后,这里有一个SO discussion 关于使用dynamic 的参考。

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 { }
}

如果不想将变量传递为dynamic,也可以在最后一英里处转换为dynamic,例如:

DoSomething((dynamic)instance);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-22
    • 1970-01-01
    • 2017-01-31
    • 2011-03-05
    • 1970-01-01
    • 2010-12-07
    相关资源
    最近更新 更多