是的,所以我有一个可枚举的对象,并希望从中获得不同的值。
使用System.Linq,当然有一个扩展方法Distinct。在简单的情况下,它可以不带任何参数使用,例如:
var distinctValues = myStringList.Distinct();

很好,但是如果我有一个需要枚举的对象需要指定相等性,则唯一可用的重载是:
var distinctValues = myCustomerList.Distinct(someEqualityComparer);

相等比较器参数必须是IEqualityComparer<T>的实例。我当然可以做到这一点,但是它有点冗长,而且很笨拙。
我期望的是一个过载,它需要一个lambda,例如Func
var distinctValues = myCustomerList.Distinct((c1, c2) => c1.CustomerId == c2.CustomerId);

谁知道是否存在这样的扩展名或等效的解决方法?还是我错过了什么?
或者,是否可以指定IEqualityComparer内联(使我感到尴尬)?
更新
我发现Anders Hejlsberg对MSDN论坛上的帖子进行了回复这个主题。他说:

您将遇到的问题是,当两个对象比较相等时,它们必须具有相同的GetHashCode返回值(否则,将使用
哈希表
我们之所以使用IEqualityComparer,是因为它将兼容的Equals和GetHashCode的实现打包到单个接口中。

我认为这很有意义。
/>

评论

有关使用GroupBy的解决方案,请参见stackoverflow.com/questions/1183403/…。
感谢Anders Hejlsberg的更新!

不,这没有意义-包含相同值的两个对象如何返回两个不同的哈希码?

它可以为-Distinct(new KeyEqualityComparer (c1 => c1.CustomerId))解决方案,并解释为什么GetHashCode()对于正常工作很重要。

相关/可能与以下项重复:LINQ在特定属性上的Distinct()

#1 楼

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


评论


优秀的!这真的很容易封装在扩展方法中,例如DistinctBy(甚至Distinct,因为签名将是唯一的)。

– Tomas Aschan
11年7月27日在11:33

对我不起作用! <方法“ First”只能用作最终查询操作。考虑在此实例中使用方法“ FirstOrDefault”。>即使我尝试了“ FirstOrDefault”,它也无法正常工作。

–JatSing
2011-09-25 13:55

@TorHaugen:请注意,创建所有这些组涉及成本。这不能流输入,并且最终将在返回任何内容之前缓冲所有数据。当然,这可能与您的情况无关,但我更喜欢DistinctBy的优雅:)

–乔恩·斯基特(Jon Skeet)
2011-09-28 11:52

@JonSkeet:这对于不想为一个功能导入其他库的VB.NET编码人员来说已经足够了。如果没有ASync CTP,VB.NET将不支持yield语句,因此从技术上讲不可能进行流传输。谢谢您的回答。我将在使用C#进行编码时使用它。 ;-)

– Alex Essilfie
2011-12-11 14:46

@BenGripka:不太一样。它只为您提供客户ID。我想要整个客户:)

– ryanman
2014-10-16 14:57

#2 楼

在我看来,您想要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;
        }
    }
}


评论


我知道最好的答案将是Jon Skeet只需阅读帖子标题即可发布。如果它与LINQ有关系,Skeet就是您的好人。阅读“ C#In Depth”以获取类似于上帝的linq知识。

– Shawn J. Molloy
2014年1月16日在3:03



好答案!!!同样,对于所有关于yield + extra lib的VB_Complainers来说,foreach可以重写为返回源。where(element => knownKeys.Add(keySelector(element)));

–丹尼斯·莫罗佐夫(denis morozov)
2014年3月4日在16:51

@ sudhAnsu63这是LinqToSql(和其他linq提供程序)的限制。 LinqToX的目的是将您的C#lambda表达式转换为X的本机上下文。也就是说,LinqToSql将您的C#转换为SQL并在可能的情况下本地执行该命令。这意味着,如果无法在SQL(或您使用的任何linq提供程序)中表达它,则无法将驻留在C#中的任何方法“传递”给linqProvider。我在扩展方法中看到了这一点,该方法将数据对象转换为视图模型。您可以通过“具体化”查询,在DistinctBy()之前调用ToList()来解决此问题。

–迈克尔·布莱克本
15年11月16日在22:18

@Shimmy:我当然会欢迎...我不确定可行性。我可以在.NET Foundation中提高它...

–乔恩·斯基特(Jon Skeet)
17年5月22日在11:39

@Shimmy:Carlo的答案可能在LINQ to SQL中有效……我不确定。

–乔恩·斯基特(Jon Skeet)
19-09-3在5:37



#3 楼

