0

this is my Clients class:

public class Clients
{
    public string Email { get; set; }
    public string Name { get; set; }


    public Clients(string e, string n)
    {
        Email = e;
        Name = n;
    }

I want to make a new list which contains the same clients from List A and List B . For example: List A - John, Jonathan, James .... List B - Martha, Jane, Jonathan .... Unsubscribers - Jonathan

    public static List<Clients> SameClients(List<Clients> A, List<Clients> B)
    {
        List<Clients> Unsubscribers = new List<Clients>();
        Unsubscribers = A.Intersect(B).ToList();
        return Unsubscribers;
    }

However for some reasons I get empty list and I have no idea what's wrong.

Andrius
  • 21
  • 2
  • 5
  • 1
    You have compared classes, not properties of that classes. You need to override `Equal` and `GetHashCode` in your `Client` class. Here is an example https://stackoverflow.com/a/38434457/2946329 – Salah Akbari Dec 08 '17 at 09:00
  • Are you sure it's the same instance of `Jonathan` in both lists? It sounds like they're not the same so of course the list is empty. To compare lists of classes implement your own comparer. – Equalsk Dec 08 '17 at 09:00

2 Answers2

4

The problem is that when you are comparing objects Equals and Gethashcode are used to compare them. You can override these two methods and provide your own implementation based on your needs...there is already an answer below covering how to override these two methods

However, normally I prefer to keep my entities/models (or whatever you want to call them) very simple and keep comparison implementation details away from my models. In that case, you can implement an IEqualityComparer<TSource> and use an overload of Intersects that takes in an IEqualityComparer

Here's an example implementation of IEqualityComprarer based on only the Name property...

public class ClientNameEqualityComparer : IEqualityComparer<Clients>
{
    public bool Equals(Clients c1, Clients c2)
    {
        if (c2 == null && c1 == null)
           return true;
        else if (c1 == null | c2 == null)
           return false;
        else if(c1.Name == c2.Name)
            return true;
        else
            return false;
    }

    public int GetHashCode(Client c)
    {
        return c.Name.GetHashCode();
    }
}

Basically, the implementation above only cares about the Name property, if two instances of Clients have the same value for the Name property, then they are considered equal.

Now you can do the followig...

A.Intersect(B, new ClientNameEqualityComparer()).ToList();

And that will produce the results you are expecting...

Leo
  • 14,625
  • 2
  • 37
  • 55
1

Intersect uses GetHashCode and Equals by default, but you haven't overriden it, so Object.Equals is used which just compares references. Since all your client-instances are initialized with new they are separate instances even if they have equal values. That's why Intersect "thinks" that there are no common clients.

So you have several options.

  • implement a custom IEqualityComparer<Clients> and pass that to Intersect(or many other LINQ methods). This has the advantage that you could implement different comparer for different requirements and you don't need to modify the original class
  • let Clients override Equals and GetHashCode and /or
  • let Clients implement IEquatable<Clients>

For example(showing the last two because other answer showed already IEqualityComparer<T>):

public class Clients : IEquatable<Clients>
{
    public string Email { get; set; }
    public string Name { get; set; }


    public Clients(string e, string n)
    {
        Email = e;
        Name = n;
    }

    public override bool Equals(object obj)
    {
        return obj is Clients && this.Equals((Clients)obj);
    }

    public bool Equals(Clients other)
    {
        return Email == other?.Email == true
            && Name == other?.Name == true;
    }

    public override int GetHashCode()
    {
        unchecked 
        {
            int hash = 17;
            hash = hash * 23 + (Email?.GetHashCode() ?? 0);
            hash = hash * 23 + (Name?.GetHashCode() ?? 0);
            return hash;
        }
    }
}

Worth reading:

Differences between IEquatable<T>, IEqualityComparer<T>, and overriding .Equals() when using LINQ on a custom object collection?

Tim Schmelter
  • 450,073
  • 74
  • 686
  • 939
  • thank you very much, now Unsubscribers list returns Name, what do I need to change, so Unsubscribers list returns Email value? – Andrius Dec 08 '17 at 09:32
  • @Andrius: what means `Unsubscribers` "returns `Name`"? That's a list which doesn't return a name. This list contains `Clients`-instances that are common in both other lists. Every `Clients`-instance has a `Name` and `Email` property. – Tim Schmelter Dec 08 '17 at 09:34
  • I'm sorry, I meant this method: public static List SameClients(List A, List B) { List Unsubscribers = new List(); Unsubscribers = A.Intersect(B).ToList(); return Unsubscribers; } Now when I return Unsubscribers, I get only Names, but what if I want to get Emails ? – Andrius Dec 08 '17 at 09:37