对,所以我有一个可枚举并希望从中获得不同的值。
使用 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 对 post 的回复。他说:
您将遇到的问题是,当两个对象比较相等时,它们必须具有相同的 GetHashCode 返回值(否则 Distinct 内部使用的哈希表将无法正常工作)。我们使用 IEqualityComparer 是因为它将 Equals 和 GetHashCode 的兼容实现打包到一个接口中。
我想这是有道理的。
.Distinct(new KeyEqualityComparer<Customer,string>(c1 => c1.CustomerId))
的 solution,并解释为什么 GetHashCode() 对正常工作很重要。
IEnumerable<Customer> filteredList = originalList
.GroupBy(customer => customer.CustomerId)
.Select(group => group.First());
在我看来,您想从 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;
}
}
}
yield
+ 额外库的 VB_Complainers,foreach 可以重写为 return source.Where(element => knownKeys.Add(keySelector(element)));
收拾东西。我认为大多数像我一样来到这里的人都想要最简单的解决方案,而无需使用任何库并具有最佳性能。
(对我来说,我认为按方法接受的分组在性能方面是过度的。)
这是一个使用 IEqualityComparer 接口的简单扩展方法,它也适用于空值。
用法:
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;
else
return leftProp.Equals(rightProp);
}
public int GetHashCode(T obj)
{
var prop = expr.Invoke(obj);
return (prop==null)? 0:prop.GetHashCode();
}
}
速记解决方案
myCustomerList.GroupBy(c => c.CustomerId, (key, c) => c.FirstOrDefault());
不,没有这样的扩展方法重载。过去我发现这让自己很沮丧,因此我通常会编写一个帮助类来处理这个问题。目标是将 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));
IEqualityComparer<T>
更容易:stackoverflow.com/questions/188120/…
<T,T>
替换为 <T>
才能正常工作。否则会出现编译错误。我错过了什么吗?
这是一个简单的扩展方法,可以满足我的需要......
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);
}
}
很遗憾他们没有在框架中加入这样的独特方法,但是嘿嘿。
x.Key
更改为 x.First()
并将返回值更改为 IEnumerable<T>
这会做你想要的,但我不知道性能:
var distinctValues =
from cust in myCustomerList
group cust by cust.CustomerId
into gcust
select gcust.First();
至少它不冗长。
从 .Net 6(Preview 7)或更高版本开始,有一个新的内置方法 Enumerable.DistinctBy 可以实现此目的。
var distinctValues = myCustomerList.DistinctBy(c => c.CustomerId);
// With IEqualityComparer
var distinctValues = myCustomerList.DistinctBy(c => c.CustomerId, someEqualityComparer);
我用过的东西对我很有效。
/// <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);
}
}
我在这里看到的所有解决方案都依赖于选择一个已经相当的字段。但是,如果需要以不同的方式进行比较,this solution here 似乎通常可以工作,例如:
somedoubles.Distinct(new LambdaComparer<double>((x, y) => Math.Abs(x - y) < double.Epsilon)).Count()
采取另一种方式:
var distinctValues = myCustomerList.
Select(x => x._myCaustomerProperty).Distinct();
序列返回不同的元素,通过属性 '_myCaustomerProperty' 比较它们。
您可以使用 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;
}
您可以使用 InlineComparer
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
Using IEqualityComparer for Union
Can I specify my explicit type comparator inline?
如果 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);
OrderBy
部分的想法非常有用。我这样使用它:instrumentList.AddRange(gridInstrumentList.OrderByDescending(g => g.ID).Distinct());
一个棘手的方法是使用 Aggregate()
扩展,使用字典作为累加器,key-property 值作为键:
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-style 解决方案正在使用 ToLookup()
:
var distincts = customers.ToLookup(c => c.CustomerId).Select(g => g.First());
Dictionary<int, Customer>
呢?
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());
我假设您有一个 IEnumerable,并且在您的示例委托中,您希望 c1 和 c2 引用此列表中的两个元素?
我相信您可以通过 self join var distinctResults = from c1 in myList join c2 in myList on 来实现这一点
Microsoft System.Interactive package 具有采用键选择器 lambda 的 Distinct 版本。这实际上与 Jon Skeet 的解决方案相同,但它可能有助于人们了解并查看图书馆的其余部分。
以下是您的操作方法:
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"))
);
NB 这也允许您指定其他函数,例如 .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")
);
NB 在这里,表达式更简单,但请注意 .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"}
};
我发现这是最简单的解决方案。
public static IEnumerable<TSource> DistinctBy<TSource, TKey>
(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
return source.GroupBy(keySelector).Select(x => x.FirstOrDefault());
}
DistinctBy
(甚至是Distinct
,因为签名是唯一的)。yield
语句,因此在技术上无法进行流式传输。不过谢谢你的回答。我将在用 C# 编码时使用它。 ;-)