Home > Mobile >  How to implement different comparison for IEnumerable<T>
How to implement different comparison for IEnumerable<T>

Time:08-25

Currently, I compare it this way (from here):

bool foo<T>(T expected, T actual) {
    return EqualityComparer<T>.Default.Equals(expected, actual);
}

It works for basic types but not for IEnumerable<T>. How can I make it work for IEnumerable<T> like SequenceEqual?

EDIT: I tried the version of David L but it doesn't work either with arrays or with lists.

CodePudding user response:

While there is nothing that prevents you from calling SequenceEquals directly, you can certainly wrap that call in helper methods. Note that SequenceEquals will compare index by index. You can also pass it an IEqualityComparer<T> instance if you want to override how the comparison is handled.

bool foo<T>(IEnumerable<T> expected, IEnumerable<T> actual)
{
    return expected.SequenceEqual(actual);
}

bool foo<T>(List<T> expected, List<T> actual)
{
    return expected.SequenceEqual(actual);
}

bool foo<T>(T[] expected, T[] actual)
{
    return expected.SequenceEqual(actual);
}

bool foo<T>(IEnumerable<T> expected, IEnumerable<T> actual,
    IEqualityComparer<T> comparer)
{
    return expected.SequenceEqual(actual, comparer);
}

Testing with the following returns true as expected:

IEnumerable<int> expected = new List<int> { 1, 2, 3 };
IEnumerable<int> actual = new List<int> { 1, 2, 3 };

List<int> expected2 = new List<int> { 1, 2, 3 };
List<int> actual2 = new List<int> { 1, 2, 3 };

int[] expected3 = new int[] { 1, 2, 3 };
int[] actual3 = new int[] { 1, 2, 3 };

Console.WriteLine(foo(expected, actual));
Console.WriteLine(foo(expected2, actual2));
Console.WriteLine(foo(expected3, actual3));

and returns false for the following:

IEnumerable<int> expected = new List<int> { 1, 2, 3 };
IEnumerable<int> actual = new List<int> { 1, 3, 2 };

The benefit of the above approach is that you get compile-time overloads for each collection type that you want to explicitly support. However, if you want to implicitly support all collection types, you can expand your current boo foo<T> method as follows:

bool foo<T>(T expected, T actual)
{
    // check that both T parameters implement IEnumerable 
    // (meaning they are a collection)
    if (expected is IEnumerable expectedEnumerable && 
        actual is IEnumerable actualEnumerable)
    {
        var expectedEnumerator = expectedEnumerable.GetEnumerator();
        var actualEnumerator = actualEnumerable.GetEnumerator();
        
        while (true)
        {
            // Determine if the enumerators have a next element
            var hasExpected = expectedEnumerator.MoveNext();
            var hasActual = actualEnumerator.MoveNext();

            // Either we have no elements or successfully iterated both
            if (!hasExpected && !hasActual)
            {
                return true;
            }

            // One of the enumerators has a value while the other does not.
            if ((hasExpected && !hasActual) || (!hasExpected && hasActual))
            {
                return false;
            }

            // check the equality of the two current elements
            if (!expectedEnumerator.Current.Equals(actualEnumerator.Current))
            {
                return false;
            }
        }
    }
    
    // fall back to your original equality comparison because
    // one or both parameters do not implement IEnumerable
    return EqualityComparer<T>.Default.Equals(expected, actual);
}

This again returns true for the three collection cases I listed above, and false for the following test cases:

IEnumerable<int> expected4 = new List<int> { 1, 2, 3 };
IEnumerable<int> actual4 = new List<int> { 1, 3, 2 };

List<int> expected5 = new List<int> { 1, 2, 3, 4 };
List<int> actual5 = new List<int> { 1, 2, 3 };

int[] expected6 = new int[] { 2, 3 };
int[] actual6 = new int[] { 1, 2, 3 };

Console.WriteLine(foo(expected4, actual4));
Console.WriteLine(foo(expected5, actual5));
Console.WriteLine(foo(expected6, actual6));
  • Related