Question

Compare two Lists for differences

I would like some feedback on how we can best write a generic function that will enable two Lists to be compared. The Lists contain class objects and we would like to iterate through one list, looking for the same item in a second List and report any differences.

We already have a method to compare classes, so we need feedback on how we can feed the method (shown below) from two Lists.

For example, say we have a simple "Employee" class that has three properties, Name, ID, Department. We want to report the differences between List and another List.

Note:
Both lists will always contain the same number of items.

As mentioned above, we have a generic method that we use to compare two classes, how can we incorporate this method to cater for Lists, i.e. from another method, loop through the List and feed the classes to the generic method .... but how do we find the equivalent class in the second List to pass to the method below;

public static string CompareTwoClass_ReturnDifferences<T1, T2>(T1 Orig, T2 Dest)
    where T1 : class
    where T2 : class
{
    // Instantiate if necessary
    if (Dest == null) throw new ArgumentNullException("Dest", "Destination class must first be instantiated.");

    var Differences = CoreFormat.StringNoCharacters;

    // Loop through each property in the destination  
    foreach (var DestProp in Dest.GetType().GetProperties())
    {
        // Find the matching property in the Orig class and compare
        foreach (var OrigProp in Orig.GetType().GetProperties())
        {

            if (OrigProp.Name != DestProp.Name || OrigProp.PropertyType != DestProp.PropertyType) continue;
            if (OrigProp.GetValue(Orig, null).ToString() != DestProp.GetValue(Dest, null).ToString())
                Differences = Differences == CoreFormat.StringNoCharacters 
                    ? string.Format("{0}: {1} -> {2}", OrigProp.Name,
                                                       OrigProp.GetValue(Orig, null),
                                                       DestProp.GetValue(Dest, null)) 
                    : string.Format("{0} {1}{2}: {3} -> {4}", Differences,
                                                              Environment.NewLine,
                                                              OrigProp.Name,
                                                              OrigProp.GetValue(Orig, null),
                                                              DestProp.GetValue(Dest, null));
        }
    }
    return Differences;
}

Any suggestions or ideas appreciated?

Edit: Targeting .NET 2.0 so LINQ is out of the question.

 45  103181  45
1 Jan 1970

Solution

 74

This solution produces a result list, that contains all differences from both input lists. You can compare your objects by any property, in my example it is ID. The only restriction is that the lists should be of the same type:

var DifferencesList = ListA.Where(x => !ListB.Any(x1 => x1.id == x.id))
            .Union(ListB.Where(x => !ListA.Any(x1 => x1.id == x.id)));
2011-05-09

Solution

 15

.... but how do we find the equivalent class in the second List to pass to the method below;

This is your actual problem; you must have at least one immutable property, a id or something like that, to identify corresponding objects in both lists. If you do not have such a property you, cannot solve the problem without errors. You can just try to guess corresponding objects by searching for minimal or logical changes.

If you have such an property, the solution becomes really simple.

Enumerable.Join(
   listA, listB,
   a => a.Id, b => b.Id,
   (a, b) => CompareTwoClass_ReturnDifferences(a, b))

thanks to you both danbruc and Noldorin for your feedback. both Lists will be the same length and in the same order. so the method above is close, but can you modify this method to pass the enum.Current to the method i posted above?

Now I am confused ... what is the problem with that? Why not just the following?

for (Int32 i = 0; i < Math.Min(listA.Count, listB.Count); i++)
{
    yield return CompareTwoClass_ReturnDifferences(listA[i], listB[i]);
}

The Math.Min() call may even be left out if equal length is guaranted.


Noldorin's implementation is of course smarter because of the delegate and the use of enumerators instead of using ICollection.

2009-03-24