我正在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
,只能有一种比较)。例如,这意味着,如果您有一个具有属性Name
和Id
的类,则只能有按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的处理都是错误的。另一篇文章指出了一些问题。另一个问题是,如果ITreeComparer
和x
都为null,则您的比较运算符都将返回y
。即根据代码,如果true
和x
都为空,则它们彼此同时大于和小于。
幸运的是,确实没有任何明显的理由使比较完全需要处理空值。按照惯例,原始实现(由用户提供)必须容纳空值。您自己的代码不需要检查它们。此外,根本没有明显的原因导致null值出现在树中。空节点引用没有多大意义:每当您在树中达到空值时,即要在其中存储节点。无需确定新节点是在该null的左侧还是右侧……它在当时的null位置正确!
关于c# - 为类型T的复杂对象创建自定义比较器,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/33359367/