>Background:
我使用 MVVM 编写了一个大型 WPF 应用程序,它一直存在一些间歇性问题。我最初在这里从代码问题中选择 ListBoxItem 时询问"已添加具有相同键的项目"异常,这解释了问题,但没有得到答案。
一段时间后,我设法找出了我得到的Exception
的原因,并将其记录在没有不可变字段的类中覆盖 Object.GetHashCode() 时返回的内容? 问题。基本上,这是因为我在公式中使用了可变字段来返回GetHashCode
的值。
从我收到的关于这个问题的非常有用的答案中,我设法加深了我对这一领域的理解。以下是三个相关规则:
- 如果 x 等于 y,则 x 的哈希码必须等于 y 的哈希码。等价地,如果 x 的哈希码不
- 等于 y 的哈希码,则 x 和 y 必须不相等。
- 当 x 在哈希表中时,x 的哈希代码必须保持稳定。
- 哈希函数应该在所有所有输入的整数。
这些规则影响了我对不知道从GetHashCode
方法返回什么的问题的可能解决方案:
- 我无法返回常量,因为这会破坏上面的第一条和第三条规则。
- 出于同样的原因,我无法为每个类创建一个额外的
readonly
字段,仅用于GetHashCode
方法。
我最终采用的解决方案是在编辑 GetHashCode
方法中使用的任何属性之前从其ObservableCollection
中删除每个项,然后再次重新添加它。虽然到目前为止这在许多视图中都工作正常,但我遇到了另一个问题,因为我的 UI 项是使用自定义Panel
进行动画处理的。当我重新添加项目(即使将其插入回集合中的原始索引)时,它会再次触发条目动画。
我已经添加了许多基类方法,例如 AddWithoutAnimation
、RemoveWithoutAnimation
,这有助于解决其中一些问题,但它不会影响任何Storyboard
动画,这些动画在重新添加后仍然会被触发。所以最后,我们来问这个问题:
问题:
首先,我想明确声明我没有在我的代码中使用任何Dictionary
对象......抛出Exception
的Dictionary
必须是ObservableCollection<T>
的内部。在我的最后一个问题中,大多数人似乎都忽略了这一点。因此,我不能选择简单地不使用Dictionary
......要是我可以就好了。
所以,我的问题是'有没有其他方法可以在可变类中实现GetHashCode
,同时不违反上述三个规则,或者首先避免实现它?
我收到了@HansPassant对上一个问题的评论,该评论表明
一个好的起点是完全删除 Equals 和 GetHashCode 覆盖,从 Object 继承的默认实现非常出色,并保证了对象的唯一性。
谁能告诉我如何删除Equals
和GetHashCode
覆盖?在 MSDN 的"IEquatable<T>
接口"页上,它说它应该为可能存储在泛型集合中的任何对象实现,然后在"IEquatable<T>.Equals
方法"页上它说如果实现Equals
,则还应重写 Object.Equals(Object)
和 GetHashCode
的基类实现,以便它们的行为与IEquatable<T>
的行为一致。
如果这是可能的,这将是我的首选解决方案。
<小时 />更新>>>
下载并安装 dotPeek 后,我已经能够查看实际发生Exception
的 PresentationFramework 命名空间内部。我已经找到了使用导致此问题的Dictionary
的确切部分。它在internal InternalSelectedItemsStorage
类构造函数中:
internal InternalSelectedItemsStorage(Selector.InternalSelectedItemsStorage collection, IEqualityComparer<ItemsControl.ItemInfo> equalityComparer = null)
{
this._equalityComparer = equalityComparer ?? collection._equalityComparer;
this._list = new List<ItemsControl.ItemInfo>((IEnumerable<ItemsControl.ItemInfo>) collection._list);
if (collection.UsesItemHashCodes)
this._set = new Dictionary<ItemsControl.ItemInfo, ItemsControl.ItemInfo>((IDictionary<ItemsControl.ItemInfo, ItemsControl.ItemInfo>) collection._set, this._equalityComparer);
this._resolvedCount = collection._resolvedCount;
this._unresolvedCount = collection._unresolvedCount;
}
在调用 ListBoxItem.OnSelected
方法后,Selector
类在内部使用它,所以我只能假设这与在Listbox
上进行选择有关。
谁能告诉我如何删除等于和 GetHashCode 覆盖?在 MSDN 的 IEquatable 接口页面上,它说它应该为可能存储在泛型集合中的任何对象实现,然后在 IEquatable.Equals 方法页面上,它说如果实现 Equals,则还应重写 Object.Equals(Object) 和 GetHashCode 的基类实现,以便它们的行为与 IEquatable 的行为一致。
可变对象通过其标识可比较,而不可变或按其值对象。
如果你有一个可变对象,你需要弄清楚它的身份(例如,如果它是存储在数据库中的实体的表示,则身份是身份的主键;如果它只是在内存中创建的"ad hoc"可变对象,那么它的身份就是这个对象的引用(即 Equals 和 GetHashCode 的默认实现))。
因此,如果你的对象不是一个实体,你只需实现IEquatable.Equals(T x) {返回this。等于(x);},即你说,是的,您可以将此类的对象与类 T 的对象进行比较,并通过引用(从 System.Object 继承的 Equals() 方法)对其进行比较。
如果你的对象是一个实体,例如有一个主键PersonId,那么你通过PersonId进行比较,并从你的GetHashCode()方法返回PersonId.GetHashCode()。
顺便说一句,对于实体,您通常使用一些OR映射器和Identity map模式,以确保在给定的工作单元中,给定实体的实例不超过一个实例,即每当主键相等时,对象引用也相等。