2013-08-06 32 views
1

我有两个对象是这样的:相等,但不能互换

class Container { 
    public HashSet<Item> Items { get; } 
} 

class Item { 
    public Container Parent { get; set; } 
    public string Value1 { get; set; } 
    public int Value2 { get; set; } 
} 

每个Item实例必须属于Container实例,并且在两者之间的一个一对多的关系,始终是两端保持同步。

我现在面临实施一种方法,比较Item的两个实例,看他们的Value1Value2值是否匹配。方法将而不是考虑到Parent值,因为我比较的每对实例在这个值上肯定不同,所以这样做会使得我实现的方法无用,因为它将具有与object.ReferenceEquals相同的结果(false)方法。

我的问题如下。我是否应该将此方法作为对象的public override bool Equals(object obj)方法(以及GetHashCode)来实现?或者它忽略了它的属性排除了我这样做的事实?为什么或者为什么不?我的另一个想法是将其实施为public bool EqualsIgnoreParent(Item other),它不会覆盖任何东西;然后我可以从自定义比较器中调用它。

+2

当你将一个可变对象放在一个'HashSet'中时,它会产生一种混乱,因为正确操作哈希代码一旦被添加到该集合后就不能改变。 –

+0

我更喜欢为可变对象实现一个'IEqualityComparer'而不是覆盖'Equals'。 – CodesInChaos

+0

@CodesInChaos是的,这就是我所做的。请参阅下面的答案。 – HappyNomad

回答

1

感谢mikez's comment和埃里克利珀的blog post,我发现我不应该重写ItemEqualsGetHashCode方法,因为Item的实例是可变的,并添加到HashSet

继建议here,我决定平等的两个概念分开。

  1. 身份。我没有覆盖ItemEqualsGetHashCode方法。我留下了这些方法,因为他们在object。这就是HashSet将使用的。
  2. 等效。我创建了一个名为ValueComparer的公共单身人员课程,它嵌套在Item之内并实施IEqualityComparer<Item>。这是我在我的问题中描述的比较逻辑。
+0

“IEqualityComparer ”的一个好处是'HashSet ','Dictionary '等有构造函数重载,允许您使用它们。 – CodesInChaos

+0

我更喜欢直接从'EqualityComparer '派生而不是直接执行'IEqualityComparer '。 – CodesInChaos

+0

@CodesInChaos你在'EqualityComparer '中找到什么优势?也许如果你阅读[这里](http://stackoverflow.com/q/5707347/122781)和[这里](http://stackoverflow.com/a/340526/122781),那么你会改变主意。 – HappyNomad

0

我想你想实现IEquatable。所以我建议你用Euqals(Item other)重载Euqals(object other),同时通过调用Equals(Item other)覆盖Equals(object other)。

当然

,使的GetHashCode返回-1总是迫使平等的比较可以通过Equals.here做是为什么需要重写的GetHashCode方法的解释 - Why is it important to override GetHashCode when Equals method is overridden?

0

是的,你可以与equals实现这一点, GetHashCode,只要Value1和Value2不变或您的散列与任何变化独立。 (否则使用的hashCode集将无法正常工作)

此外,您可以在项目级减少到基本成员

class Item { 
    public string Value1 { get; set; } 
    public int Value2 { get; set; } 
} 

和维护额外的词典的关系:(这将是有效的,如果你正确地贯彻执行的GetHashCode)

Dictionary<Item, Container> ContainersOfItems; 
+0

关于你的第二个想法,我看不到用静态字典替代'Parent'属性作为一种改进。我认为是相反的;它会使代码复杂化而没有实际的好处。 – HappyNomad

+0

由于冗余,维护关系两端的列表已使代码复杂化;)好处是, –

+0

...不可用字段不会对任何相等逻辑产生影响。从而获得安全性,失去灵活性,简单性和可读性。与静态类型语言一样折衷。 你当然可以隐藏父属性后面的静态字典。 –

-1

不这样做,除非你是100%肯定的是,父成员从来没有什么差别。可能有许多用例你没有想到,(如果你需要将2个容器中的项目放在单个字典中,那么我会推荐坚持设计。如果根据定义,平等仅由其他成员决定,那就去做吧。否则,不。
您以后可以随时编写特定于案例的相等逻辑。请记住,Dictionary,List,HashSet和其他收集允许您定义自定义IEqualityComparer
还有另一种选择,但要非常小心。重写这两种方法,但使GetHashCodeEquals更容忍,这意味着,使不相等的对象具有相同的哈希码,而不是相反。使GetHashCode忽略父成员,但不是Equals。它不会损害任何内容,然后如果您想忽略字典中的父成员,请编写一个IEqualityComparer,它们将以与GetHashCode相同的方式进行比较。

+1

'一个选项是只重写GetHashCode'你应该总是重写equals和gethashcode或者都不重写,从来就不是一个。它们都具有相同的语义含义是很重要的。 – Servy

+0

正如我看到的字典实现,'GetHashCode'用于计算字典中的起始搜索点。从这一点起,字典通过调用“Equals”来搜索该项目。它不需要哈希码是唯一的,因为最终的相等性检查仅由“Equals”进行。 – RoadBump

+0

当然不是,但是'Equals'和'GetHashCode'基于相同的相等概念是很重要的。如果两个对象相等,但是具有不同的哈希码,因为“Equals”方法使用值相等性,并且“GetHashCode”基于引用相等性(反之亦然),则字典出现故障;它突然变得可能添加多个相同的密钥,或者它会说一个项目不存在时,等等。 – Servy

0

我建议,这可能是更有意义拆分项目,所以它看起来是这样的:

class Container<TContent> { 
    public HashSet<Item<TContent>> Items { get; } 
} 

class Item<TContent> { 
    public Container Parent; 
    public TContent Content; 
} 

如果你这样做,那么你可以定义一个类来保存您的内容具有适当的Equals方法对于这种类型,并初始化您的HashSetIEqualityComparer<Item<TConcent>>其基础上等于每个Item<TContent>Content字段。

+0

我刚完成实施解决方案。修改对象模型会受到严重影响,而且没有必要。 – HappyNomad