Updated on

Use SequenceEqual(). It compares two arrays element by element and returns true when they hold equal values in the same order, which is what “the same array” almost always means.

==, Equals(), and ReferenceEquals() do something different: they ask whether two variables point at the same array instance. Two separate arrays holding identical values are not equal by any of the three. The C# language reference states the rule: “By default, reference-type operands, excluding records, are equal if they refer to the same object.”

To download the source code for this article, you can visit our GitHub repository.

How Do We Compare Two Arrays in C#?

SequenceEqual() is the answer for almost every case. It walks both arrays, compares element by element, and returns true only when the lengths match and every pair is equal.

It has three forms worth knowing. The LINQ version works on any IEnumerable<T>.

The overload taking an IEqualityComparer<T> handles reference types where the default equality is identity rather than value. And AsSpan().SequenceEqual() does the same job over spans, which is the fastest form for value types.

The operators are the trap. ==, object.Equals(), and object.ReferenceEquals() all compare references when applied to arrays, because an array is a reference type and none of the three is overloaded for element comparison. Two arrays with identical contents fail all three.

A hand-written loop is still the right choice when the comparison has rules of its own: a tolerance on floating-point values, a case-insensitive string match, or an early exit.

Preparing the Environment

Before we start comparing arrays, we should create two arrays and fill them with some elements:

private static readonly int[] _firstArray = new int[] { 10, 9, 3, 8, 7 };
private static readonly int[] _secondArray = new int[] { 10, 9, 3, 8, 7 };

These two int arrays hold the same values in the same order, and every example below compares them. If we want the fundamentals first, we have a guide to arrays in C#, from the beginning.

Now that everything is ready, let’s start.

Does == Compare Array Contents in C#?

To start with the equality operator (==), we are going to create an EqualityOperator method:

public bool EqualityOperator(int[] firstArray, int[] secondArray)
{
    return firstArray == secondArray;
}

No. == on two arrays compares references, so it is true only when both variables point at the same array object.

Arrays are reference types and do not overload the operator, so the compiler emits a reference comparison. Two arrays created separately with identical elements are not equal, and that surprises people who arrive from languages where the operator compares contents.

object.Equals(a, b) behaves the same way here. Equals is virtual and a type can override it, but arrays do not override it to compare elements, so the call falls through to the reference check.

object.ReferenceEquals(a, b) is the same comparison stated explicitly, and it cannot be overridden. When reference identity is genuinely what we want, this is the honest way to ask for it.

For element comparison, SequenceEqual() is the answer. Every reference-based check runs in constant time regardless of array length, which is why they look fast in benchmarks.

The same distinction shows up outside arrays as well, and the difference between == and Equals() covers it as a language feature.

Compare Using Loop

In this approach, we are going to create a method with a for loop to iterate the entire array:

public bool ForLoop(int[] firstArray, int[] secondArray)
{
    if (firstArray.Length != secondArray.Length)
        return false;

    for (int i = 0; i < firstArray.Length; i++)
    {
        if (firstArray[i] != secondArray[i])
            return false;
    }

    return true;
}

Our method receives two arrays as parameters. Then, it compares if both arrays have the same length. If they have different lengths, they don’t share the same values as well.

After that, we can iterate through the entire array comparing if each index element has the same value in both arrays.

Once we find any different value, we can return false because we know that the arrays are different and we don’t need to go to the next iteration. However, if we finish the loop and don’t find any different value, we can return true indicating that the arrays are equal.

Compare Using Enumerable Class

The Enumerable class provides us with many methods to compare if two arrays are equal. However, it requires some attention on our side.

Enumerable.SequenceEqual

Let’s use Enumerable.SequenceEqual to compare two arrays:

return Enumerable.SequenceEqual(_firstArray, _secondArray);

This is a built-in method that iterates through the array comparing each element. Once we are using arrays of integers, and we know that int works as a value type, we don’t need to worry about how it is going to compare the elements.

SequenceEqual With Arrays of Objects

If we are using arrays of objects, it is necessary to do some changes to compare the values.

First, we are going to create an Article class to represent the elements of our array:

public class Article
{
    public string? Title { get; set; }
    public DateTime LastUpdate { get; set; }
}

