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

Time:11-26

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?

CodePudding user response:

@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;
}

CodePudding user response:

You could use Except, and then check if there is anything in the return value:

// first list
var list1 = new List<string>();
list1.Add("A");
list1.Add("B");
list1.Add("C");
list1.Add("D");

// second list
var list2 = new List<string>();
list2.Add("C");
list2.Add("D");

var list3 = list1.Except(list2);
var listIsIdentical = !list3.Any();
  • Related