我正在C#中创建一个Tree结构,我有一些特定的目标:


Tree的用户创建new Tree时,要求他们传递
ITreeComparer的类型,用户知道T的类型,并且必须
创建正确的比较器。
Tree类创建TreeNode的所有实例,调用者仅将T类型的值添加到Tree类。 TreeNode是私有的,并且是Tree类的子类,因此我需要一种将IComparer传递给TreeNode的方法,因此我将ITreeComparer设置为静态。
我想对复杂类型的'T'使用我的自定义ITreeComparer,但是我想让ITReeComparer从IComparer继承,这样,当类型T是基元时,不需要Tree的用户通过ITreeComparer双精度和整数。


我的问题:


我希望有一种方法可以将ITreeComparer保留在Tree类中
即使Tree类处理自身的比较。我做了
ITreeComparision静态中的TreeNode属性,更好
解决方案比这?
我收到TreeNode定义'=='或'!='的警告,但是
不覆盖'Object.Equals(object o)和
'Object.GetHasCode',为什么我想使用Object.Equals
使用_comparer.Compare(x.Value, y.Value)_comparer
ITreeComparer中传递的Tree实例创建者必须提供。
如果ITreeComparer要实现IComparer,我是否要检查
如果我的_comparer is null和if是使用IComparer默认
相比之下,作为Tree的用户不必传递ITreeComparison实施方式吗?

public interface ITreeComparer<in T>
{
    int Compare(T x, T y);
}


public class Tree<T>
{
 private TreeNode _root = null;
 private ITreeComparer<T> _comparer = null;


public Tree(ITreeComparer<T> comparer)
{
    _comparer = comparer;
}

public Tree(T value, ITreeComparer<T> comparer)
{
    _root =  new TreeNode(value,_comparer);
    _comparer = comparer;
}

public T Add(T value)
{
    var newNode = new TreeNode(value, _comparer);
    if (_root == null)
    {
        _root = newNode;
        return _root.Value;
    }
    else
    {
        var startEdgeDirection = EdgeDirection.Left;

        if (_root > newNode)
            startEdgeDirection = EdgeDirection.Right;

        var parent = FindItemNodeParent(value, _root, startEdgeDirection);
    }

    return value;
}

private TreeNode FindItemNodeParent(T value, TreeNode current , EdgeDirection direction)
{
    throw new NotImplementedException();

    if (_comparer.Compare(current.Value, value) == 0)
        return null;

    if (direction == EdgeDirection.Left)
    {

    }
    else
    {

    }

}

private TreeNode Search(T value, TreeNode current, EdgeDirection direction )
{
    throw new NotImplementedException();

    if (_comparer.Compare(current.Value, value) == 0)
        return null;

    if (direction == EdgeDirection.Left)
    {

    }
    else
    {

    }
}

private enum EdgeDirection
{
    Left,
    Right
}


private class TreeNode
{
    private static ITreeComparer<T> _comparer;
    public TreeNode LeftEdge { get; set; }
    public TreeNode RightEdge { get; set; }
    public T Value { get; set; }

    public TreeNode(ITreeComparer<T> comparer)
    {
        _comparer = comparer;
    }

    public TreeNode(T value, ITreeComparer<T> comparer)
    {
        Value = value;
        _comparer = comparer;
    }

    public static bool operator < (TreeNode x, TreeNode y)
    {
          if (x == null)
            return y == null;
        return _comparer.Compare(x.Value, y.Value) < 0;

    }

    public static bool operator > (TreeNode x, TreeNode y)
    {
          if (x == null)
            return y == null;

        return _comparer.Compare(x.Value, y.Value) > 0;
    }

    public static bool operator == (TreeNode x, TreeNode y)
    {
        if (x == null)
            return y == null;

        return _comparer.Compare(x.Value, y.Value) == 0;
    }

    public static bool operator != (TreeNode x, TreeNode y)
    {
         if (x == null)
            return y == null;

        return _comparer.Compare(x.Value, y.Value)  != 0;
    }
}


}


更新

在得到有用的Stackoverflowers的建议之后,我只是在使用IComparable,然后从TreeNode中删除了所有重载的比较运算符,并将私有IComparer<T> _comparer设置为Comparer<T>.Default。这总是有效的,因为我添加了“ where T:IComparable”,这意味着Tree的用户将必须在其自定义T对象上实现IComparable,对于C#基元,已经实现了IComparable,并且当T为int时,他们将不必实现IComparable。例如,它们将永远不需要传递IComparer,因为必须始终为其类型T实现IComparable。

    public partial class Tree<T> where T : IComparable<T>
{
    private TreeNode _root = null;
    private IComparer<T> _comparer = null;


    public Tree()
    {
        _comparer = Comparer<T>.Default;
    }

    public Tree(T value)
    {
        _root = new TreeNode(value);
        _comparer = Comparer<T>.Default;
    }

    public T Add(T value)
    {
        var newNode = new TreeNode(value);
        if (_root == null)
        {
            _root = newNode;
            return _root.Value;
        }

        var startEdgeDirection = EdgeDirection.Left;

        if (_comparer.Compare(_root.Value, value) > 0)
            startEdgeDirection = EdgeDirection.Right;

        var parent = FindItemNodeParent(value, _root, startEdgeDirection);

        if (parent != null)
        {
            if (_comparer.Compare(parent.Value, value) > 0)
            {
                parent.RightDescendant = newNode;
            }
            else
            {
                parent.LeftDescendant = newNode;
            }
        }


        return value;
    }

