关于数据绑定集合中使用的可变对象的覆盖环 Equals 的持续混淆



>Background:

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

一段时间后,我设法找出了我得到的Exception的原因,并将其记录在没有不可变字段的类中覆盖 Object.GetHashCode() 时返回的内容? 问题。基本上,这是因为我在公式中使用了可变字段来返回GetHashCode的值。

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

    如果 x 等于 y,则 x 的哈希码必须等于 y 的哈希码。等价地,如果 x 的哈希码不
  1. 等于 y 的哈希码,则 x 和 y 必须不相等。
  2. 当 x 在哈希表中时,x 的哈希代码必须保持稳定。
  3. 哈希函数应该在所有所有输入的整数。

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

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

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

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

问题:

首先,我想明确声明我没有在我的代码中使用任何Dictionary对象......抛出ExceptionDictionary必须是ObservableCollection<T>的内部。在我的最后一个问题中,大多数人似乎都忽略了这一点。因此,我不能选择简单地不使用Dictionary......要是我可以就好了。

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

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

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

谁能告诉我如何删除EqualsGetHashCode覆盖?在 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模式,以确保在给定的工作单元中,给定实体的实例不超过一个实例,即每当主键相等时,对象引用也相等。

最新更新