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:
Have a goat day!