对,所以我有一个可枚举的,希望从中得到不同的值。

使用System.Linq,当然有一个名为Distinct的扩展方法。在简单的情况下,它可以不带参数使用,例如:

var distinctValues = myStringList.Distinct();

很好,但如果我有一个可枚举的对象,需要为其指定相等,唯一可用的重载是:

var distinctValues = myCustomerList.Distinct(someEqualityComparer);

相等比较器参数必须是IEqualityComparer<T>的实例。当然,我可以做到这一点,但这有点冗长,而且,嗯,包含了很多内容。

我所期望的是一个重载,它会接受lambda,比如Func<T,T,bool>:

var distinctValues = myCustomerList.Distinct((c1, c2) => c1.CustomerId == c2.CustomerId);

有人知道是否存在这样的扩展,或者是否存在等效的解决方法?还是我错过了什么?

或者,有没有一种方法可以内联指定IEqualityComparer(让我尴尬)?

使现代化

我在MSDN论坛上找到了Anders Hejlsberg对此主题的回复。他说:

您将遇到的问题是,当两个对象进行比较时相等,它们必须具有相同的GetHashCode返回值(或Distinct内部使用的哈希表将无法正常工作)。我们使用IEqualityComparer,因为它包兼容Equals和GetHashCode的实现集成到单个接口中。

我想这是有道理的。


当前回答

您可以使用LambdaEqualityComparer:

var distinctValues
    = myCustomerList.Distinct(new LambdaEqualityComparer<OurType>((c1, c2) => c1.CustomerId == c2.CustomerId));


public class LambdaEqualityComparer<T> : IEqualityComparer<T>
    {
        public LambdaEqualityComparer(Func<T, T, bool> equalsFunction)
        {
            _equalsFunction = equalsFunction;
        }

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

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

        private readonly Func<T, T, bool> _equalsFunction;
    }

其他回答

一个棘手的方法是使用Aggregate()扩展,使用字典作为累加器,键属性值作为键:

var customers = new List<Customer>();

var distincts = customers.Aggregate(new Dictionary<int, Customer>(), 
                                    (d, e) => { d[e.CustomerId] = e; return d; },
                                    d => d.Values);

GroupBy风格的解决方案使用ToLookup():

var distincts = customers.ToLookup(c => c.CustomerId).Select(g => g.First());

这里有一个简单的扩展方法,可以满足我的需要。。。

public static class EnumerableExtensions
{
    public static IEnumerable<TKey> Distinct<T, TKey>(this IEnumerable<T> source, Func<T, TKey> selector)
    {
        return source.GroupBy(selector).Select(x => x.Key);
    }
}

遗憾的是,他们没有将这样的独特方法烘焙到框架中,但嘿嘿。

在我看来,你想从MoreLINQ得到DistinctBy。然后你可以写:

var distinctValues = myCustomerList.DistinctBy(c => c.CustomerId);

这里是DistinctBy的简化版本(没有无效性检查,也没有指定自己的密钥比较器的选项):

public static IEnumerable<TSource> DistinctBy<TSource, TKey>
     (this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
    HashSet<TKey> knownKeys = new HashSet<TKey>();
    foreach (TSource element in source)
    {
        if (knownKeys.Add(keySelector(element)))
        {
            yield return element;
        }
    }
}

您可以使用LambdaEqualityComparer:

var distinctValues
    = myCustomerList.Distinct(new LambdaEqualityComparer<OurType>((c1, c2) => c1.CustomerId == c2.CustomerId));


public class LambdaEqualityComparer<T> : IEqualityComparer<T>
    {
        public LambdaEqualityComparer(Func<T, T, bool> equalsFunction)
        {
            _equalsFunction = equalsFunction;
        }

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

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

        private readonly Func<T, T, bool> _equalsFunction;
    }

不,对此没有此类扩展方法重载。过去我发现这让我很沮丧,因此我通常会编写一个助手类来处理这个问题。目标是将Func<T,T,bool>转换为IEqualityComparer<T,T>。

实例

public class EqualityFactory {
  private sealed class Impl<T> : IEqualityComparer<T,T> {
    private Func<T,T,bool> m_del;
    private IEqualityComparer<T> m_comp;
    public Impl(Func<T,T,bool> del) { 
      m_del = del;
      m_comp = EqualityComparer<T>.Default;
    }
    public bool Equals(T left, T right) {
      return m_del(left, right);
    } 
    public int GetHashCode(T value) {
      return m_comp.GetHashCode(value);
    }
  }
  public static IEqualityComparer<T,T> Create<T>(Func<T,T,bool> del) {
    return new Impl<T>(del);
  }
}

这允许您编写以下内容

var distinctValues = myCustomerList
  .Distinct(EqualityFactory.Create((c1, c2) => c1.CustomerId == c2.CustomerId));