(为长时间设置道歉。我在这里有一个问题,我保证。)
考虑具有在构造时分配的不可变唯一ID的类Node
。除了其他方面,此ID在持久化对象图时用于序列化。例如,当一个对象被反序列化时,它会通过ID对主对象图进行测试,以查找冲突并拒绝它。
此外,Node
仅由私有系统实例化,所有对它们的公共访问都是通过INode
接口完成的。
所以我们有类似这种常见模式的东西:
interface INode
{
NodeID ID { get; }
// ... other awesome stuff
}
class Node : INode
{
readonly NodeID _id = NodeID.CreateNew();
NodeID INode.ID { get { return _id; } }
// ... implement other awesome stuff
public override bool Equals(object obj)
{
var node = obj as INode;
return ReferenceEquals(node, null) ? false : _id.Equals(node.ID);
}
public override int GetHashCode()
{
return _id.GetHashCode();
}
}
我的问题围绕.NET的这些比较/与平等相关的特性:
IEquatable<T>
IComparable
/ IComparable<T>
operator ==
/ operator !=
最后是问题。实现像Node
这样的类时:
IEquatable<T>
或INode
延长了Node
?鉴于IEquatable<T>
似乎只是通过运行时类型检查来使用(主要是在BCL中),是否指向/不延伸INode
?IEquatable<NodeID>
也是Node
?obj as NodeID
?Equals
在C#工作了十多年之后,我很尴尬,没有彻底了解这些界面和与之相关的最佳实践。
(请注意,我们的.NET系统内置95%C#,5%C ++ / CLI,0%VB(如果它有所不同)。)
答案 0 :(得分:1)
您的Node.Equals
方法实际上只是NodeID.Equals
的包装器,因此您需要实现真正的比较逻辑(我假设它类似于myIntId == someOtherInt
)。否则,您将只使用从.Equals
继承的默认object
行为。
如果我认为你在最低级别的id只是一个int是正确的,你可以实现IComparable<NodeID>
和IEquatable<NodeID>
,它只是你的int id字段的包装器{{1}已经实现了Int32
和IComparable<int>
。
答案 1 :(得分:1)
我对它的理解也可能不完美,但这里有......
我从不做IEquatable,可能是我的错误,但我认为重写Equals(obj)已经足够好了,我可以根据需要在那里(或在私人帮助方法中)进行类型检查。老实说,我没有在IEquatable中看到这一点,除非你只是想明确与其他类型相等。
IComprable用于排序/排序类型操作,如果有一些业务逻辑围绕排序而无法通过覆盖GetHashCode捕获,则它非常有用。同样,大多数时候我会使用GetHashCode进行默认排序或使用linq OrderBy(),所以这里没有太多意义。
当我覆盖Equals时,我总是覆盖== /!=。它使行为更加一致。
答案 2 :(得分:1)
正如Ed所说,如果你通过NodeID比较相等而NodeID不是值类型,那么NodeID类也必须实现IEquatable<NodeID>
(仅因为你的语法为_id.Equals(node.ID)
。
也就是说,如果您在接口级别声明interface INode : IEquatable<INode>
,则仍需要在实例级别实际实现:例如对于节点:public bool Equals(INode other)
INode
,那么只需从派生类(节点)扩展IEquatable<Node>
。Joe Albhari对C# in a Nutshell
中的两个界面都有一个非常容易理解和详细的解释使用IEquatable<T>
的最重要原因是声明为您的类的使用者的合同,您保证非默认的相等实现。从框架的角度来看,您声明IEquatable<T>
,以便使用哈希算法的框架类可以根据您的Equals和GetHashCode的实现进行比较。例如,HashSet集合,以及LINQ扩展,例如&#39; Distinct&#39;。那说:
如果您不同时覆盖Equals()
和GetHashCode()
;
如果您的覆盖IEquatable
和Equals(object)
;
或者,如果您确实声明了,那么如果您实施GetHashCode()
,则可省略Equals(object)
;
答案 3 :(得分:1)
您实施了哪
IEquatable<T>
,IComparable
/IComparable<T>
,operator ==
/operator !=
?为什么呢?
我正如您所描述的那样实施IEquatable<T>
。由于字典将使用此接口,因此可以在许多隐藏位置使用,特别是如果涉及LInQ。它会比被覆盖的Equals(object)
更有效,因为类型安全和(对于值类型)缺乏拳击。
请注意,如果要修改类的相等语义,也必须覆盖GetHashCode
。
除非我的类型有某种数字解释,否则我通常不会覆盖operator ==。也就是说,== /!=几乎不是我在类上覆盖的唯一运算符。
您是从INode还是Node扩展IEquatable?鉴于IEquatable似乎只是通过运行时类型检查来使用(主要是在BCL中),是否指向/不从INode扩展?
如果我希望比较节点的相等性,我会将它添加到INode
。如果没有,那我就不会打扰了。但是,如果INode
的集合是您正在使用的集合,则可能是值得的。
对于那些你实现的人,你是在课堂上做的,还是在ID上另外做?
如果ID是一个简单类型,如整数,那么显然这是不必要的。如果它是复杂类型,那么关注点分离将要求在ID的类型上实现相等性。这就是我一直在思考的问题:什么设计可以让我以最少的努力做出最大的改变?如果你有一天决定改变复杂ID对象的格式,那么你是否想绕过代码修复所有会破坏的东西?或者您想将所有逻辑封装到ID类本身中。我当然会选择后者。 (这适用于ToString(),序列化代码以及其他所有内容。节点没有任何商业攻击其复杂ID类型的内部。)
例如,
IEquatable<NodeID>
是节点的一部分吗?
不,它不会是节点的一部分。 Node
将实施IEquatable<Node>
,NodeId
将实施IEquatable<NodeId>
。
您是否在Equals中测试obj为NodeID?
我不确定你在问什么,但我总是在object.Equals(object)
类上实现IEquatable<T>
覆盖,如下所示:
public override bool Equals(object obj)
{
return Equals(obj as Node);
}
public bool Equals(Node node)
{
if (node == null)
return false;
// ... do the type-specific comparison.
}