IEqualityComparer for anonymous type

2020-01-30 07:27发布

问题:

I have this

 var n = ItemList.Select(s => new { s.Vchr, s.Id, s.Ctr, s.Vendor, s.Description, s.Invoice }).ToList();
 n.AddRange(OtherList.Select(s => new { s.Vchr, s.Id, s.Ctr, s.Vendor, s.Description, s.Invoice }).ToList(););

I would like to do this if it where allowed

n = n.Distinct((x, y) => x.Vchr == y.Vchr)).ToList();

I tried using the generic LambdaComparer but since im using anonymous types there is no type associate it with.

"Help me Obi Wan Kenobi, you're my only hope"

回答1:

The trick is to create a comparer that only works on inferred types. For instance:

public class Comparer<T> : IComparer<T> {
  private Func<T,T,int> _func;
  public Comparer(Func<T,T,int> func) {
    _func = func;
  }
  public int Compare(T x,  T y ) {
    return _func(x,y);
  }
}

public static class Comparer {
  public static Comparer<T> Create<T>(Func<T,T,int> func){ 
    return new Comparer<T>(func);
  }
  public static Comparer<T> CreateComparerForElements<T>(this IEnumerable<T> enumerable, Func<T,T,int> func) {
    return new Comparer<T>(func);
  }
}

Now I can do the following ... hacky solution:

var comp = n.CreateComparerForElements((x, y) => x.Vchr == y.Vchr);


回答2:

Most of the time when you compare (for equality or sorting) you're interested in choosing the keys to compare by, not the equality or comparison method itself (this is the idea behind Python's list sort API).

There's an example key equality comparer here.



回答3:

I note that JaredPar's answer does not quite answer the question since the set methods like Distinct and Except require an IEqualityComparer<T> not an IComparer<T>. The following assumes that an IEquatable will have a suitable GetHashCode, and it certainly has a suitable Equals method.

public class GeneralComparer<T, TEquatable> : IEqualityComparer<T>
{
    private readonly Func<T, IEquatable<TEquatable>> equatableSelector;

    public GeneralComparer(Func<T, IEquatable<TEquatable>> equatableSelector)
    {
        this.equatableSelector = equatableSelector;
    }

    public bool Equals(T x, T y)
    {
        return equatableSelector.Invoke(x).Equals(equatableSelector.Invoke(y));
    }

    public int GetHashCode(T x)
    {
        return equatableSelector(x).GetHashCode();
    }
}

public static class GeneralComparer
{
    public static GeneralComparer<T, TEquatable> Create<T, TEquatable>(Func<T, TEquatable> equatableSelector)
    {
        return new GeneralComparer<T, TEquatable>(equatableSelector);
    }
}

Where the same inference from a static class trick is used as in JaredPar's answer.

To be more general you could provide two Funcs: a Func<T, T, bool> to check equality and Func<T, T, int> to select a hash code.