当作为参数传递给重载方法时,区分基类和派生类

本文关键字:基类 派生 参数传递 方法 重载 | 更新日期: 2023-09-27 18:28:30

我目前正在C#中为大学的一次讲座实现访问者模式。它目前运行良好,但我对我们必须完成的任务有一个问题。现在我有这样的事情:

public class TreeStructure<T>
{
  protected TreeStructure<T> _left;
  protected TreeStructure<T> _right;
  public T myValue;
  public TreeStructure(TreeStructure<T> left, T value ,TreeStructure<T> right)
  {
    this._left = left;
    this._right = right; 
    this.myValue = value;
  }
  public virtual void InOrder(MyVisitor<T> visitor)
  {
    if(!IsEmpty(this._left))
    {
      _left.InOrder(visitor);
    }
    visitor.Visit(this);
    if(!IsEmpty(this._right))
    {
      _right.InOrder(visitor);
    }
  }
  protected bool IsEmpty(TreeStructure<T> node)
  {
    return node == null;
  }
}
 public class SpecialTree<T> : TreeStructure<T>
  {
    public SpecialTree(TreeStructure<T> left, T value, TreeStructure<T> right)
    {
      this._left = left;
      this._right = right;
      this.myValue = value;
    }
    public override void InOrder(MyVisitor<T> visitor)
    {
      if(!IsEmpty(this._left))
      {
        _left.InOrder(visitor);
      }
      visitor.Visit(this);
      Console.WriteLine("Hallo");
      if(!IsEmpty(this._right))
      {
        _right.InOrder(visitor);
      }
    }
  }

现在我们应该实施一些访问者。这是没有问题的,直到我们应该在一个访问者中区分树的类型。所以我现在有一个访客是这样的:

  public interface MyVisitor<T>
  {
    void Visit(TreeStructure<T> tree);
  }
  public class CountVisitor<T> : MyVisitor<T>
  {
    public int count { get; set;}
    public CountVisitor()
    {
      count = 0;
    }
    public void Visit(TreeStructure<T> tree)
    {
      count++;
    }
    public void Visit(SpecialTree<T> specialTree)
    {
      count+=2;
    }
  }

所以我的问题是,这显然在Java中有效,但在C#中无效。从未调用SpecialTreeVisit()方法。所以我知道我可以像这里这样专门检查对象的类型:区分基类的派生类的最佳方法是什么?但有人能向我解释一下,为什么它不适用于重载方法吗?还是我这里有错?

编辑:以下是一个树的测试初始化:

TreeStructure<int> tree1 = new TreeStructure<int>(new TreeStructure<int>  (null,3,null),1,new SpecialTree<int>(null,2,null));
      CountVisitor<int> visitor2 = new CountVisitor<int>();
      tree1.InOrder(visitor2);
      Console.WriteLine("Nodecount: " + visitor2.count.ToString());

当作为参数传递给重载方法时,区分基类和派生类

这在很大程度上取决于MyVisitor<T>的实现。在你的评论中,你说这是一个界面。你确定这就是整个例子吗?问题中显示的代码不会编译,因为SpecialTree<T>没有调用其父构造函数。

如果MyVisitor<T>没有声明Visit(SpecialTree<T> specialTree),那么InOrder所做的接口调度将永远不会到达SpecialTree的情况。

更新

考虑到您更新的初始化代码,并假设接口声明为:

interface MyVisitor<T>
{
    void Visit(TreeStructure<T> treeStructure);
    void Visit(SpecialTree<T> tree);
}

正如预期的那样,你的CountVisitor<T>给了我4的结果。

为什么不起作用

我会尽力解释为什么你的例子不起作用。类SpecialTree中的方法InOrder具有类型为MyVisitor的参数。当在MyVisitor上调用方法Visit时,不存在占用SpecialTree的过载,但是,存在占用TreeStructure的过载。在寻找要调用的正确方法时,C#没有考虑接口的实现。因此,将调用具有TreeStructure作为参数的方法,而不是接受SpecialTree参数的方法。

如果我不在乎它是什么样的树结构呢

作为对您评论的回应,您只需创建一个将被树的所有实现调用的方法:

class DoNotCareVisitor<T> : MyVisitor<T> {
    void GenericVisit(TreeStructure<T> tree) {  
        //.. do whatever 
    }
    public void Visit(TreeStructure<T> tree) {
        GenericVisit(tree);
    }
    public void Visit(SpecialTree<T> tree) {
        GenericVisit(treeStructure);
    }
}

您的方法Visit(SpecialTree<T> specialTree)不是MyVisitor<T>接口的一部分(顺便说一句,在接口前面加一个大写的I是一种很好的做法,所以IMyVisitor<T>会提高可读性),所以没有理由调用它。

换句话说,当您调用visitor.Visit(this)时,您的访问者变量的类型是MyVisitor<T>,因此在这一点上,编译器不知道您打算在那里使用特定的类,顺便说一句,这可能会更好地重载您的函数。

最简单的方法是向接口添加第二个方法,该方法具有以下签名:void Visit(SpecialTree<T> specialTree)

您所需要做的就是检查public void Visit(TreeStructure<T> tree)中树之间的区别,如下所示:

public class CountVisitor<T> : MyVisitor<T>
{
    public int count { get; set;}
    public CountVisitor()
    {
      count = 0;
    }
    public void Visit(TreeStructure<T> tree)
    {
      if (tree is SpecialTree<T>)
          count += 2;
      else
          count++;
    }
}

您不需要区分这两种树类型。访问者的目的是遍历结构并以通用的方式收集某种度量。

访问者应该是这样的:

public class CountVisitor<T> : MyVisitor<T>
{
    public int count { get; set;}
    public CountVisitor()
    {
        count = 0;
    }
    public void Visit(TreeStructure<T> tree)
    {
        count++;
    }
}

就是这样。

以下是你在SpecialTree<T>课程中需要做的事情:

public override void InOrder(MyVisitor<T> visitor)
{
    if(!IsEmpty(this._left))
    {
        _left.InOrder(visitor);
    }
    visitor.Visit(this);
    visitor.Visit(this);
    if(!IsEmpty(this._right))
    {
        _right.InOrder(visitor);
    }
}

因此,基本上,SpecialTree<T>类中重写的目的是两次调用.Visit方法。

现在,当我运行你的测试代码时,我得到:

Nodecount: 4