用lambda区分()?

对,所以我有一个enumerable,并希望从中获得不同的值。

使用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返回值(否则在内部使用的哈希表将无法正常工作)。我们使用IEqualityComper,因为它包兼容Equals和GetHashCode在单个接口中的实现。

我想这是有道理的。

458304 次浏览

在我看来,你想从更多LINQ中得到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;}}}

不,没有这样的扩展方法重载。我过去发现这让我自己很沮丧,因此我通常编写一个辅助类来处理这个问题。目标是将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));

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

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

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

我用过的东西对我很有效。

/// <summary>/// A class to wrap the IEqualityComparer interface into matching functions for simple implementation/// </summary>/// <typeparam name="T">The type of object to be compared</typeparam>public class MyIEqualityComparer<T> : IEqualityComparer<T>{/// <summary>/// Create a new comparer based on the given Equals and GetHashCode methods/// </summary>/// <param name="equals">The method to compute equals of two T instances</param>/// <param name="getHashCode">The method to compute a hashcode for a T instance</param>public MyIEqualityComparer(Func<T, T, bool> equals, Func<T, int> getHashCode){if (equals == null)throw new ArgumentNullException("equals", "Equals parameter is required for all MyIEqualityComparer instances");EqualsMethod = equals;GetHashCodeMethod = getHashCode;}/// <summary>/// Gets the method used to compute equals/// </summary>public Func<T, T, bool> EqualsMethod { get; private set; }/// <summary>/// Gets the method used to compute a hash code/// </summary>public Func<T, int> GetHashCodeMethod { get; private set; }
bool IEqualityComparer<T>.Equals(T x, T y){return EqualsMethod(x, y);}
int IEqualityComparer<T>.GetHashCode(T obj){if (GetHashCodeMethod == null)return obj.GetHashCode();return GetHashCodeMethod(obj);}}

这将做你想要的,但我不知道性能:

var distinctValues =from cust in myCustomerListgroup cust by cust.CustomerIdinto gcustselect gcust.First();

至少它不是冗长的。

IEnumerable<Customer> filteredList = originalList.GroupBy(customer => customer.CustomerId).Select(group => group.First());

如果Distinct()没有产生唯一的结果,试试这个:

var filteredWC = tblWorkCenter.GroupBy(cc => cc.WCID_I).Select(grp => grp.First()).Select(cc => new Model.WorkCenter { WCID = cc.WCID_I }).OrderBy(cc => cc.WCID);
ObservableCollection<Model.WorkCenter> WorkCenter = new ObservableCollection<Model.WorkCenter>(filteredWC);

这是一个简单的扩展方法,可以做我需要的事情…

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

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

我在这里看到的所有解决方案都依赖于选择一个已经可以比较的字段。但是,如果需要以不同的方式进行比较,这个解决方案似乎通常有效,例如:

somedoubles.Distinct(new LambdaComparer<double>((x, y) => Math.Abs(x - y) < double.Epsilon)).Count()

速记解

myCustomerList.GroupBy(c => c.CustomerId, (key, c) => c.FirstOrDefault());

您可以使用内联比较r

public class InlineComparer<T> : IEqualityComparer<T>{//private readonly Func<T, T, bool> equalsMethod;//private readonly Func<T, int> getHashCodeMethod;public Func<T, T, bool> EqualsMethod { get; private set; }public Func<T, int> GetHashCodeMethod { get; private set; }
public InlineComparer(Func<T, T, bool> equals, Func<T, int> hashCode){if (equals == null) throw new ArgumentNullException("equals", "Equals parameter is required for all InlineComparer instances");EqualsMethod = equals;GetHashCodeMethod = hashCode;}
public bool Equals(T x, T y){return EqualsMethod(x, y);}
public int GetHashCode(T obj){if (GetHashCodeMethod == null) return obj.GetHashCode();return GetHashCodeMethod(obj);}}

使用示例

  var comparer = new InlineComparer<DetalleLog>((i1, i2) => i1.PeticionEV == i2.PeticionEV && i1.Etiqueta == i2.Etiqueta, i => i.PeticionEV.GetHashCode() + i.Etiqueta.GetHashCode());var peticionesEV = listaLogs.Distinct(comparer).ToList();Assert.IsNotNull(peticionesEV);Assert.AreNotEqual(0, peticionesEV.Count);

来源:https://stackoverflow.com/a/5969691/206730
为联合使用IEqualityComper
我可以内联指定我的显式类型比较器吗?

Microsoft System. Interactive软件包有一个使用键选择器lambda的版本,这实际上与Jon Skeet的解决方案相同,但它可能有助于人们了解并查看库的其余部分。

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 ListList<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 NameEmployee e2 = new Employee { Name = "Adam  Warren", EmployeeID = 823456 };lst.Add(e2);//Name is different caseEmployee e3 = new Employee { Name = "adam warren", EmployeeID = 823456 };lst.Add(e3);
//Distinct (without IEqalityComparer<T>) - Returns 4 employeesvar lstDistinct1 = lst.Distinct();
//Lambda Extension - Return 2 employeesvar lstDistinct = lst.Distinct(employee => employee.EmployeeID.GetHashCode() ^ employee.Name.ToUpper().Replace(" ", "").GetHashCode());

我认为大多数像我一样来到这里的人都希望最简单解决方案可能不使用任何库和最好的性能

(对我来说,接受的方法组在性能方面是一种过度杀戮。

这是一个使用不均衡性比较器接口的简单扩展方法,它也适用于空值。

用法:

var filtered = taskList.DistinctBy(t => t.TaskExternalId).ToArray();

扩展方法代码

public static class LinqExtensions{public static IEnumerable<T> DistinctBy<T, TKey>(this IEnumerable<T> items, Func<T, TKey> property){GeneralPropertyComparer<T, TKey> comparer = new GeneralPropertyComparer<T,TKey>(property);return items.Distinct(comparer);}}public class GeneralPropertyComparer<T,TKey> : IEqualityComparer<T>{private Func<T, TKey> expr { get; set; }public GeneralPropertyComparer (Func<T, TKey> expr){this.expr = expr;}public bool Equals(T left, T right){var leftProp = expr.Invoke(left);var rightProp = expr.Invoke(right);if (leftProp == null && rightProp == null)return true;else if (leftProp == null ^ rightProp == null)return false;elsereturn leftProp.Equals(rightProp);}public int GetHashCode(T obj){var prop = expr.Invoke(obj);return (prop==null)? 0:prop.GetHashCode();}}

一个棘手的方法是使用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);

分组方式解决方案使用ToLookup()

var distincts = customers.ToLookup(c => c.CustomerId).Select(g => g.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"}};

换一种方式:

var distinctValues = myCustomerList.Select(x => x._myCaustomerProperty).Distinct();

序列返回不同的元素,通过属性“_myCaustomerProperty”比较它们。

您可以使用LambdaEqualityComper:

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

我认为这是最简单的解决方案。

public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector){return source.GroupBy(keySelector).Select(x => x.FirstOrDefault());}

从. NET 6或更高版本中,有一个新的内置方法数不胜数。区分来实现这一点。

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