IEnumerable.Except() and a custom comparer

2019-07-24 04:03发布

问题:

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);
        }
    }

回答1:

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());
    


回答2:

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)
    {
    }