I encountered an issue while comparing two Lists. I have two Lists of customs objects and I want to have the difference between them and then Count the numbers of results. Here is an example :

This is my custom class :

public class Contact
{
    public String FirstName { get; set; }
    public String LastName { get; set; }
    public bool IsAdmin { get; set; }
}

And In my app when I make a difference between two List It sound like this :

List<Contact> Difference =
    List1.Where(Second =>
        !List2.Any(First =>
            First.FirstName == Second.FirstName
            && First.LastName == Second.LastName
            && First.IsAdmin == Second.IsAdmin))
    .ToList();

This method give me the results which match with the condition , so I can Group and count my results except when I have a result like that :

List<Contact> List1 = new List<Contact>
{
    new Contact { Firstname = "Tom", LastName = "Smith", IsAdmin = true },
    new Contact { Firstname = "Tom", LastName = "Smith", IsAdmin = true },
    new Contact { Firstname = "Bob", LastName = "Smith", IsAdmin = false },
    new Contact { Firstname = "Vincent", LastName = "Smith", IsAdmin = false }
};

List<Contact> List2 =new List<Contact>
{ 
    new Contact { Firstname = "Tom", LastName = "Smith", IsAdmin = true},
    new Contact { Firstname = "Bob", LastName = "Smith", IsAdmin = false}
};

When I run my method I have 1 results :

new Contact { Firstname = "Vincent", LastName = "Smith", IsAdmin = false }

because It matchs with the condition But I want this as result :

new Contact { Firstname = "Tom", LastName = "Smith", IsAdmin = true}
new Contact { Firstname = "Vincent", LastName = "Smith", IsAdmin = false }

How could you make it possible ?

Edit : Working method :

var groups1 = List1
    .GroupBy(c => new { c.Firstname, c.LastName, c.IsAdmin });
var groups2 = List2
    .GroupBy(c => new { c.Firstname, c.LastName, c.IsAdmin });

var diffs = from g1 in groups1
            join g2 in groups2
            on g1.Key equals g2.Key into gj
            from g2 in gj.DefaultIfEmpty(Enumerable.Empty<Contact>())
            where g1.Count() > g2.Count()
            select new { g1, g2 };
List<Contact> allDiffs = diffs
    .SelectMany(x => x.g1.Take(x.g1.Count() - x.g2.Count()))
    .ToList();
有帮助吗?

解决方案

Maybe you want all items that are in list1 but not in list2 even those which are in list2 but not in the same amount, try this:

var groups1 = List1
    .GroupBy(c => new { c.Firstname, c.LastName, c.IsAdmin });
var groups2 = List2
    .GroupBy(c => new { c.Firstname, c.LastName, c.IsAdmin });

var diffs = from g1 in groups1
            join g2 in groups2
            on g1.Key equals g2.Key into gj
            from g2 in gj.DefaultIfEmpty(Enumerable.Empty<Contact>())
            where g1.Count() > g2.Count()
            select new { g1, g2 };
List<Contact> allDiffs = diffs
    .SelectMany(x => x.g1.Take(x.g1.Count() - x.g2.Count()))
    .ToList();

( edit: i hope that there is an easier way but it works )

其他提示

If you need common item(s) in the both list then remove the negation operator

List<Contact> Difference =
Contact_List2.Where(Second =>
    Contact_List1.Any(First =>
        First.FirstName == Second.FirstName
        && First.LastName == Second.LastName
        && First.IsAdmin == Second.IsAdmin))
.ToList();

What you really want here is an intersection, there is an Intersect method available with LINQ but for it to work with custom objects you would need to implement IEquatable<T> (or implement your own custom IEqualityComparer<T>).

This would allow you to simply call var difference = List2.Intersect(List1), however, as to the reason why your code currently fails...

List<Contact> Difference = Contact_List2.Where(Second =>
    !Contact_List1.Any(First =>
        First.FirstName == Second.FirstName
        && First.LastName == Second.LastName
        && First.IsAdmin == Second.IsAdmin))
    .ToList();

You are asking for all the records in List2 which aren't in List1 - what you want is all the records in List2 which are also in List1.

To fix, just remove the ! (not) operator from the Any check i.e.

Contact_List2.Where(x => Contact_List1.Any(...));

Alternatively, you could use Enumerable.Intersect with a custom equality comparer. It will produce an IEnumerable<T> of the items that are found in both lists. You would use it this way:

var matching = List1.Intersect(List2, new ContactComparer());

And the custom comparer (shamelessly lifted and adapted from the MSDN docs):

public sealed class ContactComparer : IEqualityComparer<Contact>
{
    public bool Equals(Contact x, Contact y)
    {
        if (Object.ReferenceEquals(x, y)) return true;

        if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
            return false;

        return x.FirstName == y.FirstName && 
               x.LastName == y.LastName &&
               x.IsAdmin == y.IsAdmin;
    }

    public int GetHashCode(Contact contact)
    {
        if (Object.ReferenceEquals(contact, null)) return 0;

        //Get hash code for the Name field if it is not null. 
        int hashContactFirst = contact.FirstName == null ? 0 : contact.FirstName.GetHashCode();

        //Get hash code for the Code field. 
        int hashContactLast = contact.LastName == null ? 0 : contact.LastName.GetHashCode()

        int hashAdmin = contact.IsAdmin.GetHashCode();

        return hashContactFirst ^ hashContactLast ^ hashAdmin;
    }
}

I would personally favour this route because it creates more readable linq that is easier to understand with a quick glance.

I'm not sure how easy it is to do using LINQ alone. I think the below code gives the result you're after. Of course it's actually changing List1 so you'll need to work on a copy if you need the original list intact.

        foreach (Contact contact2 in List2)
        {
            List1.Remove(List1.FirstOrDefault(contact1 => contact1.FirstName == contact2.FirstName
                                                          && contact1.LastName == contact2.LastName
                                                          && contact1.IsAdmin == contact2.IsAdmin));
        }
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top