Search code examples
c#listcompareiequalitycomparer

UnorderedEquals method to compare 2 lists fails on Boolean values


I use an UnorderedEquals extension to compare 2 lists. It works well, except where the only difference is a Boolean value. Here is the compare method:

    public static bool UnorderedEquals<T>( this IEnumerable<T> list1, IEnumerable<T> list2, IEqualityComparer<T> comparer )
    {
        var d = new Dictionary<T, int>( comparer );
        foreach( T s in list1 )
        {
            if( d.ContainsKey( s ) )
            {
                d[s]++;
            }
            else
            {
                d.Add( s, 1 );
            }
        }
        foreach( T s in list2 )
        {
            if( d.ContainsKey( s ) )
            {
                d[s]--;
            }
            else
            {
                return false;
            }
        }
        return d.Values.All( c => c == 0 );
    }

Here is my Class and IEqualityComparer:

    public class SelectedEntities
    {
        public int Id { get; set; }
        public bool IsDelegator { get; set; }
        public bool IsDelegate { get; set; }
    }

    public class SelectedEntitiesEqualityComparer : IEqualityComparer<SelectedEntities>
    {
        public bool Equals( SelectedEntities x, SelectedEntities y )
        {
            if( object.ReferenceEquals( x, y ) )
                return true;
            if( x == null || y == null )
                return false;
            return x.Id.Equals( y.Id );
        }

        public int GetHashCode( SelectedEntities obj )
        {
            return obj.Id.GetHashCode( );
        }
    }

Using this code, I should be able to get a false value from running the following:

    private bool CompareLists( )
    {
        bool result = false;

        var list1 = new List<SelectedEntities>( );
        var list2 = new List<SelectedEntities>( );

        list1.Add( new SelectedEntities { Id = 1, IsDelegator = false, IsDelegate = true } );
        list1.Add( new SelectedEntities { Id = 2, IsDelegator = false, IsDelegate = true } );

        list2.Add( new SelectedEntities { Id = 1, IsDelegator = false, IsDelegate = true } );
        list2.Add( new SelectedEntities { Id = 2, IsDelegator = false, IsDelegate = false } ); // this is different

        result = list1.UnorderedEquals( list2, new SelectedEntitiesEqualityComparer( ) );

        return result;
    }

If I change any of the Boolean values to be different between the two list, it always returns true. Bizarre.


Solution

  • Your comparer does not take into account all the fields. Currently you only compare Id. What about IsDelegator and IsDelegate?

    You should add them:

    public class SelectedEntitiesEqualityComparer : IEqualityComparer<SelectedEntities>
    {
        public bool Equals( SelectedEntities x, SelectedEntities y )
        {
            if( object.ReferenceEquals( x, y ) )
                return true;
            if( x == null || y == null )
                return false;
            return x.Id.Equals( y.Id ) && 
                   x.IsDelegator.Equals(y.IsDelegator) &&
                   x.IsDelegate.Equals(y.IsDelegate);
        }
    
        public int GetHashCode( SelectedEntities obj )
        {
            return obj.Id.GetHashCode( )^
                   obj.IsDelegator.GetHashCode()^
                   obj.IsDelegate.GetHashCode();
        }
    }
    

    BTW this implementation of GetHashCode might not be optimal. For good practices regarding this look at What is the best way to implement this composite GetHashCode()


    Note Your implementation of UnorderedEquals can be shortened to this:

    return list1.Count() == list2.Count() &&
           list1.All(e=> list2.Contains(e, comparer));