The comparison rules belong in a class of their own:

public class ArticleComparer : IEqualityComparer<Article>
{
    public bool Equals(Article? first, Article? second)
    {
        if (ReferenceEquals(first, second))
            return true;

        if (first is null || second is null)
            return false;

        return first.Title == second.Title && first.LastUpdate == second.LastUpdate;
    }

    public int GetHashCode(Article obj)
    {
        return HashCode.Combine(obj.Title, obj.LastUpdate);
    }
}

Our ArticleComparer class implements IEqualityComparer<Article>, so it provides both an Equals method that compares the two properties and a GetHashCode method built from the same two properties.

Keeping the comparer separate from the data is what makes GetHashCode correct. A comparer has to hash the object it is handed, and a data class acting as its own comparer easily ends up returning the base implementation instead, which hashes the comparer instance rather than the argument. SequenceEqual() never calls GetHashCode, so that mistake stays invisible here — but the same type used with Distinct(), GroupBy(), a HashSet<T>, or a dictionary key would silently misbehave.

When we need ordering rules rather than equality rules, IComparable, IComparer, and comparison delegates are the matching set of abstractions.

Now we are going to create two Article arrays and fill them with different instances, but equal data:

var firstArticle = new Article() { Title = "First Article", LastUpdate = new() };
var firstArticleCopy = new Article() { Title = "First Article", LastUpdate = new() };
var secondArticle = new Article() { Title = "Second Article", LastUpdate = new() };
var secondArticleCopy = new Article() { Title = "Second Article", LastUpdate = new() };

var articleArray = new Article[] { firstArticle, secondArticle };
var articleArrayCopy = new Article[] { firstArticleCopy, secondArticleCopy };

Finally, we can execute the Enumerable.SequenceEqual with our third parameter:

return Enumerable.SequenceEqual(articleArray, articleArrayCopy, new ArticleComparer());

This third parameter (new ArticleComparer()) defines the behavior to compare the values from each position of the array.

object.ReferenceEquals

Let’s see how to use the ReferenceEquals method:

return ReferenceEquals(_firstArray, _secondArray);

The call is unqualified, so it resolves through the class we write it in, which inherits ReferenceEquals from object. It has nothing to do with LINQ: ReferenceEquals is not declared on Enumerable at all. Writing Enumerable.ReferenceEquals() does compile, because C# lets us reach an inherited static member through a derived type name, but it names a type that has no part in the call.

This call is going to return false if both arrays are not referencing the same memory address.

By default, it works similarly to the equality operator (==).

object.Equals

Let’s check how to use this built-in method to compare two arrays:

return Equals(_firstArray, _secondArray);

Again the call is unqualified and resolves to object.Equals through the containing class, not to anything on Enumerable. It returns true if both arrays are referencing the same memory address.

In this case, there’s no difference between calling one method or another because the main difference between Equals and ReferenceEquals is that Equals is a virtual method, so we can override it in our classes. However, once we are talking about arrays, we can’t override either of them. That said, both methods are going to check for reference equality.

Compare Using AsSpan().SequenceEqual()

To use this approach, let’s create a AsSpanSequenceEqual method:

public bool AsSpanSequenceEqual(int[] firstArray, int[] secondArray)
{
    return firstArray.AsSpan().SequenceEqual(secondArray);
}

First, we create a new instance of Span<int>, then, similar to the Enumerable.SequenceEqual, we call the SequenceEqual method. The difference is that this time we are not using the Enumerable class, but the MemoryExtensions class.

For bitwise-comparable element types such as int, that overload reinterprets both spans as bytes and compares them with SIMD vectors instead of one element per iteration, which is why it wins the benchmark below. There is more on using Span<T> to improve performance if we want the wider picture.

Once our array contains the same elements in the same order, the method returns true, otherwise, false.

Compare Arrays in C# Using IStructuralEquatable

Another way to compare two arrays is using the IStructuralEquatable interface:

IStructuralEquatable structuralEquatable = firstArray;
return structuralEquatable.Equals(secondArray, StructuralComparisons.StructuralEqualityComparer);

First, we need to transform our first array in a IStructuralEquatable and then, call the Equals method, sending our second array and StructuralComparisons.StructuralEqualityComparer as parameters. If the array contains the same values, it is going to return true, otherwise, false.

