C# 根据子类分派共同变量列表元素

C# 根据子类分派共同变量列表元素,c#,dynamic,dispatch,contravariance,C#,Dynamic,Dispatch,Contravariance,我有类B和C,继承自类SuperA。如果我有一个SuperA列表,其中包含SuperA的各种实现,我如何根据列表中每个元素的实际实现调用method takingB和C参数,而不必测试每个元素的类型(我宁愿避免If(项是B)打开/关闭原理的原因) 不过,我愿意接受任何更好的想法。正如在回答中指出的,进化后出现运行时错误的风险很大。您可以这样称呼它: DoSomething((dynamic)factory.GenerateOutput((dynamic)item)); 这样,使用dynamic

我有类
B
C
,继承自类
SuperA
。如果我有一个
SuperA
列表,其中包含
SuperA
的各种实现,我如何根据列表中每个元素的实际实现调用method taking
B
C
参数,而不必测试每个元素的类型(我宁愿避免
If(项是B)
打开/关闭原理的原因)


不过,我愿意接受任何更好的想法。正如在回答中指出的,进化后出现运行时错误的风险很大。

您可以这样称呼它:

DoSomething((dynamic)factory.GenerateOutput((dynamic)item));
这样,使用
dynamic
可以在运行时将对象绑定到正确的方法


使用此实现时,您必须考虑到发送未实现任何方法的
C
对象的风险,并且您的代码仍将编译,但会生成运行时错误。

编译器会抱怨您的代码,因为正如您指出的,类和方法调用解析发生在编译类型,而不是运行时,因此基于引用的类型(
是类型为
SuperA
的引用),而不是运行时实例的类型

您可以尝试不同的方法:

  • 如果您觉得有意义,可以尝试将多态行为(要生成的输出文本)移动到
    SuperA
    类层次结构中,向
    SuperA
    添加抽象方法或属性,并在
    SuperA
    的子类中以不同的方式实现它
  • 非常简单,但当
    SuperA
    B和
    C
    类超出您的控制范围时,或者当您应该为
    A
    B
    类提供的不同期望行为不属于
    B
    C`类时,它的工作不太好

  • 你可以使用一种我喜欢称之为责任集的方法:它类似于GoF模式的责任链,但没有责任链;-);您可以按如下方式重写
    TestMethod
  • 这种方法使用类型检查(
    项为B
    ),但将它们隐藏在接口后面(具体来说,接口的每个实现都应该提供类型检查,以便选择它可以处理的实例):如果需要添加层次结构根类的第三个
    D extensed SuperA
    子类,则只需添加
    DHandler:Handler
    接口的第三个
    D实现,而不修改已经提供的实现或
    CompositeHelper
    类;您应该对现有代码应用的唯一更改是在您提供给
    CompositeHelper
    的构造函数的列表中注册新的
    处理程序的实现,但这可以很容易地移动到
    IoC容器
    配置或外部配置文件中。 我喜欢这种方法,因为它可以将基于类型检查的算法转变为多态算法

    我最近在我的技术博客上写了一篇关于这个话题的文章:

  • 你可以通过GoF的访客模式来解决这个问题,这比我建议的方法要复杂一点,但正是针对这种情况而设计的
  • 您可以采用基于
    动态
    的方法,正如另一个响应中所建议的那样

  • 我希望这能帮助你

    @DavidG我承认我是在寻找一个优雅的模式,而不是反射,但如果我没有找到任何东西,我就不得不回到这一点上。@DavidG:将
    foreach
    项的类型更改为
    动态
    解决了这个问题。就像我一直告诉人们的那样(甚至今天!):每次使用
    dynamic
    ,都会有一只小猫死亡。:)@DavidG这不是dynamic的合法用例吗?@DavidG:过了一段时间,回到这个主题,我将选择我认为有人提出的访问者模式解决方案。谢谢你的全面回答。我很感激这个问题还有一个.NET4之前的解决方案集。我不会说这个cast在Handle方法中是安全的,因为它是一个公开的方法,可能会被错误调用,而不会被未来的程序员调用CanHandle。当然,您可以在
    Handle
    implementation中添加一个保护:重点是类型检查隐藏在特定的接口实现中,并且不会污染主要的算法。在考虑了它之后,离开主题一段时间,然后回到它,我将选择Visitor模式实现。正如您所说,它正是为此而设计的,是为其他开发人员编写的自文档,不依赖于直接测试类型。谢谢你的全面回答。我意识到了这一点,并在阅读你的答案之前编辑了我的原始问题=)风险确实很大,我会仔细考虑的。谢谢不过,我可以简单地在foreach中使用dynamic。而不是像你给我看的那样投两次。
    foreach (dynamic item in list)
    {
        DoSomething(factory.GenerateOutput(item));
    }
    
    DoSomething((dynamic)factory.GenerateOutput((dynamic)item));
    
    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);
        }
    }
    
    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);
        }
    }
    
    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);
      }
    }