Поделиться через


Array.Equals

"I wish .NET can compare contents of an Array." - Annoymous Array Comparer

Currently, when you compare two arrays with the = operator, we are really using the System.Object's = operator, which only compares the instances. (i.e. this uses reference equality, so it will only be true if both arrays points to the exact same instance) 

Well until we add this feature request in the framework itself, I've provided a temporary solution for you:

public static bool ArrayEquals<T>(T[] a, T[] b)

{

if (a.Length != b.Length)

return false;

for (int i = 0; i < a.Length; i++)

{

if (!a[i].Equals(b[i]))

return false;

}

return true;

}

Again, my solution is not perfect, because if you are comparing value types, I am boxing the value type before calling System.Object::Equal as you can see here:

IL_0026: box !!T
IL_002b: constrained. !!T
IL_0031: callvirt instance bool [mscorlib]System.Object::Equals(object)

My office neighbour Brian Grunkemeyer have the following perf suggestions:

1) Add a generic constraint to this method

public static bool ArrayEquals<T>(T[] a, T[] b) where T: IEquatable<T>

This will help our performance, but limits the method to only take types that implement IEquatable<T>. This may be too restrictive for some though.

2) Use EqualityComparer<T>.Default's Equal method instead of calling Equals on the types themselves.

    public static bool ArrayEquals<T>(T[] a, T[] b)

    {

        if (a.Length != b.Length)

            return false;

        EqualityComparer<T> comparer = EqualityComparer<T>.Default;

        for (int i = 0; i < a.Length; i++)

        {

            if (!comparer.Equals(a[i], b[i]))

                return false;

        }

        return true;

    }

Without measuring the perf of these on long arrays, my guess is that for value types #1 has the best perf, followed by #2 and then the original implementation.

What do you think?

Comments

  • Anonymous
    March 08, 2006
    the following method may get around the boxing issue for value types, its not how you would do this but it shows of anon. methods

               int[] a1 = new int[] { 2, 3, 4, 6, 7 };
               int[] a2 = new int[] { 2, 3, 4, 6, 7 };

               Predicate<int> d = delegate(int ii) {
                   foreach (int v in a2)
                   {
                       if (v==ii)
                       {
                           return true;
                       }
                   }
                   return false;
               };

               if (a1.Length==a2.Length)
               {
                   if (Array.TrueForAll<int>(a1, d))
                       MessageBox.Show("Arrays are equal");
                   else
                       MessageBox.Show("Arrays are not equal");
               }

  • Anonymous
    March 08, 2006
    Your first variant isn't immediately usable because you have to check whether a[i] is a null reference before you can try invoking Equals on it. The additional checking code definitely makes the first variant longer than the second, although the speedup might be worth it.

  • Anonymous
    March 29, 2006
    You also have to think about the semantics of comparing an array of arrays. You may not care for the recursive comparison, or maybe you do. If you do want recursive comparison, you have to worry about loops in the object graph.

  • Anonymous
    April 23, 2007
    If you're looking for very generic code, without additional hastle or constraints you're always going to end up checking null for reference types. I was wondering if this would work...  I havn't actually tried it or debugged it or really thought about it much public bool IsEqual(T[] a, T[] b) {    if ((a == null || b == null) &&        (a != null || b != null))        return false;    if (a.Length != b.Length)        return false;    // HACK: And proud    bool isRef = false;    try    {        T moo = null;        isRef = true;    } catch { }    if (isRef)    {        // Object comparrison with null check        // a.Length == b.Length        for (long l = 0; l < a.Length; l++)        {            if (a[l] != null && !a[l].Equals(b[l]))            return false;        }    }    else    {        // Value types        // a.Length == b.Length        for (long l = 0; l < a.Length; l++)        {            if (a[l] != b[l])            return false;        }    }    return true; }

  • Anonymous
    April 12, 2008
    This is a great example of something really simple that C# makes really hard. And it just happens that I'm feeling this exact pain today (array comparison), transitioning from C++.

  • Anonymous
    May 23, 2008
    looks like the SequenceEqual extension in C#3.0 or 3.5 or whatever may be what we want!

  • Anonymous
    August 20, 2008
    SequenceEqual is not as fast as the method here.... not atleast as far as my tests show..

  • Anonymous
    May 27, 2010
    //here is my solution tests and all... private static bool Validate<T>(T[] array1, T[] array2) { array1.ValidateIsNotNull("array1"); //Throw Exception if item is null... array2.ValidateIsNotNull("array2"); if (array1.Length != array2.Length) { return false; } return true; } public static bool AreValuesEqual<T>(this T[] array1, T[] array2) where T : struct, IEquatable<T> { if (!Validate(array1, array2)) { return false; } for (var i = 0; i < array1.Length; i++) { if(!array1[i].Equals(array2[i])) { return false; } } return true; } public static bool AreItemsEqual<T>(this T[] array1, T[] array2) where T : class, IEquatable<T> { if (!Validate(array1, array2)) { return false; } for (var i = 0; i < array1.Length; i++) { var item1 = array1[i]; var item2 = array2[i]; if(item1 == null ^ item2 == null) { return false; } if (item1 != null && !item1.Equals(item2)) { return false; } } return true; } public static bool AreItemsEqual(string[] array1, string[] array2, StringComparer comparer) { comparer.ValidateIsNotNull("comparer"); if (!Validate(array1, array2)) { return false; } for (var i = 0; i < array1.Length; i++) { if (!comparer.Equals(array1[i], array2[i])) { return false; } } return true; } [Test] public void TestMethod() { var bytes1 = new byte[] {1,2,3,4,79}; var bytes2 = new byte[] {1,2,3,4,79}; Assert.IsTrue(ArrayExtender.AreValuesEqual(bytes1, bytes2)); var strs1 = new string[] {"123", "abc"}; var strs2 = new string[] {"123", "aBc"}; Assert.IsTrue(ArrayExtender.AreItemsEqual(strs1, strs2, StringComparer.OrdinalIgnoreCase)); Assert.IsFalse(ArrayExtender.AreItemsEqual(strs1, strs2, StringComparer.Ordinal)); } [Test] public void TestMethod2() { var bytes1 = new string[] {"123", "abc"}; var bytes2 = new string[] {"123", "aBc"}; Assert.IsTrue(ArrayExtender.AreItemsEqual(bytes1, bytes2, StringComparer.OrdinalIgnoreCase)); } [Test] public void TestMethod3() { var strs1 = new string[] {"123", "abc"}; var strs2 = new string[] {"123", null}; Assert.IsFalse(ArrayExtender.AreItemsEqual(strs1, strs2, StringComparer.OrdinalIgnoreCase)); strs1 = new string[] {"123", null}; Assert.IsTrue(ArrayExtender.AreItemsEqual(strs1, strs2, StringComparer.OrdinalIgnoreCase)); }

  • Anonymous
    June 10, 2010
    I know this is an old post and everyone is probably using Linq now, but should you call Sort on the two lists before you step through them?