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