关于c#:与lambda的Distinct()?

Distinct() with lambda?

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

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

1
var distinctValues = myStringList.Distinct();

很好,但是如果我有一个可枚举的对象,需要为其指定相等性,那么唯一可用的重载是:

1
var distinctValues = myCustomerList.Distinct(someEqualityComparer);

相等比较器参数必须是IEqualityComparer的实例。当然,我可以这样做,但有点冗长,嗯,有点笨拙。

我所期望的是一个需要lambda的重载,比如func

1
2
var distinctValues
    = myCustomerList.Distinct((c1, c2) => c1.CustomerId == c2.CustomerId);

有人知道是否存在这样的扩展,或者类似的解决方法吗?还是我错过了什么?

或者,是否有一种指定IEqualityComparer内联的方法(EmbarasMe)?

更新

我在一个关于这个问题的msdn论坛上找到了anders hejlsberg的回复。他说:

The problem you're going to run into is that when two objects compare
equal they must have the same GetHashCode return value (or else the
hash table used internally by Distinct will not function correctly).
We use IEqualityComparer because it packages compatible
implementations of Equals and GetHashCode into a single interface.

我想这是有道理的……


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


在我看来,你想从莫林克得到埃多克斯1〔0〕。然后你可以写:

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

这里有一个简化版的DistinctBy(没有无效检查,也没有指定自己的键比较器的选项):

1
2
3
4
5
6
7
8
9
10
11
12
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;
        }
    }
}


把事情总结起来。我想大多数像我一样来到这里的人都希望在不使用任何库和尽可能好的性能的情况下得到最简单的解决方案。

(对于我来说,我认为被接受的分组方法在性能上是一种过度杀伤力。)

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

用途:

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

扩展方法代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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();
    }
}


不,没有这样的扩展方法重载。我发现这在过去让我自己很沮丧,因此我通常编写一个助手类来处理这个问题。其目的是将Func转换为IEqualityComparer

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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);
  }
}

这允许您编写以下内容

1
2
var distinctValues = myCustomerList
  .Distinct(EqualityFactory.Create((c1, c2) => c1.CustomerId == c2.CustomerId));


速记解决方案

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


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

1
2
3
4
5
var distinctValues =
    from cust in myCustomerList
    group cust by cust.CustomerId
    into gcust
    select gcust.First();

至少它不冗长。


这里有一个简单的扩展方法,可以满足我的需要…

1
2
3
4
5
6
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);
    }
}

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/// <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);
    }
}


Take another way:

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

序列返回不同的元素,并按属性"_myCauseTomerProperty"对它们进行比较。


我在这里看到的所有解决方案都依赖于选择一个已经具有可比性的领域。但是,如果需要以不同的方式进行比较,这里的这个解决方案似乎在一般情况下适用,比如:

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


可以使用inlinecomparer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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);
    }
}

使用样品:

1
2
3
4
  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进行联合我可以在内联中指定显式类型比较器吗?


您可以使用lambdaeQualityComparer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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;
    }

要做到这一点,一个棘手的方法是使用Aggregate()扩展,使用字典作为累加器,键属性值作为键:

1
2
3
4
5
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()

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


Microsoft System.Interactive包的distinct版本采用键选择器lambda。这实际上与JonSkeet的解决方案相同,但它可能有助于人们了解,并查看图书馆的其余部分。


IEnumerablelambda扩展:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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);
    }
}

用途:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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());

如果Distinct()不能产生独特的结果,请尝试以下方法:

1
2
3
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);


你可以这样做:

1
2
3
4
5
6
7
8
9
10
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))来使用它,但它还允许您将having条件指定为第二个参数,如so:

1
2
3
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(...)

如果您只想公开条件,可以通过将其实现为:

1
2
3
4
5
6
7
8
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));
}

在这种情况下,查询看起来就像:

1
2
3
var myQuery2 = (from x in _myObject select x).MyDistinct2(d => d.Name,
                    y => y.Name.Contains("1") || y.Name.Contains("2")
                    );

注意,这里的表达式比较简单,但注意.MyDistinct2隐式使用.FirstOrDefault(...)

注意:上面的示例使用以下演示类

[cc lang="csharp"]class MyObject
{
public string Name;
public string Code;
}

private MyObject[] _myObject = {
new MyObject() { Name ="Test1", Code ="T


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

我相信你可以通过自我加入来实现这个目标var distinctresults=来自mylist中的c1在我的列表中加入C2