简体   繁体   English

比较两个列表的差异

[英]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.我想要一些关于我们如何最好地编写通用 function 的反馈,这将使两个列表能够进行比较。 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.列表包含 class 对象,我们希望遍历一个列表,在第二个列表中查找相同的项目并报告任何差异。

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.例如,假设我们有一个简单的“Employee”class,它具有三个属性:Name、ID、Department。 We want to report the differences between List and another List.我们要报告 List 和另一个 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, ie 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;如上所述,我们有一个用于比较两个类的泛型方法,我们如何合并该方法来满足列表的需求,即从另一个方法循环遍历列表并将类提供给泛型方法....但是我们如何在第二个列表中找到等效的 class 以传递给下面的方法;

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.编辑:针对 .NET 2.0 所以 LINQ 是不可能的。

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.您可以通过任何属性来比较您的对象,在我的示例中是 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)));

.... but how do we find the equivalent class in the second List to pass to the method below; ....但是我们如何在第二个列表中找到等效的 class 以传递给下面的方法;

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.你必须至少有一个不可变的属性,一个 id 或类似的东西,来识别两个列表中的对应对象。 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.感谢 danbruc 和 Noldorin 的反馈。 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?所以上面的方法很接近,但是你可以修改这个方法以将 enum.Current 传递给我上面发布的方法吗?

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.如果保证相等的长度,甚至可以省略 Math.Min() 调用。


Noldorin's implementation is of course smarter because of the delegate and the use of enumerators instead of using ICollection. Noldorin 的实现当然更智能,因为委托和使用枚举器而不是使用 ICollection。

I think you're looking for a method like this:我认为您正在寻找这样的方法:

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);
    }
}

It's untested, but it should do the job nonetheless.它未经测试,但它仍然可以完成这项工作。 Note that what's particularly useful about this method is that it's full generic, ie it can take two sequences of arbitrary (and different) types and return objects of any type.请注意,此方法特别有用的是它是完全通用的,即它可以采用两个任意(和不同)类型的序列并返回任何类型的对象。

This solution of course assumes that you want to compare the nth item of seq1 with the nth item in seq2 .这个解决方案当然假设您想要将seq1的第 n 项与seq2的第 n 项进行比较。 If you want to do match the elements in the two sequences based on a particular property/comparison, then you'll want to perform some sort of join operation (as suggested by danbruc using Enumerable.Join . Do let me know if it neither of these approaches is quite what I'm after and maybe I can suggest something else.如果您想根据特定属性/比较来匹配两个序列中的元素,那么您将需要执行某种连接操作(如 danbruc 使用Enumerable.Join建议的那样。如果两者都不是,请告诉我这些方法正是我所追求的,也许我可以提出其他建议。

Edit: Here's an example of how you might use the CompareSequences method with the comparer function you originally posted.编辑:这是一个如何将CompareSequences方法与您最初发布的比较器 function 一起使用的示例。

// 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());
}

This approach from Microsoft works very well and provides the option to compare one list to another and switch them to get the difference in each. Microsoft 的这种方法效果很好,并提供了将一个列表与另一个列表进行比较并切换它们以获得每个列表的差异的选项。 If you are comparing classes simply add your objects to two separate lists and then run the comparison.如果您要比较类,只需将您的对象添加到两个单独的列表中,然后运行比较。

http://msdn.microsoft.com/en-us/library/bb397894.aspx http://msdn.microsoft.com/en-us/library/bb397894.aspx

I hope that I am understing your question correctly, but you can do this very quickly with Linq.我希望我正确理解了您的问题,但是您可以使用 Linq 快速完成此操作。 I'm assuming that universally you will always have an Id property.我假设您将始终拥有 Id 属性。 Just create an interface to ensure this.只需创建一个接口来确保这一点。

If how you identify an object to be the same changes from class to class, I would recommend passing in a delegate that returns true if the two objects have the same persistent id.如果您如何识别 object 与从 class 到 class 的相同变化,我建议传入一个如果两个对象具有相同的持久 id 则返回 true 的委托。

Here is how to do it in Linq:以下是在 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);
        }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM