[英]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
。 我的问题:
Tree
类中,即使Tree
类可以处理自身的比较。 我将TreeNode
的ITreeComparision
属性ITreeComparision
静态,有什么比这更好的解决方案了? 我收到警告, TreeNode
定义了'=='或'!=',但没有覆盖'Object.Equals(object o)和'Object.GetHasCode' ,为什么在我想使用_comparer.Compare(x.Value, y.Value)
时使用Object.Equals _comparer.Compare(x.Value, y.Value)
, _comparer
是Tree
必须提供的ITreeComparer
实例创建者中传递的。
如果要使用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> _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
排序的树,但不能同时具有这两种树。
确实,由于TreeNode<T>
类中的该字段为static
,因此Tree<T>
的实例字段毫无意义,因为所有Tree<T>
对象将使用相同的TreeNode<T>
类型。
在我看来,甚至将ITreeComparer
公开给TreeNode
的唯一原因是为了允许重载比较运算符。 如果必须使用这些运算符,我将继续使_comparer
字段在TreeNode<T>
为非静态,或者甚至将其更改为对父Tree<T>
对象的引用(然后它可以从_comparer
获取家长)。
但实际上,我觉得比较运算符没有太大帮助。 您也可以直接在Tree<T>
类中直接调用_comparer
,而不必担心TreeNode<T>
甚至不知道如何进行比较。
- 我收到警告,指出TreeNode定义了'=='或'!='但未覆盖'Object.Equals(object o)和'Object.GetHasCode',为什么当我想使用_comparer时为什么要使用Object.Equals .Compare(x.Value,y.Value),_ comparer是Tree必须提供的ITreeComparer实例创建者中传递的。
不管是好是坏,对象在C#中有几种不同的比较方式。 每当这些方法的实现方式不同时,您就需要为一些真正的抓头bug做好准备。
因此,当您重载与相等性相关的运算符==
和!=
而不是Equals()
方法时,会收到警告。 同样,实现自定义的相等关系而不修复GetHashCode()
来正确反映那些相等关系,这是获取很难修复的错误的好方法。
如果您开始在类型本身中实现相等操作,那么确保您通过该类型并进行一致的所有与相等相关的操作确实非常重要。
- 如果要使用ITreeComparer实现IComparer,我是否只需检查_comparer是否为null并使用IComparer默认比较,那么作为Tree的用户,则不必传递ITreeComparison实现吗?
如前所述,我认为您应该完全删除ITreeComparer
。 没有实际的“沙箱操作”……实际上,如果需要,用户可以使用一个完全相同的方法,使单个类实现两个接口。 当内置的界面足以满足要求时,强迫用户实施自定义界面只会使他们感到烦恼。
只要我在写,我都会同意ipavlu提供的非答案确实提供了一些有用的观察。 特别是,您对null的处理都是错误的。 另一篇文章指出了一些问题。 另一个问题是,如果x
和y
均为null,则比较运算符均返回true
。 即,根据代码,如果x
和y
均为空,则它们彼此同时大于和小于。
幸运的是,确实没有任何明显的理由使比较完全需要处理空值。 按照惯例,原始实现(由用户提供)必须容纳空值。 您自己的代码不需要检查它们。 此外,根本没有明显的原因导致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.