Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

IEnumerable.Except() and a custom comparer

I'm having troubles with the Except() method. Instead of returning the difference, it returns the original set.

I've tried implementing the IEquatable and IEqualityComparer in the Account class. I've also tried creating a separate IEqualityComparer class for Account.

When the Except() method is called from main, it doesn't seem to call my custom Equals() method, but when I tried Count(), it did call the custom GetHashCode() method!

I'm sure I made a trivial mistake somewhere and I hope a fresh pair of eyes can help me.

main:

IEnumerable<Account> everyPartnerID = 
    from partner in dataContext.Partners
    select new Account { IDPartner = partner.ID, Name = partner.Name };


IEnumerable<Account> hasAccountPartnerID = 
    from partner in dataContext.Partners
    from account in dataContext.Accounts
    where
        !partner.ID.Equals(Guid.Empty) &&
        account.IDPartner.Equals(partner.ID) &&
        account.Username.Equals("Special")
    select new Account { IDPartner = partner.ID, Name = partner.Name };

IEnumerable<Account> noAccountPartnerID = 
    everyPartnerID.Except(
        hasAccountPartnerID, 
        new LambdaComparer<Account>((x, y) => x.IDPartner.Equals(y.IDPartner)));

Account:

    public class Account : IEquatable<Account>
    {
        public Guid IDPartner{ get; set; }
        public string Name{ get; set; }

/*        #region IEquatable<Account> Members

        public bool Equals(Account other)
        {
            return this.IDPartner.Equals(other.IDPartner);
        }

        #endregion*/
    }

LambdaComparer:

   public class LambdaComparer<T> : IEqualityComparer<T>
    {
        private readonly Func<T, T, bool> _lambdaComparer;
        private readonly Func<T, int> _lambdaHash;

        public LambdaComparer(Func<T, T, bool> lambdaComparer) :
            this(lambdaComparer, o => o.GetHashCode())
        {
        }

        public LambdaComparer(Func<T, T, bool> lambdaComparer, Func<T, int> lambdaHash)
        {
            if (lambdaComparer == null)
                throw new ArgumentNullException("lambdaComparer");
            if (lambdaHash == null)
                throw new ArgumentNullException("lambdaHash");

            _lambdaComparer = lambdaComparer;
            _lambdaHash = lambdaHash;
        }

        public bool Equals(T x, T y)
        {
            return _lambdaComparer(x, y);
        }

        public int GetHashCode(T obj)
        {
            return _lambdaHash(obj);
        }
    }

2 Answers

Basically your LambdaComparer class is broken when you pass in just a single function, because it uses the "identity hash code" provider if you don't provide anything else. The hash code is used by Except, and that's what's causing the problem.

Three options here:

  1. Implement your own ExceptBy method and then preferably contribute it to MoreLINQ which contains that sort of thing.

  2. Use a different implementation of IEqualityComparer<T>. I have a ProjectionEqualityComparer class you can use in MiscUtil - or you can use the code as posted in another question.

  3. Pass a lambda expression into your LambdaComparer code to use for the hash:

    new LambdaComparer<Account>((x, y) => x.IDPartner.Equals(y.IDPartner)),
                                x => x.IDPartner.GetHashCode());
    
like image 87
Jon Skeet Avatar answered Dec 08 '25 05:12

Jon Skeet


You could also quickly fix your LambdaComparer to work when only the equality parameters are supplied like this:

    public LambdaComparer(Func<T, T, bool> lambdaComparer) :
        this(lambdaComparer, o => 1)
    {
    }
like image 39
Alex M Avatar answered Dec 08 '25 05:12

Alex M



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!