0

I'm trying to implement an IEqualityComparer for my object that basically detects if an object is older that another one. The following simpler example will synthesises what i'm trying to accomplish:

class Program
{
    static void Main(string[] args)
    {
        var authorsList = new List<Author>()
        {
            new Author{ Firstname = "Bob", Lastname = "Smith", Age=11 },
            new Author{ Firstname = "Bob", Lastname = "Smith", Age=20 },
            new Author{ Firstname = "Bob", Lastname = "Smith", Age=12 },
            new Author{ Firstname = "Bob", Lastname = "Smith", Age=14 },
            new Author{ Firstname = "Bob", Lastname = "Smith", Age=12 },
            new Author{ Firstname = "Fred", Lastname = "Smith", Age=12 },
            new Author{ Firstname = "Trevor", Lastname = "Smith", Age=15 },
            new Author{ Firstname = "Brian", Lastname = "Smith", Age=11 },
            new Author{ Firstname = "Billy", Lastname = "Smith", Age=11 },
        };
        var authorsListExcept = new List<Author>()
        {
            new Author{ Firstname = "Bob", Lastname = "Smith", Age=12 },
            new Author{ Firstname = "Fred", Lastname = "Smith", Age=12 },
        };
        var authorsList2 = authorsList
            .Except(authorsListExcept, new AuthorUpdatedComparer()).ToList();
    }
}

class Author
{
    public string Firstname { get; set; }
    public string Lastname { get; set; }
    public int Age { get; set; }
}

class AuthorUpdatedComparer : IEqualityComparer<Author>
{
    public bool Equals(Author x, Author y)
    {
        return x.Age >= y.Age;
    }

    public int GetHashCode(Author obj)
    {
        //Check whether the object is null 
        if (Object.ReferenceEquals(obj, null)) return 0;

        int FirstnameHash = (obj.Firstname ?? "").GetHashCode();
        int LastnameHash = (obj.Lastname ?? "").GetHashCode();
        int finalResult = FirstnameHash ^ LastnameHash;

        return finalResult;
    }
}

My authorsList2 result would be: * Bob Smith with age 20 * Bom Smith with age 14 * Trevor Smith with age 15 * Brian Smith with age 11 * Billy Smith with age 11

But instead of this the Bob Smith with age 14 is not included. When debugging I reached the conclusion that the Comparer after included Bob Smith with Age 20 start to using it has a comparer excluding then after all the Bob's younger than 20.

This is a strange behavior in my point of view it should only excluded the ones that are younger or with the same age to those included on the authorsListExcept. I tried to read msdn documentation and what I want it should be supposed to happen: font: http://msdn.microsoft.com/en-us/library/bb336390(v=vs.100).aspx

Anyone can help me? Thanks, Hugo Salgado

  • 3
    Your Equals implementation doesn't make much sense to me. >= is dependent on the previous sorting of the list. When comparing 20 to 14 you get different results depending into which parameter x or y they go. There is no chance to get stable results with this. In an EqualityComparer compare for equality and nothing else. – Ralf Sep 19 '13 at 15:13
  • Do you have other sugestion to do what I want. I'm willing to change my code if there is a better approach. – Hugo Salgado Sep 19 '13 at 15:21
  • @HugoSalgado: It is not even clear what you want to achieve here. Please provide the expected output and explain it. Right now, what you have directly under the source code makes no sense to me in general and in particular, there is no `Bom Smith` anywhere. – Daniel Hilgarth Sep 19 '13 at 15:23
  • @Ralf my goal is to have the following result: * Bob Smith with age 20 * Bob Smith with age 14 * Trevor Smith with age 15 * Brian Smith with age 11 * Billy Smith with age 11 – Hugo Salgado Sep 19 '13 at 15:27
  • @Ralf I want to accomplish a way that detects if a given Author has the same name of any of a List and if is yourger or with the same age to the ones that match on the List. – Hugo Salgado Sep 19 '13 at 15:28

2 Answers2

1

The following LINQ query provides the result you expect:

var result = 
    authorsList.GroupBy(x => Tuple.Create(x.Firstname, x.Lastname))
               .SelectMany(g => g.Where(x => authorsListExcept.All(e => e.Firstname != x.Firstname || e.Lastname != x.Lastname || e.Age < x.Age)));

The following query also produces this result. It should perform better:

var result = 
    authorsList.GroupBy(x => Tuple.Create(x.Firstname, x.Lastname))
               .GroupJoin(authorsListExcept, x => x.Key,
                          x => Tuple.Create(x.Firstname, x.Lastname),
                          (a, e) => a.Where(x => x.Age > e.Select(z => z.Age)
                                                          .DefaultIfEmpty(0)
                                                          .Max()))
               .SelectMany(x => x)

