3

Users class:

public class User  
{
    public int ID { get; set; }
    public string Email { get; set; }
}

Code:

        var usersL = new List<User>()
                        {
                            new User{ID = 1,Email = "abc@foo.com"},
                            new User{ID = 2,Email = "def@foo.com"}
                        };

        var usersR = new List<User>()
                        {
                            new User{ID = 1,Email = "abc@foo.com"},
                            new User{ID = 2,Email = "def@foo.com"}
                        };

        var both = (from l in usersL select l)
            .Intersect(from users in usersR select users);

        foreach (var r in both)
            Console.WriteLine(r.Email);

Which returns 0 results.

I know I can accomplish something similar by using join, but I want to use Intersect because A) this is eventually going to work on some DB code and we want to use this function (too long to go into why) and B) I'm just plain curious as to why Intersect isn't working here.

        var both = from l in usersL
                   join r in usersR on l.ID equals r.ID
                   select l;
WhiskerBiscuit
  • 4,795
  • 8
  • 62
  • 100
  • [Distinct not working with LINQ to Objects](http://stackoverflow.com/questions/1365748/distinct-not-working-with-linq-to-objects) : the same behavior observed due to the same reason, just different LINQ method (`Distinct()` there vs `Intersect()` here) – har07 Jul 18 '15 at 06:58
  • when you say intersect you want to intersect on the Id right ? In which case your code should read as var both = (from l in usersL select l.ID) .Intersect(from users in usersR select users.ID); – sundeep Jul 18 '15 at 07:05
  • because two instances are not equal. you must override Equals and GetHashCode methods. – M.kazem Akhgary Jul 18 '15 at 07:06
  • @M.kazemAkhgary why both Equals and GetHashCode need to override? I tried to override them individually, but didn't work. – Helic Jul 18 '15 at 07:26
  • @Helic [Why is it important to override GetHashCode when Equals method is overridden?](http://stackoverflow.com/questions/371328/why-is-it-important-to-override-gethashcode-when-equals-method-is-overridden) if you dont override gethashcode the equals method you overridden will never called. – M.kazem Akhgary Jul 18 '15 at 07:35

2 Answers2

1

.Net provides comparison logic for predefined types. In case of your join query your were joining (comparing) two IDs which were of type Int (predefined types)

var both = from l in usersL
           join r in usersR on l.ID equals r.ID
           select l;

In case of your intersect query you are trying to compare two user defined custom objects of type User. Hence you need to provide your own custom compare implementation logic.

There are 2 ways to tackle this...

Option 1: Implement the IEqualityComparer

public class User
{
    public int ID { get; set; }
    public string Email { get; set; }
}

public class MyEqualityComparer : IEqualityComparer<User>
{
    public bool Equals(User x, User y)
    {
        if (object.ReferenceEquals(x, y))
            return true;
        if (x == null || y == null)
            return false;
        return x.ID.Equals(y.ID) &&
               x.Email.Equals(y.Email);
    }

    public int GetHashCode(User u)
    {
        return new { u.ID, u.Email }.GetHashCode();
    }
}

class Program
{
    static void Main(string[] args)
    {

        var usersL = new List<User>()
                    {
                        new User{ID = 1,Email = "abc@foo.com"},
                        new User{ID = 2,Email = "def@foo.com"}
                    };

        var usersR = new List<User>()
                    {
                        new User{ID = 1,Email = "abc@foo.com"},
                        new User{ID = 2,Email = "def@foo.com"}
                    };

        var both =  (from l in usersL select l)
          .Intersect(from users in usersR select users, new MyEqualityComparer());

        foreach (var r in both)
            Console.WriteLine(r.Email);
    }
}

Option 2: Override the Equals and GetHashcode methods in the custom object itself

public class User 
{
    public int ID { get; set; }
    public string Email { get; set; }

    public override bool Equals(Object obj)
    {
        // Check for null values and compare run-time types.
        if (obj == null || GetType() != obj.GetType())
            return false;

        User x = (User)obj;
        return (ID == x.ID) && (Email == x.Email);
    }

    public override int GetHashCode()
    {
        return new { ID, Email }.GetHashCode();
    }
}

class Program
{
    static void Main(string[] args)
    {

        var usersL = new List<User>()
            {
                new User{ID = 1,Email = "abc@foo.com"},
                new User{ID = 2,Email = "def@foo.com"}
            };

        var usersR = new List<User>()
            {
                new User{ID = 1,Email = "abc@foo.com"},
                new User{ID = 2,Email = "def@foo.com"}
            };

        var both = (from l in usersL select l)
          .Intersect(from users in usersR select users);

        foreach (var r in both)
            Console.WriteLine(r.Email);
    }
}

Hope this helps.

sundeep
  • 1,792
  • 1
  • 15
  • 21
  • That works, but I would like to make the Comparer part of the class, so I implemented those methods directly against User and then removed the new MyEqualityComparer() from the Intersect statement. This does not work. The question is why? Doesn't Intersect look at the underlying object type and look for a IEqualityComparer implementation? – WhiskerBiscuit Jul 18 '15 at 17:32
  • 1
    You can make your User class itself implement IEqualityComparer and have it implement the two methods Equals and GetHashCode. In that case you will have use "var both = (from l in usersL select l) .Intersect(from users in usersR select users, new User());" – sundeep Jul 18 '15 at 17:44
  • I tried implementing IEqualityConstuctor in User and that didn't return any results. I'll post some code later. – WhiskerBiscuit Jul 18 '15 at 18:50
  • Just to add this as a separate question, do EF objects implement IEqualityComparper by default? I suspect they must, because I've interested two EF queries without specifying an overload. – WhiskerBiscuit Jul 18 '15 at 18:52
  • I have added to my earlier reply above and included the 2nd option whereby you can achieve the same without using the IEqualityComparer. – sundeep Jul 19 '15 at 03:17
0

This is in response to @sundeep who said "Now regards to your 2nd question... " (I wish you could link to comments) -- I'm just creating a new answer as I don't want to ruin the context of my original question

User class implementing IEqualityComparer

public class User : IEqualityComparer<User>
{
    public int ID { get; set; }
    public string Email { get; set; }

    public bool Equals(User x, User y)
    {
        if (object.ReferenceEquals(x, y))
            return true;
        if (x == null || y == null)
            return false;
        return x.ID.Equals(y.ID) &&
               x.Email.Equals(y.Email);
    }

    public int GetHashCode(User obj)
    {
        return new { obj.ID, obj.Email }.GetHashCode();
    }
}

Intersection returns no rows:

        var usersL = new List<User>()
                        {
                            new User{ID = 1,Email = "abc@foo.com"},
                            new User{ID = 2,Email = "def@foo.com"}
                        };

        var usersR = new List<User>()
                        {
                            new User{ID = 1,Email = "abc@foo.com"},
                            new User{ID = 2,Email = "def@foo.com"}
                        };

        var both = (from l in usersL select l)
            .Intersect(from users in usersR select users);

        foreach (var r in both)
            Console.WriteLine(r.Email);
WhiskerBiscuit
  • 4,795
  • 8
  • 62
  • 100
  • You are right... there is another way to achieve this. I have modified my reply above and provided the 2nd option without using the IEqualityComparer. – sundeep Jul 19 '15 at 03:15