我正在用C#创建一个Tree结构,我有一些特定的目标:
Tree
的用户创建new Tree
时,他们需要传入
类型ITreeComparer
,用户知道T
的类型并且必须
创建正确的Comparer。Tree
类创建TreeNode
的所有实例,调用者只将T
类型的值添加到Tree
类。 TreeNode
是私有的Tree
类的子类所以我需要一种方法将IComparer传递给TreeNode
所以我使ITreeComparer
静态。ITreeComparer
用于复杂类型的'T',但我希望ITReeComparer继承自IComparer,以便Tree的用户不需要传递ITreeComparer
时类型T是基元,如double和int。我的问题:
Tree
类中
即使Tree
类处理自身的比较。我做了
ITreeComparision
静态TreeNode
属性,更好
解决方案比这个?我收到警告TreeNode
定义'=='或'!='但是
不会覆盖'Object.Equals(对象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;
}
}
}
答案 0 :(得分:4)
正如我在上面的评论中提到的,我不明白为什么你有这个界面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<T>
知道如何比较自己。
- 我收到一个警告,TreeNode定义'=='或'!='但不覆盖'Object.Equals(object o)和'Object.GetHasCode',为什么我会在我想要时使用Object.Equals使用_comparer.Compare(x.Value,y.Value),_comparer是在Tree必须提供的ITreeComparer实例创建者中传递的。
醇>
无论好坏,有几种不同的方法让对象在C#中进行比较。任何时候这些方法都没有相同的实现,你正在为自己设置一些真正令人头疼的错误。
因此,当您重载与等式相关的运算符==
和!=
,而不是Equals()
方法时,会收到警告。同样,实现自定义相等关系而不修复GetHashCode()
以正确反映这些平等关系是获得非常难以修复的错误的好方法。
非常重要的是,如果您开始在类型本身内实现相等操作,以确保您通过并在该类型中进行一致的所有与相等的操作。
- 如果ITreeComparer要实现IComparer,我只是检查我的_comparer是否为null,如果使用IComparer默认比较,那么Tree的用户不必传入ITreeComparison implmentation?
醇>
正如我所提到的,我认为你应该完全放弃ITreeComparer
。没有实际的“沙盒”......实际上,用户可以根据需要使用完全相同的方法在单个类中实现两个接口。强制用户在内置接口足够的时候实现自定义接口会让事情变得更加烦人。
只要我写作,我就会同意the non-answer provided by ipavlu确实提供了一些有用的观察。特别是,您对空值的处理都是错误的。另一篇文章指出了一些问题。另一个问题是如果x
和y
都为null,则比较运算符都返回true
。即根据代码,x
和y
同时大于和小于彼此,如果它们都为空。
幸运的是,没有明显的理由比较应该根本需要处理空值。原始实现(由用户提供)按照惯例要求已经容纳空值。您自己的代码不需要检查它们。此外,树中根本没有出现空值的明显原因。空节点引用没有多大意义:只要你在树中找到空值,那就是你要存储节点的地方;没有必要决定新节点是在那个空的左边还是右边...它正好在那个时候空的地方!
答案 1 :(得分:1)
您的代码存在很大问题:
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,
只要您使用相同的类型,运算符==就会调用自身。 为了使它干净,这里是object.ReferenceEquals。
另外,您是否注意到您的代码不安全? 它是什么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;
}