对,所以我有一个可枚举的,希望从中得到不同的值。
使用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的实现集成到单个接口中。
我想这是有道理的。
以下是如何做到的:
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"}
};