And a third option (the same as the previous but in query syntax):

var result = 
    (from a in authorsList
    group a by Tuple.Create(a.Firstname, a.Lastname) into g
    join e in authorsListExcept on g.Key equals Tuple.Create(e.Firstname, e.Lastname) into er
    from age in er.Select(x => x.Age).DefaultIfEmpty()
    select g.Where(x => x.Age > age)).SelectMany(x => x);

The interface IEqualityComparer<T> is there to check for equality. It has nothing to do with any ordering. As such, you can't use it in the way you try.

In general: An implementation of this interface should always use the exact same set of properties in both the GetHashCode implementation and the Equals method.

Daniel Hilgarth
  • 171,043
  • 40
  • 335
  • 443
  • 1
    @TimSchmelter: I don't follow. But I think the OP didn't really grasp the concept of that interface in the first place. It looks like he wants to somehow define an ordering and filtering at the same time. – Daniel Hilgarth Sep 19 '13 at 15:10
  • Thank you all for the quick replies... So basically you are stating that I need to use all the First and Last names Something like this: public bool Equals(Author x, Author y) { if(x.Firstname == y.Firstname && x.Lastname == y.Lastname) return x.Age >= y.Age; return false; } This still doesn't work... I can't use Age on GetHash method because I have no way to compare it. If you think that this is not the best approach feel free to give me a work around. Again thank you all Hugo – Hugo Salgado Sep 19 '13 at 15:17
  • 1
    No, this is a comparer for *equality*. Your `>=` makes absolutely no sense here. – Daniel Hilgarth Sep 19 '13 at 15:20
  • So is there other way to check if an Author has the same name and is older than and then remove from the list? – Hugo Salgado Sep 19 '13 at 15:29
  • Thanks Daniel... I understand that I should not be using Except but rather perform a query that gives me the results I wanted. Meanwhile I reached myself to a solution a bit different from yours. Just curiosity which one you think has better performance? Yours or my solution: var result = authorsList .Where(g => !authorsListExcept.Any(ale => ale.Firstname == g.Firstname && ale.Lastname == g.Lastname && ale.Age >= g.Age)) .ToList(); – Hugo Salgado Sep 19 '13 at 15:45
  • @HugoSalgado: Your solution is very similar to my first solution. I think it performs worse then the other two solutions I posted, but I also think that the difference is so small you don't need to care, especially with only the little amount of items you got. You should choose the solution you find easier to read and understand. In my case, that would be solution 2. – Daniel Hilgarth Sep 19 '13 at 15:53
  • In Ticks from my machine Daniel1 = 444, Daniel2 = 690, Hugo1 = 341. With the data provided in the example – Ralf Sep 19 '13 at 15:57
  • Many thanks to all... especially to Daniel and Ralf. Resuming I was trying to catch a fly with a cannon. Many times the simplest way is right in front of you. I will go to use the query. Again thank you all – Hugo Salgado Sep 19 '13 at 16:01
0

If i have understood what you want try this.

class Program
{
    static void Main(string[] args)
    {
        var authorsList = new List<Author>()
        {
            new Author{ Firstname = "Bob", Lastname = "Smith", Age=11 },
            new Author{ Firstname = "Bob", Lastname = "Smith", Age=20 },
            new Author{ Firstname = "Bob", Lastname = "Smith", Age=12 },
            new Author{ Firstname = "Bob", Lastname = "Smith", Age=14 },
            new Author{ Firstname = "Bob", Lastname = "Smith", Age=12 },
            new Author{ Firstname = "Fred", Lastname = "Smith", Age=12 },
            new Author{ Firstname = "Trevor", Lastname = "Smith", Age=15 },
            new Author{ Firstname = "Brian", Lastname = "Smith", Age=11 },
            new Author{ Firstname = "Billy", Lastname = "Smith", Age=11 },
        };
        var authorsListExcept = new List<Author>()
        {
            new Author{ Firstname = "Bob", Lastname = "Smith", Age=12 },
            new Author{ Firstname = "Fred", Lastname = "Smith", Age=12 },
        };

        var authorsList2 = authorsList.Where(x => !authorsListExcept.Any(y => y.Firstname == x.Firstname && y.Lastname == x.Lastname && x.Age <= y.Age));
    }
}

public class Author
{
    public string Firstname { get; set; }
    public string Lastname { get; set; }
    public int Age { get; set; }
}
Ralf
  • 1,216
  • 10
  • 20