2

Before marking this as duplicate because of its title please consider the following short program:

static void Main()
{
    var expected = new List<long[]> { new[] { Convert.ToInt64(1), Convert.ToInt64(999999) } };
    var actual = DoSomething();
    if (!actual.SequenceEqual(expected)) throw new Exception();
}

static IEnumerable<long[]> DoSomething()
{
    yield return new[] { Convert.ToInt64(1), Convert.ToInt64(999999) };
}

I have a method which returns a sequence of arrays of type long. To test it I wrote some test-code similar to that one within Main.

However I get the exception, but I don´t know why. Shouldn´t the expected sequence be comparable to the actually returned one or did I miss anything?

To me it looks as both the method and the epxected contain exactly one single element containing an array of type long, doesn´t it?

EDIT: So how do I achieve to not get the exception meaning to compare the elements within the enumeration to return equality?

MakePeaceGreatAgain
  • 35,491
  • 6
  • 60
  • 111
  • 1
    What is the exception? – Magnus Dec 02 '15 at 15:32
  • 2
    The elements in your sequence are `long[]`. The comparison will be that of array references, which are indeed different. The elements of the array (which is in the sequence) will not be compared. – Vikas Gupta Dec 02 '15 at 15:41
  • You should implement your own comparer and should pass an instance of this comparer as the second parameter to SequenceCompare. – Oguz Ozgul Dec 02 '15 at 15:51

3 Answers3

6

The actual problem is the fact that you're comparing two long[], and Enumerable.SequenceEquals will use an ObjectEqualityComparer<Int64[]> (you can see that by examining EqualityComparer<long[]>.Default which is what is being internally used by Enumerable.SequenceEquals), which will compare references of those two arrays, and not the actual values stored inside the array, which obviously aren't the same.

To get around this, you could write a custom EqualityComparer<long[]>:

static void Main()
{
    var expected = new List<long[]> 
                       { new[] { Convert.ToInt64(1), Convert.ToInt64(999999) } };
    var actual = DoSomething();

    if (!actual.SequenceEqual(expected, new LongArrayComparer()))
        throw new Exception();
}

public class LongArrayComparer : EqualityComparer<long[]>
{
    public override bool Equals(long[] first, long[] second)
    {
        return first.SequenceEqual(second);
    }

    // GetHashCode implementation in the courtesy of @JonSkeet
    // from http://stackoverflow.com/questions/7244699/gethashcode-on-byte-array
    public override int GetHashCode(long[] arr)
    {
        unchecked
        {
            if (array == null)
            {
                return 0;
            }

            int hash = 17;
            foreach (long element in arr)
            {
                hash = hash * 31 + element.GetHashCode();
            }

            return hash;
        }
    }
}
Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
  • 1
    Your `GetHashCode` is incorrect. It'll work fine with this particular use, because `SequenceEqual` doesn't use `GetHashCode`, but if anyone used that for another use of `EqualityComparer` like `Distinct` it would be incorrect. You need to produce a hash code that relates to sequence equality. – Jon Hanna Dec 02 '15 at 15:47
  • @JonHanna You're right, this was a quick hack just to show that one could easily implement a custom comparer. I'll fix it up. – Yuval Itzchakov Dec 02 '15 at 15:48
  • What a luck I implemented a generic `EqualityComparer` some months ago whereas I can use lambda-expressions for implementing the two methods. However I´ll basically use exactly this approach now, thank you. – MakePeaceGreatAgain Dec 02 '15 at 15:51
  • Yeah. No harm as a quicky, but worth pointing out in case someone copy-pastes it to where it won't work. – Jon Hanna Dec 02 '15 at 15:52
  • @JonHanna This one should be better. – Yuval Itzchakov Dec 02 '15 at 15:55
  • You can and should move some code out of the unchecked block. Only have what's needed there. – ErikE Dec 02 '15 at 16:12
  • @ErikE Do explain why that is of such importance? – Yuval Itzchakov Dec 02 '15 at 16:15
  • 1
    Code is written for programmers, not computers. Thus, unusual code should be doing something unusual, otherwise the next person looking at it will waste time fruitlessly trying to figure out what is going on. In this case, the non-needed code being inside the unchecked block forces the reader to scan the whole block looking for the reason. And someone's modification later might accidentally put more code inside that block, too. It's about the signal-to-noise ratio. It's about keeping the surface area of "this is weird code" small. – ErikE Dec 02 '15 at 17:05
4

SequenceEquals tests for the elements within the sequences to be identical. The elements within the enumerations are of type long[], so we actually compare two different arrays (containing the same elements however) against each other which is obsiously done by comparing their references instead of their actual value .

So what we actually check here is this expected[0] == actual[0] instead of expected[0].SequqnceEquals(actual[0])

This is obiosuly returns false as both arrays share different references.

If we flatten the hierarchy using SelectMany we get what we want:

if (!actual.SelectMany(x => x).SequenceEqual(expected.SelectMany(x => x))) throw new Exception();

EDIT:

Based on this approach I found another elegant way to check if all the elements from expected are contained in actual also:

if (!expected.All(x => actual.Any(y => y.SequenceEqual(x)))) throw new Exception();

This will search if for ever sub-list within expected there is a list within actual that is sequentially identical to the current one. This seems much smarter to be as we do not need any custom EqualityComparer and no weird hashcode-implementation.

Community
  • 1
  • 1
MakePeaceGreatAgain
  • 35,491
  • 6
  • 60
  • 111
4

No, your sequences are not equal!

Lets remove the sequence bit, and just take what is in the first element of each item

var firstExpected = new[] { Convert.ToInt64(1), Convert.ToInt64(999999) };
var firstActual = new[] { Convert.ToInt64(1), Convert.ToInt64(999999) };
Console.WriteLine(firstExpected == firstActual); // writes "false"

The code above is comparing two separate arrays for equality. Equality does not check the contents of arrays it checks the references for equality.

Your code using SequenceEquals is, essentially, doing the same thing. It checks the references in each case of each element in an enumerable.

Jamiec
  • 133,658
  • 13
  • 134
  • 193