63

I am trying to compare two arrays with each other. I tried this code and got the following errors.

static bool ArraysEqual(Array a1, Array a2)
{
    if (a1 == a2)
        return true;

    if (a1 == null || a2 == null)
        return false;

    if (a1.Length != a2.Length)
        return false;

    IList list1 = a1, list2 = a2; //error CS0305: Using the generic type 'System.Collections.Generic.IList<T>' requires '1' type arguments
    for (int i = 0; i < a1.Length; i++)
    {
        if (!Object.Equals(list1[i], list2[i])) //error CS0021: Cannot apply indexing with [] to an expression of type 'IList'(x2)
            return false;
    }
    return true;
}

Why do I get that error? I went for a low-tech solution and did this which works fine, but I need to copy/paste it several times for each type.

static bool ArraysEqual(byte[] a1, byte[] a2)
{
    if (a1 == a2)
        return true;

    if (a1 == null || a2 == null)
        return false;

    if (a1.Length != a2.Length)
        return false;

    for (int i = 0; i < a1.Length; i++)
    {
        if (a1[i] != a2[i])
            return false;
    }
    return true;
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131

6 Answers6

164

Providing that you have LINQ available and don't care too much about performance, the easiest thing is the following:

var arraysAreEqual = Enumerable.SequenceEqual(a1, a2);

In fact, it's probably worth checking with Reflector or ILSpy what the SequenceEqual methods actually does, since it may well optimise for the special case of array values anyway!

Noldorin
  • 144,213
  • 56
  • 264
  • 302
  • 16
    Or just a1.SequenceEquals(a2). What leads you to believe this would perform any worse than the accepted answer? – Joel Mueller Apr 03 '09 at 20:28
  • 4
    I suppose the enumerable version can't rely on comparing lengths, so the accepted answer would be faster in cases where the arrays being compared were of different lengths. – Joel Mueller Apr 03 '09 at 20:29
  • @Joel: That's essentially the reason. The SequenceEquals method uses enumerators, which means instantiating an iterator and then making numerous function calls to loop through it, all adding overhead. Normally I wouldn't think twice about using this method - it was just a small caveat. – Noldorin Apr 03 '09 at 20:33
  • 2
    I wouldn't be surprised if Enumerable.SequenceEqual is optimised to check if the enumerables are arrays etc. I tried checking the method in ILSpy but it only showed an empty method with a TargetedPatchingOptOut attribute on it. – mackenir Dec 13 '12 at 18:04
  • @mackenir: Indeed, it's quite possible. Other methods in the BCL are known to be optimised for specific instance types of `IEnumerable`. – Noldorin Dec 14 '12 at 01:08
  • 2
    If you know the arrays will frequently be of different lengths, you can add `a1.Length == a2.Length &&` before. – Guvante Mar 22 '13 at 16:09
  • 1
    There are some cases where `Enumerable` optimizes by attempting to cast the sequences to `ICollection` – such as to read `Count` – but this is not the case for `SequenceEqual` (see [source](http://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs,9bdd6ef7ba6a5615)). So you *should* check for `Length` inequality before calling `SequenceEqual`. – Douglas Feb 12 '16 at 16:51
74

"Why do i get that error?" - probably, you don't have "using System.Collections;" at the top of the file - only "using System.Collections.Generic;" - however, generics are probably safer - see below:

static bool ArraysEqual<T>(T[] a1, T[] a2)
{
    if (ReferenceEquals(a1,a2))
        return true;

    if (a1 == null || a2 == null)
        return false;

    if (a1.Length != a2.Length)
        return false;

    EqualityComparer<T> comparer = EqualityComparer<T>.Default;
    for (int i = 0; i < a1.Length; i++)
    {
        if (!comparer.Equals(a1[i], a2[i])) return false;
    }
    return true;
}
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 1
    Thank you for the 2nd time today Marc :D –  Apr 03 '09 at 10:21
  • 1
    this should work only for sorted arrays. if they are not sorted you need at least O(nlgn). – DarthVader Apr 06 '11 at 14:17
  • 6
    @user177883 Eh? If the two arrays are in different order, should they not be considered different? – RandomInsano Apr 10 '11 at 04:48
  • 4
    @RandomInsano indeed; in the question *as asked*, order was important – Marc Gravell Apr 10 '11 at 07:31
  • if the order is different but contains the same elements, yes, they are identical. however running time changes, and the code above wouldnt work out. – DarthVader May 13 '11 at 19:56
  • Actually these two arrays are not identical: 1, 2, 3 and 1, 3, 2. They contain the same elements but they are different arrays. Whoever is doing the comparing needs to determine what type of comparison they want to do. 1. Same object in memory, 2. identical values/order in array, 3. Contains the same values any order. – Rhyous Oct 11 '12 at 21:11
  • 4
    @DarthVader: Those would be identical *sets*, but not identical *arrays*. – Ben Voigt May 21 '13 at 00:48
13

For .NET 4.0 and higher, you can compare elements in array or tuples using the StructuralComparisons type:

object[] a1 = { "string", 123, true };
object[] a2 = { "string", 123, true };
        
Console.WriteLine (a1 == a2);        // False (because arrays is reference types)
Console.WriteLine (a1.Equals (a2));  // False (because arrays is reference types)
        
IStructuralEquatable se1 = a1;
//Next returns True
Console.WriteLine (se1.Equals (a2, StructuralComparisons.StructuralEqualityComparer)); 
Pang
  • 9,564
  • 146
  • 81
  • 122
Yuliia Ashomok
  • 8,336
  • 2
  • 60
  • 69
7

Recommending SequenceEqual is ok, but thinking that it may ever be faster than usual for(;;) loop is too naive.

Here is the reflected code:

public static bool SequenceEqual<TSource>(this IEnumerable<TSource> first, 
    IEnumerable<TSource> second, IEqualityComparer<TSource> comparer)
{
    if (comparer == null)
    {
        comparer = EqualityComparer<TSource>.Default;
    }
    if (first == null)
    {
        throw Error.ArgumentNull("first");
    }
    if (second == null)
    {
        throw Error.ArgumentNull("second");
    }
    using (IEnumerator<TSource> enumerator = first.GetEnumerator())     
    using (IEnumerator<TSource> enumerator2 = second.GetEnumerator())
    {
        while (enumerator.MoveNext())
        {
            if (!enumerator2.MoveNext() || !comparer.Equals(enumerator.Current, enumerator2.Current))
            {
                return false;
            }
        }
        if (enumerator2.MoveNext())
        {
            return false;
        }
    }
    return true;
}

As you can see it uses 2 enumerators and fires numerous method calls which seriously slow everything down. Also it doesn't check length at all, so in bad cases it can be ridiculously slower.

Compare moving two iterators with beautiful

if (a1[i] != a2[i])

and you will know what I mean about performance.

It can be used in cases where performance is really not so critical, maybe in unit test code, or in cases of some short list in rarely called methods.

Valentin Kuzub
  • 11,703
  • 7
  • 56
  • 93
  • I slightly modified your code. Specific the using statement. I almost made it use `var` but left it alone –  May 21 '13 at 00:06
3

SequenceEqual can be faster. Namely in the case where almost all of the time, both arrays have indeed the same length and are not the same object.

It's still not the same functionality as the OP's function, as it won't silently compare null values.

Imi
  • 39
  • 1
0

I know this is an old topic, but I think it is still relevant, and would like to share an implementation of an array comparison method which I feel strikes the right balance between performance and elegance.

static bool CollectionEquals<T>(ICollection<T> a, ICollection<T> b, IEqualityComparer<T> comparer = null)
{
    return ReferenceEquals(a, b) || a != null && b != null && a.Count == b.Count && a.SequenceEqual(b, comparer);
}

The idea here is to check for all of the early out conditions first, then fall back on SequenceEqual. It also avoids doing extra branching and instead relies on boolean short-circuit to avoid unecessary execution. I also feel it looks clean and is easy to understand.

Also, by using ICollection for the parameters, it will work with more than just arrays.

Xavier
  • 3,254
  • 15
  • 22