Background:
I've written a large scale WPF application using MVVM and it's been suffering from some intermittent problems. I initially asked the 'An item with the same key has already been added' Exception on selecting a ListBoxItem from code question here which explains the problem, but got no answers.
After some time, I managed to work out the cause of the Exception
s that I was getting and documented it in the What to return when overriding Object.GetHashCode() in classes with no immutable fields? question. Basically, it was because I had used mutable fields in the formula to return a value for GetHashCode
.
From the very useful answers that I received for that question, I managed to deepen my understanding in that area. Here are three relevant rules:
- If x equals y then the hash code of x must equal the hash code of y. Equivalently, if the hash code of x does not equal the hash code of y, then x and y must be unequal.
- The hash code of x must remain stable while x is in a hash table.
- The hash function should generate a random distribution among all integers for all inputs.
These rules affected the possible solutions that I had to my problem of not knowing what to return from the GetHashCode
method:
- I couldn't return a constant because that would break the first and third rules above.
- I couldn't create an additional
readonly
field for each class, solely to be used in theGetHashCode
method for the same reasons.
The solution that I eventually went with was to remove each item from its ObservableCollection
before editing any of the properties used in the GetHashCode
method and then to re-add it again afterwards. While this has worked Ok in a number of views so far, I've run into a further problem as my UI items are animated using custom Panel
s. When I re-add an item (even by inserting it back to its original index in the collection), it sets off the entry animation(s) again.
I had already added a number of base class methods such as AddWithoutAnimation
, RemoveWithoutAnimation
, which has helped fix some of these issues, but it doesn't affect any Storyboard
animations, which still get triggered after re-adding. So finally, we come to the question:
Question:
First, I'd like to clearly state that I am not using any Dictionary
objects in my code... the Dictionary
that throws the Exception
must be internal to an ObservableCollection<T>
. This point seems to have been missed by most people in my last question. Therefore, I cannot chose to simply not use a Dictionary
... if only I could.
So, my question is 'is there any other way that I can implement GetHashCode
in mutable classes while not breaking the three rules above, or avoid implementing it in the first place?'
I received a comment on the previous question from @HansPassant that suggested that
A good starting point is to completely remove the Equals and GetHashCode overrides, the default implementations inherited from Object are excellent and guarantee object uniqueness.
Can anyone tell me how can I remove the Equals
and GetHashCode
overrides? On the IEquatable<T>
Interface page on MSDN it says It should be implemented for any object that might be stored in a generic collection and then on the IEquatable<T>.Equals
Method page it says If you implement Equals
, you should also override the base class implementations of Object.Equals(Object)
and GetHashCode
so that their behaviour is consistent with that of the IEquatable<T>
.
If this is possible, it would be my preferred solution.
UPDATE >>>
After downloading and installing dotPeek, I have been able to look inside the PresentationFramework namespace where the Exception
is actually occurring. I have found the exact part that uses the Dictionary
that is causing this problem. It is in the internal InternalSelectedItemsStorage
class constructor:
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;
}
This is used internally by the Selector
class after the ListBoxItem.OnSelected
method has been called, so I can only assume that this has something to do with when a selection is made on the Listbox
.