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

使用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的实现集成到单个接口中。

我想这是有道理的。


当前回答

这将满足您的需求,但我不知道性能:

var distinctValues =
    from cust in myCustomerList
    group cust by cust.CustomerId
    into gcust
    select gcust.First();

至少它并不冗长。

其他回答

以下是如何做到的:

public static class Extensions
{
    public static IEnumerable<T> MyDistinct<T, V>(this IEnumerable<T> query,
                                                    Func<T, V> f, 
                                                    Func<IGrouping<V,T>,T> h=null)
    {
        if (h==null) h=(x => x.First());
        return query.GroupBy(f).Select(h);
    }
}

此方法允许您通过指定一个参数(如.MyDistinct(d=>d.Name))来使用它,但也允许您将具有条件指定为第二个参数,如:

var myQuery = (from x in _myObject select x).MyDistinct(d => d.Name,
        x => x.FirstOrDefault(y=>y.Name.Contains("1") || y.Name.Contains("2"))
        );

注意:这也允许您指定其他函数,例如LastOrDefault(…)。


如果您只想公开条件,可以将其实现为:

public static IEnumerable<T> MyDistinct2<T, V>(this IEnumerable<T> query,
                                                Func<T, V> f,
                                                Func<T,bool> h=null
                                                )
{
    if (h == null) h = (y => true);
    return query.GroupBy(f).Select(x=>x.FirstOrDefault(h));
}

在这种情况下,查询将如下所示:

var myQuery2 = (from x in _myObject select x).MyDistinct2(d => d.Name,
                    y => y.Name.Contains("1") || y.Name.Contains("2")
                    );

注意:这里的表达式更简单,但请注意。MyDistinct2隐式使用.FirstOrDefault(…)。


注意:上面的示例使用了以下演示类

class MyObject
{
    public string Name;
    public string Code;
}

private MyObject[] _myObject = {
    new MyObject() { Name = "Test1", Code = "T"},
    new MyObject() { Name = "Test2", Code = "Q"},
    new MyObject() { Name = "Test2", Code = "T"},
    new MyObject() { Name = "Test5", Code = "Q"}
};

我假设您有一个IEnumerable<T>,在您的示例委托中,您希望c1和c2引用此列表中的两个元素?

我相信你可以通过自我加入实现这一点:

var distinctResults = from c1 in myList
                      join c2 in myList on <your equality conditions>

IEnumerable lambda扩展名:

public static class ListExtensions
{        
    public static IEnumerable<T> Distinct<T>(this IEnumerable<T> list, Func<T, int> hashCode)
    {
        Dictionary<int, T> hashCodeDic = new Dictionary<int, T>();

        list.ToList().ForEach(t => 
            {   
                var key = hashCode(t);
                if (!hashCodeDic.ContainsKey(key))
                    hashCodeDic.Add(key, t);
            });

        return hashCodeDic.Select(kvp => kvp.Value);
    }
}

用法:

class Employee
{
    public string Name { get; set; }
    public int EmployeeID { get; set; }
}

//Add 5 employees to List
List<Employee> lst = new List<Employee>();

Employee e = new Employee { Name = "Shantanu", EmployeeID = 123456 };
lst.Add(e);
lst.Add(e);

Employee e1 = new Employee { Name = "Adam Warren", EmployeeID = 823456 };
lst.Add(e1);
//Add a space in the Name
Employee e2 = new Employee { Name = "Adam  Warren", EmployeeID = 823456 };
lst.Add(e2);
//Name is different case
Employee e3 = new Employee { Name = "adam warren", EmployeeID = 823456 };
lst.Add(e3);            

//Distinct (without IEqalityComparer<T>) - Returns 4 employees
var lstDistinct1 = lst.Distinct();

//Lambda Extension - Return 2 employees
var lstDistinct = lst.Distinct(employee => employee.EmployeeID.GetHashCode() ^ employee.Name.ToUpper().Replace(" ", "").GetHashCode()); 

在.NET 6或更高版本中,有一个新的内置方法Enumerable.DistinctBy来实现这一点。

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

// With IEqualityComparer
var distinctValues = myCustomerList.DistinctBy(c => c.CustomerId, someEqualityComparer);

您可以使用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;
    }