整理东西。我认为像我这样来这里的大多数人都希望有尽可能简单的解决方案,而无需使用任何库,并要达到最佳性能。

性能。)

这是使用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();
    }
}


#4 楼

没有,对此没有这种扩展方法重载。过去,我发现这很令人沮丧,因此,我通常编写一个帮助程序类来解决此问题。目标是将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 更容易:stackoverflow.com/questions/188120/…

–乔恩·斯基特(Jon Skeet)
09年8月19日在14:05

(只是为了解释我对哈希码的评论-该代码以Equals(x,y)== true结束很容易,但是GetHashCode(x)!= GetHashCode(y)。这基本上破坏了像哈希表这样的东西)

–乔恩·斯基特(Jon Skeet)
09年8月19日在14:06

我同意哈希码异议。不过,模式需要+1。

– Tor Haugen
09年8月19日在14:11

@Jon,是的,我同意GetHashcode的原始实现不是最优的(很懒惰)。我将其切换为现在基本上使用EqualityComparer .Default.GetHashcode(),它稍微更标准了。但实际上,在这种情况下,唯一可以保证GetHashcode实现的方法是简单地返回一个常数。终止哈希表查找,但保证在功能上正确。

– JaredPar
09年8月19日在14:13

@JaredPar:是的。哈希码必须与您正在使用的均等函数一致,这大概不是默认函数,否则您就不会打扰:)这就是为什么我更喜欢使用投影-您可以同时获得均等和合理的哈希这样编码。这也使调用代码具有较少的重复。诚然,它仅在需要两次相同投影的情况下有效,但这是我在实践中见过的每种情况:)

–乔恩·斯基特(Jon Skeet)
09年8月19日在14:16

#5 楼

速记解决方案

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


评论


您能否添加一些解释,以说明为什么它得到了改进?

–基思·皮森(Keith Pinson)
2014年8月20日13:50

康拉德(Konrad's)没这么做时,这实际上对我很好。

–新
19年4月17日在20:09

#6 楼

这将满足您的要求,但我对性能一无所知:

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


至少它并不冗长。

#7 楼

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

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


他们没有在框架中引入这样的独特方法,这很可惜。

评论


这是最好的解决方案,而无需添加该库morelinq。

– toddmo
2014年9月18日22:10在

但是,我不得不将x.Key更改为x.First()并将返回值更改为IEnumerable

– toddmo
2014-09-18 22:17

@toddmo感谢您提供反馈意见:-)是的,听起来很合逻辑。在进一步调查之后,我将更新答案。

–大卫·柯克兰(David Kirkland)
2014-09-18 22:20

永远感谢您的解决方案,简单而干净

–阿里
18年2月24日在0:12

#8 楼

我使用过的东西对我来说很有效。

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


评论


@Mukus我不确定您为什么在这里询问班级名称。为了实现IEqualityComparer,我需要给该类命名,所以我只是给My加上前缀。

– Kleinux
2015年6月11日14:20在

#9 楼

我在这里看到的所有解决方案都依赖于选择一个已经具有可比性的领域。但是,如果需要以不同的方式进行比较,则此解决方案似乎可以正常工作,例如:

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


评论


什么是LambdaComparer,您是从哪里导入的?

–帕特里克·格雷厄姆(Patrick Graham)
18年1月25日在20:45

@PatrickGraham链接到答案:brendan.enrick.com/post/…

–德米特里·列登佐夫(Dmitry Ledentsov)
18年1月29日在12:41

#10 楼

采取另一种方式:

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


序列返回不同的元素,并通过'_myCaustomerProperty'属性对其进行比较。

评论


来这里说这个。这应该是公认的答案

–Still.Tony
18/12/18在18:34

不,这不是公认的答案,除非您想要的只是自定义属性的不同值。一般的OP问题是如何根据对象的特定属性返回不同的对象。

– tomo
19年5月27日19:39

#11 楼

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


#12 楼

您可以使用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将IEqualityComparer用于Union是否可以内联指定我的显式类型比较器?

#13 楼

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


,并且使用GroupBy风格的解决方案ToLookup()

var distincts = customers.ToLookup(c => c.CustomerId).Select(g => g.First());


评论


很好,但是为什么不只创建Dictionary 呢?

–松饼
18年1月30日在19:09

#14 楼

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

我相信您可以通过自连接实现< br varvardistingResults =来自myList中的c1

上加入myList中的c2

#15 楼

如果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);


#16 楼

Microsoft System.Interactive软件包的Distinct版本采用键选择器lambda。这实际上与Jon Skeet的解决方案相同,但是对于人们了解和检查库的其余部分可能会有所帮助。

#17 楼

操作方法如下:

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


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


#18 楼

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