繁体   English   中英

为类型T的复杂对象创建自定义比较器

[英]Creating a custom comparer for a complex object of Type T

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

  • Tree的用户创建new Tree时,要求他们传入ITreeComparer类型,该用户知道T的类型,并且必须创建正确的Comparer。
  • Tree类创建TreeNode的所有实例,调用者仅将T类型的值添加到Tree类。 TreeNode是私有的,并且是Tree类的子类,因此我需要一种将IComparer传递给TreeNode因此我将ITreeComparer静态。
  • 我想将我的自定义ITreeComparer用于'T'的复杂类型,但我想让ITReeComparer从IComparer继承,这样,当类型T为基本类型(例如double和int)时,不需要Tree用户使用ITreeComparer

我的问题:

  1. 我希望有一种方法可以将ITreeComparer保留在Tree类中,即使Tree类可以处理自身的比较。 我将TreeNodeITreeComparision属性ITreeComparision静态,有什么比这更好的解决方案了?
  2. 我收到警告, TreeNode 定义了'=='或'!=',但没有覆盖'Object.Equals(object o)和'Object.GetHasCode' ,为什么在我想使用_comparer.Compare(x.Value, y.Value)时使用Object.Equals _comparer.Compare(x.Value, y.Value)_comparerTree必须提供的ITreeComparer实例创建者中传递的。

  3. 如果要使用ITreeComparer实现IComparer ,我是否只需检查_comparer is null并使用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> _comparerComparer<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

那说...

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

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

确实,由于TreeNode<T>类中的该字段为static ,因此Tree<T>的实例字段毫无意义,因为所有Tree<T>对象将使用相同的TreeNode<T>类型。

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

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

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

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

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

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

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

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


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

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

您的代码中存在一个大问题:

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

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

x是TreeNode,y是TreeNode,因此:

      if (x == null) return y == null;

x == null将最终调用operator ==方法,因此该方法将自行调用。

您必须这样做:

(object)x == null, 

只要您使用相同的类型,operator ==就会自我调用。 为此,这里是object.ReferenceEquals。

另外,您是否注意到代码不是null安全的? 它x!= null和y == null是什么? 然后y.Value将抛出异常:

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

尝试这样的事情:

public static bool operator == (TreeNode x, TreeNode y)
{
    var x_null = object.ReferenceEquals(x,null);
    var y_null = object.ReferenceEquals(y,null);
    if (x_null && y_null) return true;
    if (x_null || y_null) return false;

    //now is x.Value, y.Value is safe to call
    return _comparer.Compare(x.Value, y.Value) == 0;
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM