Question

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();
Was it helpful?

Solution

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 )

OTHER TIPS

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));
        }
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top