How to implement IEquatable<T> when mutable fields are part of the equality - Problem with GetHashCode
Asked Answered
A

2

1

I am using Entity Framework in my application.

I implemented with the partial class of an entity the IEquatable<T> interface:

Partial Class Address : Implements IEquatable(Of Address) 'Other part generated
  Public Overloads Function Equals(ByVal other As Address) As Boolean _
      Implements System.IEquatable(Of Address).Equals
    If ReferenceEquals(Me, other) Then Return True
    Return AddressId = other.AddressId
  End Function

  Public Overrides Function Equals(ByVal obj As Object) As Boolean
    If obj Is Nothing Then Return MyBase.Equals(obj)
    If TypeOf obj Is Address Then 
      Return Equals(DirectCast(obj, Address)) 
  Else
    Return False
  End Function

  Public Overrides Function GetHashCode() As Integer
    Return AddressId.GetHashCode
  End Function
End Class

Now in my code I use it this way:

Sub Main()
  Using e As New CompleteKitchenEntities
    Dim job = e.Job.FirstOrDefault
    Dim address As New Address()

    job.Addresses.Add(address)
    Dim contains1 = job.Addresses.Contains(address) 'True
    e.SaveChanges()
    Dim contains2 = job.Addresses.Contains(address) 'False

    'The problem is that I can't remove it:
    Dim removed = job.Addresses.Remoeve(address) 'False

  End Using
End Sub

Note (I checked in the debugger visualizer) that the EntityCollection class stores its entities in HashSet so it has to do with the GetHashCode function, I want it to depend on the ID so entities are compared by their IDs.

The problem is that when I hit save, the ID changes from 0 to its db value. So the question is how can I have an equatable object, being properly hashed.

Please help me find what's wrong in the GetHashCode function (by ID) and what can I change to make it work.

Thanks a lot.

Administer answered 13/4, 2010 at 3:45 Comment(0)
E
1

You've used a mutable field (AddressId) as part of the hash - that is unfortunately doomed. By which I mean: when you add it, the AddressId is 0? -1? it doesn't matter what exactly, but it isn't the final id - and it is stored with this key / hash. When you save it, the actual id (the IDENTITY column from the database) is updated into the object.

Quite simply, you cannot reliably hash against this value if it can change when it is part of a dictionary. One possible workaround would be to consider the unit-of-work, i.e. an insert is a unit-of-work. Meaning: if the data etc only lives as long as this, then it is a non-issue as you will never attempt to access the data after saving it. Subsequently (in a different context) loading the data should be fine too, as the id doesn't then change during the lifetime.

Alternatively: drop this hash/equality.

Embraceor answered 13/4, 2010 at 5:10 Comment(6)
I started it anew, sorry. Anyway, my aim is the equality, I wish I could skip the GetHashCode implementation part. And yes, the initial value is 0. Anyway I use EF so all the objects are initialized with ID as 0 and then the properties are individually set one by one, not by the initializer, meaning that the ID field changes while entity has already been hashed, maybe you'd know how to solve it and enjoy both proper hashing along with equality on this mutable object.Administer
I think the title of this question should rather be "How to apply equality on mutable fields"Administer
@Shimmy - in a hash? You don't. In some cases you could memoize the hash the first time it is fetched, but you can't do that here without breaking Equals.Embraceor
The class EntityCollection<TEntity> uses an internal HashSet<TEntity> so when you add a new entity to the set, it's ID is 0, till you call SaveChanges which is when it reloads the ID, then when I want to check if the collection contains the newly added entity it doesn't find it. Any workaround? I didn't understand what is the certain answer: is it possible to have equality availability in a mutable field of a hashed value? Any alternatives? I think the design pattern of Equality against GetHashCode sucks, they're intended to provide 2 functionalities while in fact they're limiting each otherAdminister
I tried to remove the overriden GetHashCode, and everything works fine now, since in HashTables or sets, I must not rely on mutable fields, so it always returns the regular GetHashCode, which doesn't apply to Arrays, Lists, Collections etc. where the Equals functions is used to determine the equality when calling contains etc.Administer
The question is: What are the concequences (if there are) of making the GetHashCode to return a different equality pattern than the Equals function.Administer
F
1

Non-sealed classes should not implement IEquatable<T>, because the only way to ensure that a derived class which overrides Object.Equals() and Object.GetHashCode() will implement IEquatable<BaseType> in a fashion consistent with Object.GetHashCode() is for the interface implementation to call the virtual Object.Equals(Object) method. Since the only purpose of IEquatable<T> is to avoid the overhead of calling Object.Equals(Object), and a safe implementation of IEquatable<T> on an unsealed class cannot avoid calling it, such an implementation would serve no purpose.

Also, I would strongly counsel against any override of Object.Equals (or implementation of IEquatable<T>) for any mutable class type. It is good for structs, whether mutable or not, to override Object.Equals and Object.GetHashCode, and to implement IEquatable<theirOwnType>, since the fields of a struct stored as e.g. a Dictionary key will be immutable even if the struct type exposes mutable public fields.

Fernandez answered 27/8, 2012 at 17:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.