2017-06-11 15 views
-1

我正在探索Visitor模式,下面是代码。请注意,我已经知道如何解决这个问题了 - 使用虚拟方法并用各种过载扩展Visitor界面。我想更好地理解c#中的类型和重载分辨率。方法解析 - 为什么重载的方法不叫

在测试方法中,当调用Accept方法时,它始终使用参数类型“Element”而不是BinaryOperator调用Visit方法。根据关于SO的其他类似问题的答案,这是因为在编译时解析了超载方法&对象类型。

链接:

why-isnt-the-overloaded

runtime-type-vs-compile-time

non-virtual-method-resolution

当我检查生成的IL,用于接受方法,在呼叫为Visitor.Visit()的指令是“callvirt “而不是”呼叫“。有什么我应该看看在IL中,表明过载设置在编译时?

此外,如果我使用反射检查Accept方法中的对象类型,它将打印MathVisitor和BinaryOperator。所以运行时间知道正确的类型。那么为什么没有调用Visit方法的正确重载呢?

抽象:

public enum MathOp 
{ 
    ADD, 
    SUBSTRACT, 
    MULTIPLY 
} 

public interface IElementVisitor 
{ 
    void Visit(Element e); 
} 

public abstract class Element 
{ 
    public string ElementValue { get; set; } 
    public void Accept(IElementVisitor ev) { 

     //Console.WriteLine("Type for Paramter is {0}",ev.GetType().Name); 
     //Console.WriteLine("Type for 'this' is {0}", this.GetType().Name); 

     ev.Visit(this); 

     //(ev as dynamic).Visit(this as dynamic); 
    } 
    public int ToNumber 
    { 
     get { return int.Parse(ElementValue); } 
    } 
} 

混凝土:

class NumberLiteral:Element 
{ 
    public NumberLiteral(int number) 
    { 
     ElementValue = number.ToString(); 
    } 
} 

class BinaryOperator:Element 
{ 
    public NumberLiteral Left { get; set; } 
    public NumberLiteral Right { get; set; } 

    public MathOp MathOpType { get; set; } 

    public BinaryOperator(MathOp optype) 
    { 
     MathOpType = optype; 
    } 
} 

class MathVisitor : IElementVisitor 
{ 
    public int Result { get; private set; } 
    public void Visit(Element e) 
    { 
     Console.WriteLine("---Not Implemented--for--Element"); 
    } 

    public void Visit(NumberLiteral e) 
    { 
     Console.WriteLine("Num Lit - do nothing"); 
    } 

    public void Visit(BinaryOperator b) 
    { 
     if (b.MathOpType.Equals(MathOp.ADD)) 
     { 
      int v1 = b.Left.ToNumber; 
      int v2 = b.Right.ToNumber; 
      Result = v1 + v2; 
     } 
    } 
} 

测试:

public class TestVisitorPattern 
{ 
    public void TestMethod() 
    { 
     NumberLiteral e1 = new NumberLiteral(1); 
     NumberLiteral e2 = new NumberLiteral(2); 

     BinaryOperator b1 = new BinaryOperator(MathOp.ADD); 

     b1.Left = e1; 
     b1.Right = e2; 


     MathVisitor mv = new MathVisitor(); 
     Console.WriteLine("------------direct call---------------"); 
     mv.Visit(b1); 
     Console.WriteLine(mv.Result); 


     mv = new MathVisitor(); 
     Console.WriteLine("------------call through accept---------------"); 

     b1.Accept(mv); 
     Console.WriteLine(mv.Result); 

    } 
} 
+0

正如标记为副本所述,当通过类型为接口的变量调用方法时,编译器没有足够的上下文来选择任何方法过载,除了为该接口实现的对象之外。这是编译器当时唯一可以看到的超载方法。另请参阅https://stackoverflow.com/questions/2038333/overloads-done-at-compile-time和https://stackoverflow.com/questions/5572880/c-sharp-method-overload-problem-with-class-派生自泛型抽象类。 –

+0

请注意,另一个解决方法是,接口的实施者可以进行一些类型检查来选择正确的过载,而不是扩展接口。或者直接用'as'运算符,或者用'dynamic'隐式地执行。恕我直言,这将是更好的混乱你的接口声明一堆类型,实现者可能永远不需要甚至不需要知道,不介意使用(当接口公开为公共类型时可能很重要,而试图保持实现特定类型内部)。 –

回答

0

当你调用接受,它进入这个方法:

public void Accept(IElementVisitor ev) 
    { 

     //Console.WriteLine("Type for Paramter is {0}",ev.GetType().Name); 
     //Console.WriteLine("Type for 'this' is {0}", this.GetType().Name); 

     ev.Visit(this); 

     //(ev as dynamic).Visit(this as dynamic); 
    } 

这反过来调用到:

public void Visit(Element e) 
    { 
     Console.WriteLine("---Not Implemented--for--Element"); 
    } 

,这是因为这是实现IElementVisitor接口的唯一方法:

public interface IElementVisitor 
{ 
    void Visit(Element e); 
} 

因此,即使implementator有其他的“好”的方法可用,因为ev类型为IElementVisitor唯一可用于调用的方法是Visit(Element e) - 因此它是它调用的方法。

相关问题