Distinct()与lambda?

Tor*_*gen 714 c# lambda extension-methods c#-3.0

是的,所以我有一个可枚举的,并希望从中获得不同的值.

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

var distinctValues = myStringList.Distinct();
Run Code Online (Sandbox Code Playgroud)

好的,但如果我有一个可以指定相等性的可枚举对象,唯一可用的重载是:

var distinctValues = myCustomerList.Distinct(someEqualityComparer);
Run Code Online (Sandbox Code Playgroud)

equality comparer参数必须是.的实例IEqualityComparer<T>.当然,我可以做到这一点,但它有点冗长,而且很有说服力.

我所期望的是一个需要lambda的重载,比如Func <T,T,bool>:

var distinctValues
    = myCustomerList.Distinct((c1, c2) => c1.CustomerId == c2.CustomerId);
Run Code Online (Sandbox Code Playgroud)

任何人都知道是否存在某些此类扩展或某些等效的解决方法?或者我错过了什么?

或者,有没有一种方法可以指定IEqualityComparer内联(embarass me)?

更新

我找到了Anders Hejlsberg对MSDN论坛中关于这个主题的帖子的回复.他说:

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

我认为那是有道理的..

小智 982

IEnumerable<Customer> filteredList = originalList
  .GroupBy(customer => customer.CustomerId)
  .Select(group => group.First());
Run Code Online (Sandbox Code Playgroud)

  • @TorHaugen:请注意,创建所有这些组都需要花费成本.这不能对输入进行流式处理,最终会在返回任何内容之前缓冲所有数据.当然,这可能与你的情况无关,但我更喜欢DistinctBy的优雅:) (62认同)
  • 优秀!这也非常容易封装在扩展方法中,例如`DistinctBy`(甚至是`Distinct`,因为签名将是唯一的). (12认同)
  • 对我不起作用!&lt;方法“First”只能用作最终查询操作。考虑在此实例中使用方法“FirstOrDefault”。&gt; 即使我尝试过“FirstOrDefault”,它也不起作用。 (3认同)
  • @JonSkeet:这对于不想为一个功能导入额外库的VB.NET编码器来说已经足够了.如果没有ASync CTP,VB.NET不支持`yield`语句,因此在技术上不可能进行流式处理.谢谢你的回答.我在C#中编码时会使用它.;-) (2认同)
  • @BenGripka:那不太一样.它只为您提供客户ID.我想要整个客户:) (2认同)

Jon*_*eet 478

它看起来像你想要的更多DistinctBy来自MoreLINQ.然后你可以写:

var distinctValues = myCustomerList.DistinctBy(c => c.CustomerId);
Run Code Online (Sandbox Code Playgroud)

这是一个简化版本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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 我知道Jon Skeet只需阅读帖子的标题即可发布最佳答案.如果它与LINQ有关,Skeet是你的男人.阅读'C#In Depth'以达到上帝般的linq知识. (14认同)
  • @ sudhAnsu63这是LinqToSql(和其他linq提供者)的限制.LinqToX的目的是将您的C#lambda表达式转换为X的本机上下文.也就是说,LinqToSql将您的C#转换为SQL并尽可能本地执行该命令.这意味着如果无法在SQL(或者您使用的任何linq提供程序)中表达它,那么驻留在C#中的任何方法都不能"通过"linqProvider.我在扩展方法中看到这一点,将数据对象转换为视图模型.你可以通过"实现"查询来解决这个问题,在DistinctBy()之前调用ToList(). (5认同)
  • 很棒的答案!另外,对于所有关于`yield` + extra lib的VB_Complainers,foreach可以重写为`return source.Where(element => knownKeys.Add(keySelector(element)));` (2认同)
  • @Shimmy:我当然会欢迎...我不确定可行性。我可以在.NET Foundation中提出它... (2认同)
  • @Shimmy:Carlo 的答案可能适用于 LINQ to SQL...我不确定。 (2认同)

Ane*_*lou 37

总结一下.我想像我这样来到这里的大多数人都希望在不使用任何库和最佳性能的情况下实现最简单的解决方案.

(对我而言,接受的方法对我而言,我认为在表现方面是一种矫枉过正.)

这是一个使用IEqualityComparer接口的简单扩展方法,该接口也适用于空值.

用法:

var filtered = taskList.DistinctBy(t => t.TaskExternalId).ToArray();
Run Code Online (Sandbox Code Playgroud)

扩展方法代码

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 男人你是天才,如果我能,我现在会给你买啤酒:) (4认同)

Vij*_*mal 20

从 .NET 6 或更高版本开始,有一个新的内置方法Enumerable.DistinctBy可以实现此目的。

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

// With IEqualityComparer
var distinctValues = myCustomerList.DistinctBy(c => c.CustomerId, someEqualityComparer);
Run Code Online (Sandbox Code Playgroud)

  • 这应该是一个新的接受答案 (4认同)

Jar*_*Par 19

没有这样的扩展方法重载.我在过去发现这令人沮丧,因此我经常写一个助手类来处理这个问题.目标是将a转换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);
  }
}
Run Code Online (Sandbox Code Playgroud)

这允许您编写以下内容

var distinctValues = myCustomerList
  .Distinct(EqualityFactory.Create((c1, c2) => c1.CustomerId == c2.CustomerId));
Run Code Online (Sandbox Code Playgroud)

  • 这有一个令人讨厌的哈希代码实现.从投影中创建一个"IEqualityComparer <T>"更容易:http://stackoverflow.com/questions/188120/can-i-specify-my-explicit-type-comparator-inline (7认同)
  • (只是为了解释我对哈希码的评论 - 用这段代码很容易得到Equals(x,y)== true,但GetHashCode(x)!= GetHashCode(y).这基本上就像哈希表一样破坏了.) (7认同)

Ara*_*RRK 18

速记解决方案

myCustomerList.GroupBy(c => c.CustomerId, (key, c) => c.FirstOrDefault());
Run Code Online (Sandbox Code Playgroud)


小智 12

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

var distinctValues =
    from cust in myCustomerList
    group cust by cust.CustomerId
    into gcust
    select gcust.First();
Run Code Online (Sandbox Code Playgroud)

至少它不是冗长的.


Dav*_*and 12

这是一个简单的扩展方法,可以满足我的需求......

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • 为什么选择downvote?如果有充分的理由,我真的很感兴趣 (3认同)