    private TreeNode FindItemNodeParent(T value, TreeNode current, EdgeDirection direction)
    {
        if (_comparer.Compare(current.Value, value) == 0)
            return null;

        if (direction == EdgeDirection.Left)
        {
            if (current.LeftDescendant == null)
                return current;

            if (_comparer.Compare(current.LeftDescendant.Value, value) > 0)
            {
                FindItemNodeParent(value, current.LeftDescendant, EdgeDirection.Right);
            }
            else
            {
                FindItemNodeParent(value, current.LeftDescendant, EdgeDirection.Left);
            }
        }
        else
        {
            if (current.RightDescendant == null)
                return current;

            if (_comparer.Compare(current.RightDescendant.Value, value) > 0)
            {
                FindItemNodeParent(value, current.RightDescendant, EdgeDirection.Right);
            }
            else
            {
                FindItemNodeParent(value, current.RightDescendant, EdgeDirection.Left);
            }

        }

        return null;

    }

    private TreeNode Search(T value, TreeNode current, EdgeDirection direction)
    {
        throw new NotImplementedException();

        if (_comparer.Compare(current.Value, value) == 0)
            return null;

        if (direction == EdgeDirection.Left)
        {

        }
        else
        {

        }
    }

    private enum EdgeDirection
    {
        Left,
        Right
    }

}

 public partial class Tree<T>
{
    private class TreeNode
    {
        public TreeNode LeftDescendant { get; set; }
        public TreeNode RightDescendant { get; set; }
        public T Value { get; set; }

        public TreeNode(T value)
        {
            Value = value;
        }
    }
}

最佳答案

正如我在上面的评论中提到的,我不明白为什么您要使用接口ITreeComparer。在我看来,它与IComparer完全相同,因此您可以使用IComparer代替。

那说...


  
  我希望有一种方法可以将ITreeComparer保留在Tree类中,即使Tree类可以处理自身的比较。我将TreeNode中的ITreeComparision属性设为静态,有什么比这更好的解决方案了?
  


我同意将其设为static是个坏主意。一个明显的问题是,这意味着您在一个进程中只能使用一种Tree<T>(即,对于任何给定的T,只能有一种比较)。例如,这意味着,如果您有一个具有属性NameId的类,则只能有按Name排序的树或按Id排序的树,但不能同时有两种树同时。

实际上,在static类中使用此字段TreeNode<T>时,在Tree<T>中作为实例字段是没有意义的,因为所有Tree<T>对象将使用相同的TreeNode<T>类型。

在我看来,甚至将ITreeComparer公开给TreeNode的唯一原因是为了允许重载比较运算符。如果必须使用这些运算符,我将继续使_comparer字段在TreeNode<T>中为非静态,或者甚至将其更改为对父Tree<T>对象的引用(然后它可以获取(来自父级)。

但实际上,我觉得比较运算符没有太大帮助。您也可以直接在_comparer类中直接调用_comparer,而不必担心Tree<T>甚至不知道如何进行比较。


  
  我收到警告,指出TreeNode定义了'=='或'!=',但没有覆盖'Object.Equals(object o)和'Object.GetHasCode',为什么当我想使用_comparer时为什么要使用Object.Equals .Compare(x.Value,y.Value),_ comparer是Tree必须提供的ITreeComparer实例创建者中传递的。
  


不管是好是坏,对象在C#中有几种不同的比较方式。每当这些方法的实现方式不同时,您就需要为一些真正的抓头bug做好准备。

因此,当您重载与相等性相关的运算符TreeNode<T>==而不是!=方法时,会收到警告。同样,在不修复Equals()来正确反映那些相等关系的情况下实现自定义相等关系,也是获取难以修复的错误的好方法。

如果您开始在类型本身中实现相等操作,那么确保您通过该类型并进行一致的所有与相等相关的操作确实非常重要。


  
  如果要使用ITreeComparer实现IComparer,我是否只需检查_comparer是否为null并使用IComparer默认比较,那么作为Tree的用户,则不必传递ITreeComparison实现吗?
  


如前所述,我认为您应该完全放弃GetHashCode()。没有实际的“沙箱操作”……实际上,如果需要,用户可以使用一个完全相同的方法,使单个类实现两个接口。当内置的界面足以满足要求时,强迫用户实施自定义界面只会使他们感到烦恼。


只要我在写,我都会同意the non-answer provided by ipavlu确实提供了一些有用的观察。特别是,您对null的处理都是错误的。另一篇文章指出了一些问题。另一个问题是,如果ITreeComparerx都为null,则您的比较运算符都将返回y。即根据代码,如果truex都为空,则它们彼此同时大于和小于。

幸运的是,确实没有任何明显的理由使比较完全需要处理空值。按照惯例,原始实现(由用户提供)必须容纳空值。您自己的代码不需要检查它们。此外,根本没有明显的原因导致null值出现在树中。空节点引用没有多大意义:每当您在树中达到空值时,即要在其中存储节点。无需确定新节点是在该null的左侧还是右侧……它在当时的null位置正确!

关于c# - 为类型T的复杂对象创建自定义比较器,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/33359367/

10-11 02:06