Which Way to Compare Arrays Is Fastest in C#?

Let’s implement a benchmark comparison to check which is the fastest way to compare arrays.

For this benchmark, we are going to write a FillElements method to populate two arrays with 1 million elements:

private void FillElements(int length)
{
    _firstArray = new int[length];
    _secondArray = new int[length];

    for (int i = 0; i < length; i++)
    {
        var value = new Random().Next(0, 1000);
        _firstArray[i] = value;
        _secondArray[i] = value;
    }
}

After running this benchmark, we can inspect these results:

| Method                  | Mean               | Error             | StdDev            | Median             |
|------------------------ |-------------------:|------------------:|------------------:|-------------------:|
| AsSpanSequenceEqual     |     96,194.4740 ns |     1,899.7005 ns |     3,749.8238 ns |     96,310.7239 ns |
| EnumerableSequenceEqual |    102,574.2026 ns |     2,026.4937 ns |     5,267.1271 ns |    101,545.7703 ns |
| ForLoop                 |    636,872.3826 ns |    17,075.6470 ns |    47,881.9485 ns |    622,740.7227 ns |
| StructuralEquatable     | 20,264,823.5106 ns | 1,081,160.5965 ns | 3,084,612.2428 ns | 19,384,420.0000 ns |

Three of our methods are deliberately absent from that table. EqualityOperator, ObjectReferenceEquals, and ObjectEquals compare references rather than elements, so they finish in constant time however long the arrays are. BenchmarkDotNet measures all three below its own resolution and warns that their duration is indistinguishable from an empty method, so publishing them as numbers would invite a comparison that does not exist.

The StructuralEquatable approach is the slowest, taking around 20.3 million nanoseconds (about 0.02 seconds) to compare one million elements.

Among the methods that compare each element individually, AsSpanSequenceEqual and EnumerableSequenceEqual are the fastest, at roughly 96 thousand and 103 thousand nanoseconds respectively.

The manual implementation (ForLoop) takes us about 637 thousand nanoseconds (0.637 milliseconds).

Among the approaches that actually compare elements, AsSpan().SequenceEqual() is fastest, with LINQ’s SequenceEqual() close behind and a hand-written loop slower than both.

That ordering is worth explaining. SequenceEqual over spans of primitive types compares whole blocks of memory at a time rather than one element per iteration, which a for loop written by hand does not do.

IStructuralEquatable is dramatically slower than everything else, because each element goes through a comparer call rather than a direct comparison. Reach for it when the structure is the point, not the speed.

The reference-based checks (==, Equals(), ReferenceEquals()) appear at the top of any benchmark and should be read separately. They answer a different question in constant time and are not an alternative to element comparison.

For arrays small enough to fit on a screen, none of this matters. Choose on clarity.

ApproachComparesWorks onUse it when
==ReferencesAny arrayChecking whether two variables are the same instance
object.ReferenceEquals(a, b)ReferencesAny arrayThe same check, stated explicitly and unambiguously
object.Equals(a, b)References for arraysAny arrayRarely; it reads like a value comparison and is not one
for loop with a length checkElementsAny arrayCustom rules: tolerance, case-insensitivity, early exit on a condition
Enumerable.SequenceEqual(a, b)Elements, in orderAny IEnumerable<T>The default answer
SequenceEqual(a, b, comparer)Elements, by our rulesAny IEnumerable<T>Reference types where value equality is not the default
a.AsSpan().SequenceEqual(b)Elements, in orderArrays and spansHot paths over value types; vectorised for bitwise-comparable elements
IStructuralEquatable.Equals(b, StructuralComparisons.StructuralEqualityComparer)Elements, structurallyArrays and tuplesNested or multidimensional structures; slowest by a wide margin

Conclusion

In this article, we’ve learned that we have many different, built-in and manual, ways to compare arrays in C#. In some of them, we compare just the reference, others we compare value by value.

With the help of the benchmark, we’ve seen the time each method takes, and it can help us to decide which method to use in different scenarios.

When the arrays in question hold bytes, comparing byte arrays specifically has its own faster answers worth knowing.

Tested with .NET 10.0.10.