如何使用多态性+重载改进这种方法以减少 IS(类型检查)?

Posted

技术标签:

【中文标题】如何使用多态性+重载改进这种方法以减少 IS(类型检查)?【英文标题】:how to improve this method using polymorphism+overloading so as to reduce IS (type check)? 【发布时间】:2012-04-11 04:02:54 【问题描述】:

例如

BaseClass MyBase()

    public int Add(BaseClass next)
    
        if (this is InheritedA && next is InheritedA)
            return 1;
        else if (this is InheritedA && next is InheritedB)
            return 2;
        else if (this is InheritedB && next is InheritedA)
            return 3;
        else if (this is InheritedB && next is InheritedB)
            return 4;      
     

其中InheritedAInheritedB 是它的继承类。其实继承类更​​多,Add根据其操作数的顺序和类型返回不同的结果。

我正在考虑使用多态性和重载来重写它,但是它变得相当复杂,我必须引入一个辅助方法来解析任一端的类型。

例如

InheritedA myA()

    public override int Add(BaseClass next)
    
        return next.AddTo(this);
    

现在我必须将AddTo 放入BaseClass,并在继承的类中覆盖它。

InheritedA myA()

    public override int AddTo(InheritedA next)  return 1; 
    public override int AddTo(InheritedB next)  return 3; 


BaseClass myBase()

    public abstract int Add(BaseClass next);
    public abstract int AddTo(InheritedA next);
    public abstract int AddTo(InheritedB next);

有没有更好的方法?

【问题讨论】:

一旦有 InheritedC 类会发生什么? IE 做 A + C = A + B? 每个操作数(lval & rval)是否只有两个值? @CAbbott 我可以修改我的班级以遵守这个限制,所以是的,只有两个值 @Sign 取决于,但通常 A+C != A+B 顺便说一下,Add 是一个运算符,我应该将其命名为“Marry”或“Cat”,只是为了与常识 你能给这个“上下文”吗?即,我猜您正在尝试简化“现实生活”场景,但这会影响您“如何”“调用”添加,操作员在做什么,大局,您是否有层次结构- 并且'总是关于比较类型' - 例如你如何处理'添加'和之后的结果,你是否结合了更多 - ... 【参考方案1】:

您正在实现的模式称为双重虚拟调度

单个虚拟调度根据接收者的运行时类型编译时类型选择调用哪个方法的论点。这是一个传统的虚拟调度:

abstract class Animal 
class Tiger : Animal 
class Giraffe : Animal  
class B

    public virtual void M(Tiger x) 
    public virtual void M(Animal x) 

class D : B

    public override void M(Tiger x) 
    public override void M(Animal x) 

...
B b = whatever;
Animal a = new Tiger();
b.M(a);

调用哪个方法?未选择B.M(Tiger)D.M(Tiger);我们根据参数的编译时间类型拒绝它们,即Animal。但是我们根据whatevernew B()还是new D()来选择是在运行时调用B.M(Animal)还是D.M(Animal)

Double virtual dispatch 根据两个事物的运行时类型选择调用哪个方法。如果 C# 支持双虚拟分派,但它不支持,那么即使参数的编译时类型是 Animal,运行时分派也会转到 B.M(Tiger)D.M(Tiger)

C# 4 确实支持 dynamic 调度。如果你说

dynamic b = whatever;
dynamic a = new Tiger();
b.M(a);

然后,M 的分析将完全在运行时使用ba 的运行时类型进行。这速度明显较慢,但确实有效。

或者,如果您想进行双重虚拟调度并在编译时完成尽可能多的分析,那么标准的方法是实现访问者模式,您可以在互联网上轻松查找。

【讨论】:

很高兴知道这一切。您说“接收器的运行时类型和参数的编译时类型”..但是为什么您说“a”的运行时类型不是 Animal 而不是 Tiger ?我知道我可以肯定地将 a 类型转换为 Tiger,但类型转换之前的“a”类型仍然是 Animal。没有? @dnkulkarni,某物的运行时类型是运行时的实际类型。而这里的实际类型是Tiger,因为这就是a 中实际存储的内容(或者更确切地说,是对Tiger 的引用)。 @dnkulkarni:变量的类型是Animal。那就是表达式的“编译时类型”。表达式的“运行时类型”是实际存储在变量中的事物的类型。 @EricLippert 也许这太吹毛求疵了,但是对于引用类型,说“运行时类型是变量实际引用的事物的类型”不是更好吗?【参考方案2】:

正如 cmets 中所建议的那样,如果您能够为每个派生分配一个常量值,那么您可以构建一个比我在这里描述的更简洁的实现,只需拥有一个名为 Value 或类似名称的虚拟属性用于加法。

假设这不是一个选项,您可能希望考虑在基类级别预先计算结果,以描述您为每个组合分配的值。随着类集的增长,这可能会崩溃并变得容易出错且乏味,因此我建议仅在您希望维护非常小的集时才考虑这一点。

在我的基本示例中,我使用字典来保存集合并对组合进行硬编码。从您的评论来看,似乎没有任何基本的算术规则适用,所以我在这里将它们作为约束排除在外。如果结果值没有实际意义并且您只是增加它,您可以考虑使用反射构建结果集以提取派生类并考虑每种组合。

public class BaseClass

  private static readonly Dictionary<int, int> addResults = new Dictionary<int, int>();

  static BaseClass()
  
    addResults.Add(CreateKey(typeof(ChildA), typeof(ChildA)), 1);
    addResults.Add(CreateKey(typeof(ChildA), typeof(ChildB)), 2);
    addResults.Add(CreateKey(typeof(ChildB), typeof(ChildA)), 3);
    addResults.Add(CreateKey(typeof(ChildB), typeof(ChildB)), 4);
  

  public static int CreateKey(Type a, Type b)
  
    return (String.Concat(a.Name, b.Name).GetHashCode());
  

  public int Add(BaseClass next)
  
    var result = default(int);

    if (!addResults.TryGetValue(CreateKey(this.GetType(), next.GetType()), out result))
    
      throw new ArgumentOutOfRangeException("Unknown operand combination");
    

    return result;
  


public class ChildA : BaseClass 
public class ChildB : BaseClass 

【讨论】:

恐怕会比我的版本 1 慢? 性能总是很难推测,但我希望性能与代表性数据集相当。预先计算的实现需要更多的开销来构造计算集。每次调用“Add”都需要分配内存来进行连接和哈希值计算,每次调用一次。初始实现是通过在子句中使用“is”运算符对条件的每个分支执行与 GetType 等效的操作。除非您在实时系统中,否则我认为性能不是两者之间有意义的标准。

以上是关于如何使用多态性+重载改进这种方法以减少 IS(类型检查)?的主要内容,如果未能解决你的问题,请参考以下文章

重载与多态

方法的重载和重写

Java中重载和重写的区别

重写与重载之间的区别

java重载和重写

重载与重写的区别