just.another.programmer
just.another.programmer

Reputation: 8785

IEnumerable.Equals seems to call the wrong Equals method

I implemented a PagedModel class to wrap around IEnumerable to provide paging data for grids in my MVC app. I used Resharper's auto-generated Equality code telling it to check the data, total rows, page number, and page size fields. Here's the class code:

Public Class PagedModel(Of T)
    Public Property PageSize As Integer
    Public Property PageNumber As Integer
    Public Property ModelData As IEnumerable(Of T)
    Public Property TotalRecords As Integer

    Public Overloads Function Equals(ByVal other As PagedModel(Of T)) As Boolean
        If ReferenceEquals(Nothing, other) Then Return False
        If ReferenceEquals(Me, other) Then Return True
        Return other._PageSize = _PageSize AndAlso other._PageNumber = _PageNumber AndAlso Equals(other._ModelData, _ModelData) AndAlso other._TotalRecords = _TotalRecords
    End Function

    Public Overloads Overrides Function Equals(ByVal obj As Object) As Boolean
        If ReferenceEquals(Nothing, obj) Then Return False
        If ReferenceEquals(Me, obj) Then Return True
        If Not Equals(obj.GetType(), GetType(PagedModel(Of T))) Then Return False
        Return Equals(DirectCast(obj, PagedModel(Of T)))
    End Function

    Public Overrides Function GetHashCode() As Integer
        Dim hashCode As Long = _PageSize
        hashCode = CInt((hashCode * 397) Xor _PageNumber Mod Integer.MaxValue)
        If _ModelData IsNot Nothing Then hashCode = CInt(((hashCode * 397) Xor _ModelData.GetHashCode()) Mod Integer.MaxValue)
        hashCode = CInt((hashCode * 397) Xor _TotalRecords Mod Integer.MaxValue)
        Return CInt(hashCode Mod Integer.MaxValue)
    End Function
End Class

I found the call to Equals(other._ModelData, _ModelData) peculiar, as AFAIK, this checks that it is the same object rather than that the contained items are the same. Because my tests were failing anyways, I went ahead and changed it to other._ModelData.Equals(_ModelData) with no success. Then I reflected into it at debug time and found that other._ModelData.GetType().GetMethod("Equals",{GetType(Object)}).DeclaringType was Object! Obviously, that would result in the failed comparison.

I came up with a solution to create a EnumerableEquals method which compares every item in the two enumerables to confirm the are the same, but it seems sloppy. Is there anything I can do to use the normal .Equals method?

Private Function EnumerableAreEqual(ByVal a As IEnumerable(Of T), ByVal b As IEnumerable(Of T)) As Boolean
    b = b.ToList() 'avoid multiple query execution
    Return a.All(Function(item) b.Contains(item))
End Function

Upvotes: 5

Views: 838

Answers (3)

supercat
supercat

Reputation: 81179

In general, for any two arbitrary storage locations X and Y, the value of X.Equals(Y) should never change unless X or Y is written. While it would be possible for an immutable collection type to override Equals to test sequence equality, mutable class types cannot sensibly do anything with Equals(Object) other than test for reference identity (which is the default behavior).

Upvotes: 0

carlosfigueira
carlosfigueira

Reputation: 87238

You can't really use the "normal" Equals method, since it's not defined for enumerables (i.e., it doesn't do element comparison). What you have is perfectly fine (*), but if you want to use the Equals syntax, you can consider using the what @StriplingWarrior suggested.

(*) Your implementation doesn't really check whether both are equal. If 'a' has more elements than 'b', it will return True; also, if 'a' has the same elements as 'b', but in a different order, it will also return True. If that's ok for your scenario, then it's fine.

Upvotes: 0

StriplingWarrior
StriplingWarrior

Reputation: 156544

You probably want to use SequenceEqual.

(new[]{1,2,3}).SequenceEqual(new[]{1,2,3}) // True
(new[]{1,2,3}).SequenceEqual(new[]{3,2,1}) // False
(new[]{1,2,3}).SequenceEqual(new[]{1,2})  // False
(new[]{1,2}).SequenceEqual(new[]{1,2,3})  // False

This will ensure that both IEnumerables have the same elements in the same order.

Upvotes: 5

Related Questions