Search code examples
c#arraysobjectcompareequality

Comparing a collection of reference type objects for equality ignoring order of items in collection


I have the following sample classes

public class Item
{
    public string name { get; set; }
    public double price { get; set; }
}

public class Basket
{
    public Item[] items;
}

I then made two instances of Basket, both containing Items

var basket1 = new Basket()
{
   items = new Item[]
   {
       new Item() { name = "bread", price = 1.5 },
       new Item() { name = "butter", price = 2 }
   }
};

var basket2 = new Basket()
{
   items = new Item[]
   {
       new Item() { name = "butter", price = 2 },
       new Item() { name = "bread", price = 1.5 }
   }
};

I would like to compare Basket1 with Basket2, ignoring the order of items in the basket. This example should return True (they are equal) when compared. How should I proceed?


Solution

  • @Neil answer is correct, except that it won't work with reference types (string are an exception since they are immutable).

    Item is a class so it is a reference type.

    Except uses the default equality comparer to compare the elements. Since Item is a class, it will be compared by reference, which is not the desired solution. So we need to bypass the default comparison, with a custom equality comparer. An overload of Except exists for that purpose.

    You will need to create a type that implements IEqualityComparer<Item> and pass an instance of that type to Except.

    See: Except overload documentation and IEqualityComparer documentation

    Here is an example that you can run in Linqpad. It uses both Except overloads. One return false, the other true:

    void Main()
    {
        var basket1 = new Basket()
        {
            items = new Item[]
       {
           new Item() { name = "bread", price = 1.5 },
           new Item() { name = "butter", price = 2 }
       }
        };
    
        var basket2 = new Basket()
        {
            items = new Item[]
           {
           new Item() { name = "butter", price = 2 },
           new Item() { name = "bread", price = 1.5 }
           }
        };
        
        var isIdenticalByReference = (!(basket1.items.Except(basket2.items).Any())); // false
        isIdenticalByReference.Dump();
        
        var isIdenticalWithCustomEqualityComparer = (!(basket1.items.Except(basket2.items, new ItemEqualityComparer()).Any())); // true
        isIdenticalWithCustomEqualityComparer.Dump();
    }
    
    // You can define other methods, fields, classes and namespaces here
    
    public class Item
    {
        public string name { get; set; }
        public double price { get; set; }
    
        public int GetHashCode(object obj)
        {
            return (name?.GetHashCode() ?? 0) ^ price.GetHashCode();
        }
    }
    
    public class ItemEqualityComparer : IEqualityComparer<Item>
    {
        public bool Equals(Item I1, Item I2)
        {
            if (I2 == null && I1 == null)
                return true;
            else if (I1 == null || I2 == null)
                return false;
            else return I1.name == I2.name && I1.price == I2.price;
        }
    
        public int GetHashCode(Item item)
        {
            return (item.name?.GetHashCode() ?? 0) ^ item.price.GetHashCode();
        }
    }
    
    public class Basket
    {
        public Item[] items;
    }