Mastering complex objects comparison using SequenceEqual on C#

One of the most useful features of .NET is LINQ (Language-Integrated Query), a component that provides a set of standard query operators for querying data sources in C#.

Among these operators, there is SequenceEqual, which allows us to compare two sequences for equality. In this article, we will explore how SequenceEqual works and how it can be used to compare enumerable.

Understanding

The SequenceEqual operator is a function in LINQ that compares two sequences for equality. It returns true if the two sequences have the same elements in the same order. Otherwise, it returns false.

The function takes two arguments: the first is the sequence to compare, and the second is the sequence to compare it to.

The sequences can be of any type that implements the IEnumerable interface, such as arrays, lists, or even LINQ query results.

Here is a quick example of using SequenceEqual with classic types:

int[] array1 = { 1, 2, 3 }; 
int[] array2 = { 1, 2, 3 }; 

bool result = array1.SequenceEqual(array2); // result: true

SequenceEqual not only compares whether each element of a sequence is in the other, the elements within the sequences must also be in the same order.

Apply it

Now consider a more complex scenario where we have a list of goats in a field and we want to compare two different lists to see if they are identical. We can create a class to represent a goat:

public class Goat
{
    public string Name { get; set; }
    public int Age { get; set; }
}

One goat is created, and added to two different list and compared.

var goat = new Goat() { Name = "Terry", Age = 30 };

var goatsFromHeaven = new List<Goat>() { goat };
var goatsFromHell = new List<Goat>() { goat };

bool result = goatsFromHeaven.SequenceEqual(goatsFromHell); // result: true

In this case, the result is true, but it's not always the case...

Limitations

Let's go deeper and create 2 two differents lists with goat having the exact same properties and order.

var goatsFromHeaven = new List<Goat>
{
    new() { Name = "Terry", Age = 30 },
    new() { Name = "Bobby", Age = 30 },
    new() { Name = "Jerry", Age = 28 }
};

var goatsFromHell = new List<Goat>
{
    new() { Name = "Terry", Age = 30 },
    new() { Name = "Bobby", Age = 30 },
    new() { Name = "Jerry", Age = 28 }
};

bool result = goatsFromHeaven.SequenceEqual(goatsFromHell); // result: false

In this example, both lists contain the same goats in the same order. Despite this, the result is wrong, why?

Because SequenceEqual will by default use the internal comparison which uses the reference to the object. In the second case, even though the Terry Goat has the same properties in both lists, the two objects have different references since they are not the same object.

The best way to solve this is to provide our own equality comparer. We can do this by implementing the IEqualityComparer interface and passing an instance of our comparer to the SequenceEqual method:

public class GoatSkillEqualityComparer : IEqualityComparer<Goat>
{
    public bool Equals(Goat x, Goat y)
    {
        if (x == null || y == null)
            return false;

        return x.Name == y.Name && x.Age == y.Age;
    }

    public int GetHashCode(Goat obj)
    {
        return obj.GetHashCode();
    }
}

//...

bool result = goatsFromHeaven.SequenceEqual(goatsFromHell, new GoatSkillEqualityComparer()); // result: true

We have created a custom GoatSkillEqualityComparer class to implement our own comparison method, and pass it inside `SequenceEqual` to perform it.

In this case, the comparison returns true, perfect!

Summary

SequenceEqual is a very useful method for comparing enumerables:

  • Works directly with types without references
  • Requires implementation of IEqualityComparer for complex types

If you want to know more, here is the official documentation:

Enumerable.SequenceEqual Method (System.Linq)
Determines whether two sequences are equal according to an equality comparer.

Have a goat day!