比较两个列表的差异

44
我希望能够得到一些关于如何编写通用函数进行列表比较的反馈。这些列表包含类对象,我们想要遍历一个列表,在第二个列表中查找相同的项,并报告任何差异。
我们已经有了比较类的方法,因此我们需要反馈如何从两个列表中提供方法(如下所示)。
例如,假设我们有一个简单的“Employee”类,它具有三个属性:Name、ID和Department。我们想要报告List和另一个List之间的差异。
注意: 两个列表始终包含相同数量的项。
如上所述,我们有一个通用的方法来比较两个类,如何将该方法纳入考虑以满足列表的需求?也就是说,从另一个方法中循环遍历列表并将类提供给通用方法...但如何找到第二个列表中的等效类以传递给以下方法?
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;
}

有任何建议或想法都欢迎提出?

编辑:目标是.NET 2.0,因此LINQ不可用。


笑...不,这是一个高级别的、系统关键的应用程序 :-) 说真的,我在试图将这个功能实现到一个小的业余应用程序中...这都是为了学习。 - pedro
这些列表长度相等吗? - Noldorin
相等吗?我正在学习,很高兴将其作为学习练习。你能扩展一下吗? - pedro
哎呀,我误删了注释。我是想将对象与ID属性相等进行比较。并为具有相同ID的对象添加GetDifferences()方法。然后使用LINQ。 - gcores
@pedro: LINQ 可以 在 .NET 2.0 上运行。请查看 http://code.google.com/p/linqbridge/ - Mauricio Scheffer
5个回答

73

这个解决方案生成一个结果列表,其中包含两个输入列表中的所有差异。您可以通过任何属性比较对象,在我的示例中是ID。唯一的限制是列表应该是相同类型的:

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

6
太好了。社区真正地让你从另一个人的角度看待问题。谢谢! - Jeremy
1
运行完美!谢谢。 - Haris
我相信这假设两个列表中都没有重复项? - NStuke

15

....但我们如何在第二个列表中找到与方法下面传递的等效类?

这是您实际遇到的问题;您必须至少拥有一个不可变属性,例如id或类似的东西,以识别两个列表中对应的对象。如果没有这样的属性,则无法解决问题而不出现错误。您可以尝试通过寻找最小或最合理的更改来猜测相应的对象。

如果您拥有此类属性,则解决方案变得非常简单。

Enumerable.Join(
   listA, listB,
   a => a.Id, b => b.Id,
   (a, b) => CompareTwoClass_ReturnDifferences(a, b))
感谢danbruc和Noldorin的反馈。两个列表将会是相同的长度和相同的顺序。所以上面的方法已经接近解决问题,但是你能否修改这个方法将枚举中的当前项传递给我上面发布的方法吗?现在我有点困惑了...这有什么问题吗?为什么不直接这样做呢?
for (Int32 i = 0; i < Math.Min(listA.Count, listB.Count); i++)
{
    yield return CompareTwoClass_ReturnDifferences(listA[i], listB[i]);
}

如果保证长度相等,则甚至可以省略Math.Min()调用。


Noldorin的实现当然更加聪明,因为使用了委托和枚举器,而不是使用ICollection。


抱歉,我们的目标是.NET 2.0。我应该在之前明确这一点。 - pedro

5

我认为你需要的是这样一种方法:

public static IEnumerable<TResult> CompareSequences<T1, T2, TResult>(IEnumerable<T1> seq1,
    IEnumerable<T2> seq2, Func<T1, T2, TResult> comparer)
{
    var enum1 = seq1.GetEnumerator();
    var enum2 = seq2.GetEnumerator();

    while (enum1.MoveNext() && enum2.MoveNext())
    {
        yield return comparer(enum1.Current, enum2.Current);
    }
}

这段代码未经测试,但仍能完成任务。需要注意的是,这种方法非常通用,即它可以接受两个任意(且不同)类型的序列,并返回任何类型的对象。
当然,此解决方案假设您想比较seq1的第n个项目与seq2的第n个项目。如果您想基于特定属性/比较匹配两个序列中的元素,则需要执行某种连接操作(如danbruc建议使用Enumerable.Join)。如果以上两种方法都无法满足您的需求,请告诉我,也许我可以提供其他建议。
编辑:以下是一个示例,说明如何使用您最初发布的比较器函数和CompareSequences方法。
// Prints out to the console all the results returned by the comparer function (CompareTwoClass_ReturnDifferences in this case).
var results = CompareSequences(list1, list2, CompareTwoClass_ReturnDifferences);
int index;    

foreach(var element in results)
{
    Console.WriteLine("{0:#000} {1}", index++, element.ToString());
}

这将同时遍历两个列表,但是对象在两个列表中的顺序可能不一样。 - Daniel Brückner
当然可以。我不太确定问题是否是这种情况,但在阅读您的评论之前,我已经编辑了帖子,所以现在它已经被正确地限定了... - Noldorin
感谢danbruc和Noldorin的反馈。两个列表将具有相同的长度和顺序。因此,上面的方法是接近的,但您能否修改此方法以将enum.Current传递给我上面发布的方法? - pedro
没问题... 事实上,这两个列表长度相等且排序相同确实非常有帮助,所以我认为这个解决方案会很好地完成工作(danbruc提供的方案值得注意,适用于更一般的情况)。 - Noldorin
现在,我不太确定你指的是什么意思,关于传递enum.Current(上面的代码必须传递enum1.Current和enum2.Current)。我已经编辑了帖子,以澄清如何使用CompareSequences函数。 - Noldorin

2
这种来自微软的方法非常有效,可以比较两个列表并切换它们以获得每个列表中的差异。如果您要比较类,只需将对象添加到两个单独的列表中,然后运行比较即可。 http://msdn.microsoft.com/en-us/library/bb397894.aspx

1

我希望我正确理解了你的问题,但是你可以使用Linq非常快速地完成这个任务。我假设你的所有类都有一个Id属性,只需创建一个接口来确保这一点。

如果每个类中用于标识对象相同的方式不同,我建议传递一个委托,如果两个对象具有相同的持久性id,则返回true。

以下是如何在Linq中完成此操作:

List<Employee> listA = new List<Employee>();
        List<Employee> listB = new List<Employee>();

        listA.Add(new Employee() { Id = 1, Name = "Bill" });
        listA.Add(new Employee() { Id = 2, Name = "Ted" });

        listB.Add(new Employee() { Id = 1, Name = "Bill Sr." });
        listB.Add(new Employee() { Id = 3, Name = "Jim" });

        var identicalQuery = from employeeA in listA
                             join employeeB in listB on employeeA.Id equals employeeB.Id
                             select new { EmployeeA = employeeA, EmployeeB = employeeB };

        foreach (var queryResult in identicalQuery)
        {
            Console.WriteLine(queryResult.EmployeeA.Name);
            Console.WriteLine(queryResult.EmployeeB.Name);
        }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接