ChatGPT解决这个技术问题 Extra ChatGPT

与 lambda 不同()?

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

使用 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 的兼容实现打包到一个接口中。

我想这是有道理的。

有关使用 GroupBy 的解决方案,请参阅 stackoverflow.com/questions/1183403/…
不,这没有意义——两个包含相同值的对象如何返回两个不同的哈希码?
它可以帮助 - .Distinct(new KeyEqualityComparer<Customer,string>(c1 => c1.CustomerId))solution,并解释为什么 GetHashCode() 对正常工作很重要。
现在您可以使用 .Net 6 的内置 DistinctBy 方法,例如 DistinctBy(x => x.CustomerId)。

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

出色的!这也很容易封装在扩展方法中,例如 DistinctBy(甚至是 Distinct,因为签名是唯一的)。
对我不起作用! <'First' 方法只能用作最终查询操作。考虑在这种情况下使用“FirstOrDefault”方法。> 即使我尝试了“FirstOrDefault”,它也不起作用。
@TorHaugen:请注意,创建所有这些组是有成本的。这无法流式传输输入,并且最终会在返回任何内容之前缓冲所有数据。当然,这可能与您的情况无关,但我更喜欢 DistinctBy 的优雅:)
@JonSkeet:这对于不想只为一个功能导入其他库的 VB.NET 编码人员来说已经足够了。如果没有 ASync CTP,VB.NET 不支持 yield 语句,因此在技术上无法进行流式传输。不过谢谢你的回答。我将在用 C# 编码时使用它。 ;-)
@BenGripka:那不完全一样。它只为您提供客户 ID。我想要整个客户:)
S
Shimmy Weitzhandler

在我看来,您想从 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#”以获得神一样的 linq 知识。
很好的答案!!!此外,对于所有关于 yield + 额外库的 VB_Complainers,foreach 可以重写为 return source.Where(element => knownKeys.Add(keySelector(element)));
@sudhAnsu63 这是 LinqToSql(和其他 linq 提供程序)的限制。 LinqToX 的目的是将您的 C# lambda 表达式转换为 X 的本机上下文。也就是说,LinqToSql 将您的 C# 转换为 SQL 并尽可能在本机执行该命令。这意味着如果无法在 SQL(或您正在使用的任何 linq 提供程序)中表达它,则驻留在 C# 中的任何方法都不能“通过” linqProvider。我在将数据对象转换为视图模型的扩展方法中看到了这一点。您可以通过“具体化”查询来解决此问题,在 DistinctBy() 之前调用 ToList()。
@Shimmy:我当然欢迎......我不确定可行性是什么。我可以在 .NET 基金会中提出它...
@Shimmy:Carlo 的答案可能适用于 LINQ to SQL ......我不确定。
A
Anestis Kivranoglou

收拾东西。我认为大多数像我一样来到这里的人都想要最简单的解决方案,而无需使用任何库并具有最佳性能。

(对我来说,我认为按方法接受的分组在性能方面是过度的。)

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

t
tdog

速记解决方案

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

您能否添加一些解释为什么会有所改进?
U
Uwe Keim

不,没有这样的扩展方法重载。过去我发现这让自己很沮丧,因此我通常会编写一个帮助类来处理这个问题。目标是将 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/…
(只是为了解释我对哈希码的评论 - 这段代码很容易以 Equals(x, y) == true 结尾,但是 GetHashCode(x) != GetHashCode(y)。这基本上破坏了像哈希表这样的任何东西.)
@Jon,是的,我同意 GetHashcode 的原始实现不是最佳的(很懒惰)。我现在将它切换为基本上使用 EqualityComparer.Default.GetHashcode() ,它稍微标准一些。不过说实话,在这种情况下,唯一能保证工作的 GetHashcode 实现是简单地返回一个常量值。杀死哈希表查找,但保证功能正确。
@JaredPar:没错。哈希码必须与您正在使用的相等函数一致,这可能不是默认值,否则您不会打扰:) 这就是我更喜欢使用投影的原因 - 您可以获得相等和合理的哈希那样编码。这也使得调用代码的重复更少。诚然,它仅适用于您想要两次相同的投影的情况,但这是我在实践中看到的所有情况:)
只有将 <T,T> 替换为 <T> 才能正常工作。否则会出现编译错误。我错过了什么吗?
D
David Kirkland

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

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>
@toddmo 感谢您的反馈:-) 是的,听起来合乎逻辑......我会在进一步调查后更新答案。
G
Gordon Freeman

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

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

至少它不冗长。


V
Vijay Nirmal

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

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

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

这应该是一个新的接受的答案
K
Kleinux

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

/// <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.
D
Dmitry Ledentsov

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

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

什么是 LambdaComparer,你是从哪里导入的?
@PatrickGraham 在答案中链接:brendan.enrick.com/post/…
B
Bob

采取另一种方式:

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

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


不,这不应该是公认的答案,除非您想要的只是自定义属性的不同值。一般的 OP 问题是如何根据对象的特定属性返回不同的对象。
В
Валентин Миронов

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

仅供参考,这还不够,还需要提供一个 getHashCode 委托。
C
Community

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


J
Jon Egerton

如果 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());
A
Arturo Menchaca

一个棘手的方法是使用 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> 呢?
Q
Quality Catalyst

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

M
MattH

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

我相信您可以通过 self join var distinctResults = from c1 in myList join c2 in myList on 来实现这一点


N
Niall Connaughton

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


M
Matt

以下是您的操作方法:

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

T
Tulshi Das

我发现这是最简单的解决方案。

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