簡體   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