4

背景:

我已经使用 MVVM 编写了一个大型 WPF 应用程序,但它一直遇到一些间歇性问题。我最初询问“已添加具有相同键的项目”从代码问题中选择 ListBoxItem 的异常,这解释了问题,但没有得到答案。

一段时间后,我设法找出了Exception我得到的 s 的原因,并将其记录在What to return when overriding Object.GetHashCode() in classes with no immutable fields? 问题。基本上,这是因为我在公式中使用了可变字段来返回GetHashCode.

从我收到的关于该问题的非常有用的答案中,我设法加深了对该领域的理解。以下是三个相关规则:

  1. 如果 x 等于 y,则 x 的哈希码必须等于 y 的哈希码。等效地,如果 x 的哈希码不等于 y 的哈希码,则 x 和 y 必须不相等。
  2. 当 x 在哈希表中时,x 的哈希码必须保持稳定。
  3. 散列函数应该在所有输入的所有整数中生成随机分布。

这些规则影响了我不知道从该GetHashCode方法返回什么的问题的可能解决方案:

  • 我不能返回一个常量,因为这会破坏上面的第一条和第三条规则。
  • 我不能为每个类创建一个额外的字段,出于同样的原因readonly,只能在方法中使用。GetHashCode

我最终采用的解决方案是ObservableCollection在编辑方法中使用的任何属性之前从其中删除每个项目GetHashCode,然后再重新添加它。虽然到目前为止这在许多视图中都可以正常工作,但我遇到了进一步的问题,因为我的 UI 项目是使用 custom 动画Panel的。当我重新添加一个项目时(即使将它插入回集合中的原始索引),它也会再次触发条目动画。

我已经添加了一些基类方法,例如AddWithoutAnimation, RemoveWithoutAnimation,这有助于解决其中一些问题,但它不会影响任何Storyboard动画,重新添加后仍然会触发。所以最后,我们来到了这个问题:

问题:

首先,我想明确说明我没有在我的代码中使用任何Dictionary对象...... 在我上一个问题中,大多数人似乎都忽略了这一点。因此,如果可以的话,我不能选择简单地不使用...。DictionaryExceptionObservableCollection<T>Dictionary

所以,我的问题是'有没有其他方法可以GetHashCode在可变类中实现而不违反上述三个规则,或者避免首先实现它?'

我收到了@HansPassant 对上一个问题的评论,该评论表明

一个好的起点是完全删除 Equals 和 GetHashCode 覆盖,从 Object 继承的默认实现非常好,并保证了对象的唯一性。

谁能告诉我如何删除EqualsGetHashCode覆盖?在 MSDN 上的IEquatable<T>Interface页面上,它说它应该为可能存储在通用集合中的任何对象实现,然后在IEquatable<T>.EqualsMethod页面上它说If you implement Equals,您还应该覆盖 and 的基类实现,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;
  }

Selector这在调用方法后由类在内部使用ListBoxItem.OnSelected,所以我只能假设这与在Listbox.

4

1 回答 1

0

谁能告诉我如何删除 Equals 和 GetHashCode 覆盖?在 MSDN 上的 IEquatable 接口页面上,它说应该为任何可能存储在通用集合中的对象实现它,然后在 IEquatable.Equals 方法页面上它说如果你实现 Equals,你还应该覆盖 Object 的基类实现.Equals(Object) 和 GetHashCode 使它们的行为与 IEquatable 的行为一致。

可变对象通过它们的身份进行比较,而不可变对象或值对象通过它们的来比较。

如果您有一个可变对象,则需要确定其身份(例如,如果它是存储在数据库中的实体的表示,则身份是身份的主键;如果它只是在内存,那么它的标识就是这个对象的引用(即 Equals 和 GetHashCode 的默认实现)。

因此,如果您的对象不是实体,您只需实现 IEquatable.Equals(T x) { return this.Equals(x); },即你说,是的,你可以将此类的对象与 T 类的对象进行比较,然后通过引用进行比较(从 System.Object 继承的 Equals() 方法)。

如果您的对象是一个实体并且例如具有主键 PersonId,那么您可以通过 PersonId 进行比较并从您的 GetHashCode() 方法返回 PersonId.GetHashCode()。

顺便说一句,在实体的情况下,您通常使用一些 OR 映射器和身份映射模式,以确保在给定的工作单元内,您没有给定实体的多个实例,即只要主键相等,对象引用就相等也。

于 2013-12-16T20:07:40.760 回答