C# 您能否使用 lambda 表达式创建一个简单的“EqualityComparer<T>”

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/716552/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 22:18:37  来源:igfitidea点击:

Can you create a simple 'EqualityComparer<T>' using a lambda expression

c#linqdistinct

提问by Simon_Weaver

IMPORTANT: THIS IS NOT A LINQ-TO-SQLQUESTION. This is LINQ to objects.

重要提示:这不是 LINQ-TO-SQL问题。这是 LINQ 到对象。

Short question:

简短的问题:

Is there a simple way in LINQ to objects to get a distinct list of objects from a list based on a key property on the objects.

LINQ to objects 中是否有一种简单的方法可以根据对象上的键属性从列表中获取不同的对象列表。

Long question:

长问题:

I am trying to do a Distinct()operation on a list of objectsthat have a key as one of their properties.

我正在尝试对具有键作为其属性之一的对象Distinct()列表执行操作。

class GalleryImage {
   public int Key { get;set; }
   public string Caption { get;set; }
   public string Filename { get; set; }
   public string[] Tags {g et; set; }
}

I have a list of Galleryobjects that contain GalleryImage[].

我有一个Gallery包含GalleryImage[].

Because of the way the webservice works [sic] I have duplicates of the GalleryImageobject. i thought it would be a simple matter to use Distinct()to get a distinct list.

由于网络服务的工作方式 [原文如此] 我有GalleryImage对象的副本 。我认为用它Distinct()来获得一个不同的列表是一件简单的事情。

This is the LINQ query I want to use :

这是我要使用的 LINQ 查询:

var allImages = Galleries.SelectMany(x => x.Images);
var distinctImages = allImages.Distinct<GalleryImage>(new 
                     EqualityComparer<GalleryImage>((a, b) => a.id == b.id));

The problem is that EqualityCompareris an abstract class.

问题在于它EqualityComparer是一个抽象类。

I dont want to :

我不想:

  • implement IEquatable on GalleryImagebecause it is generated
  • have to write a separate class to implement IEqualityCompareras shown here
  • 实现 IEquatable onGalleryImage因为它是生成的
  • 必须编写一个单独的类来实现IEqualityComparer如下所示

Is there a concrete implementation of EqualityComparersomewhere that I'm missing?

是否有EqualityComparer我遗漏的某个地方的具体实现?

I would have thought there would be an easy way to get 'distinct' objects from a set based on a key.

我本以为会有一种简单的方法可以根据键从集合中获取“不同”的对象。

采纳答案by Jon Skeet

(There are two solutions here - see the end for the second one):

(这里有两个解决方案——第二个见文末):

My MiscUtillibrary has a ProjectionEqualityComparerclass (and two supporting classes to make use of type inference).

我的MiscUtil库有一个ProjectionEqualityComparer类(和两个使用类型推断的支持类)。

Here's an example of using it:

下面是一个使用它的例子:

EqualityComparer<GalleryImage> comparer = 
    ProjectionEqualityComparer<GalleryImage>.Create(x => x.id);

Here's the code (comments removed)

这是代码(注释已删除)

// Helper class for construction
public static class ProjectionEqualityComparer
{
    public static ProjectionEqualityComparer<TSource, TKey>
        Create<TSource, TKey>(Func<TSource, TKey> projection)
    {
        return new ProjectionEqualityComparer<TSource, TKey>(projection);
    }

    public static ProjectionEqualityComparer<TSource, TKey>
        Create<TSource, TKey> (TSource ignored,
                               Func<TSource, TKey> projection)
    {
        return new ProjectionEqualityComparer<TSource, TKey>(projection);
    }
}

public static class ProjectionEqualityComparer<TSource>
{
    public static ProjectionEqualityComparer<TSource, TKey>
        Create<TKey>(Func<TSource, TKey> projection)
    {
        return new ProjectionEqualityComparer<TSource, TKey>(projection);
    }
}

public class ProjectionEqualityComparer<TSource, TKey>
    : IEqualityComparer<TSource>
{
    readonly Func<TSource, TKey> projection;
    readonly IEqualityComparer<TKey> comparer;

    public ProjectionEqualityComparer(Func<TSource, TKey> projection)
        : this(projection, null)
    {
    }

    public ProjectionEqualityComparer(
        Func<TSource, TKey> projection,
        IEqualityComparer<TKey> comparer)
    {
        projection.ThrowIfNull("projection");
        this.comparer = comparer ?? EqualityComparer<TKey>.Default;
        this.projection = projection;
    }

    public bool Equals(TSource x, TSource y)
    {
        if (x == null && y == null)
        {
            return true;
        }
        if (x == null || y == null)
        {
            return false;
        }
        return comparer.Equals(projection(x), projection(y));
    }

    public int GetHashCode(TSource obj)
    {
        if (obj == null)
        {
            throw new ArgumentNullException("obj");
        }
        return comparer.GetHashCode(projection(obj));
    }
}

Second solution

第二种解决方案

To do this just for Distinct, you can use the DistinctByextension in MoreLINQ:

要仅针对 Distinct 执行此操作,您可以使用MoreLINQ 中DistinctBy扩展名:

    public static IEnumerable<TSource> DistinctBy<TSource, TKey>
        (this IEnumerable<TSource> source,
         Func<TSource, TKey> keySelector)
    {
        return source.DistinctBy(keySelector, null);
    }

    public static IEnumerable<TSource> DistinctBy<TSource, TKey>
        (this IEnumerable<TSource> source,
         Func<TSource, TKey> keySelector,
         IEqualityComparer<TKey> comparer)
    {
        source.ThrowIfNull("source");
        keySelector.ThrowIfNull("keySelector");
        return DistinctByImpl(source, keySelector, comparer);
    }

    private static IEnumerable<TSource> DistinctByImpl<TSource, TKey>
        (IEnumerable<TSource> source,
         Func<TSource, TKey> keySelector,
         IEqualityComparer<TKey> comparer)
    {
        HashSet<TKey> knownKeys = new HashSet<TKey>(comparer);
        foreach (TSource element in source)
        {
            if (knownKeys.Add(keySelector(element)))
            {
                yield return element;
            }
        }
    }

In both cases, ThrowIfNulllooks like this:

在这两种情况下,ThrowIfNull看起来像这样:

public static void ThrowIfNull<T>(this T data, string name) where T : class
{
    if (data == null)
    {
        throw new ArgumentNullException(name);
    }
}

回答by Charlie Flowers

You could group by the key value and then select the top item from each group. Would that work for you?

您可以按键值分组,然后从每个组中选择顶部的项目。这对你有用吗?

回答by Simon_Weaver

This is the best i can come up with for the problem in hand. Still curious whether theres a nice way to create a EqualityCompareron the fly though.

这是我能想出的最好的解决问题的方法。仍然很好奇是否有一种很好的方法可以EqualityComparer即时创建一个。

Galleries.SelectMany(x => x.Images).ToLookup(x => x.id).Select(x => x.First());

Create lookup table and take 'top' from each one

创建查找表并从每个表中取出“顶部”

Note: this is the same as @charlie suggested but using ILookup - which i think is what a group must be anyway.

注意:这与@charlie 建议的相同,但使用 ILookup - 我认为无论如何这是一个组必须的。

回答by Samuel

What about a throw away IEqualityComparergeneric class?

扔掉IEqualityComparer泛型类怎么样?

public class ThrowAwayEqualityComparer<T> : IEqualityComparer<T>
{
  Func<T, T, bool> comparer;

  public ThrowAwayEqualityComparer(Func<T, T, bool> comparer)   
  {
    this.comparer = comparer;
  }

  public bool Equals(T a, T b)
  {
    return comparer(a, b);
  }

  public int GetHashCode(T a)
  {
    return a.GetHashCode();
  }
}

So now you can use Distinctwith a custom comparer.

所以现在您可以使用Distinct自定义比较器。

var distinctImages = allImages.Distinct(
   new ThrowAwayEqualityComparer<GalleryImage>((a, b) => a.Key == b.Key));

You might be able to get away with the <GalleryImage>, but I'm not sure if the compiler could infer the type (don't have access to it right now.)

您可能可以摆脱<GalleryImage>,但我不确定编译器是否可以推断类型(现在无法访问它。)

And in an additional extension method:

并在一个额外的扩展方法中:

public static class IEnumerableExtensions
{
  public static IEnumerable<TValue> Distinct<TValue>(this IEnumerable<TValue> @this, Func<TValue, TValue, bool> comparer)
  {
    return @this.Distinct(new ThrowAwayEqualityComparer<TValue>(comparer);
  }

  private class ThrowAwayEqualityComparer...
}

回答by kvb

Building on Charlie Flowers' answer, you can create your own extension method to do what you want which internally uses grouping:

基于 Charlie Flowers 的回答,您可以创建自己的扩展方法来执行您想要的内部使用分组的操作:

    public static IEnumerable<T> Distinct<T, U>(
        this IEnumerable<T> seq, Func<T, U> getKey)
    {
        return
            from item in seq
            group item by getKey(item) into gp
            select gp.First();
    }

You could also create a generic class deriving from EqualityComparer, but it sounds like you'd like to avoid this:

您还可以创建一个从 EqualityComparer 派生的通用类,但听起来您想避免这种情况:

    public class KeyEqualityComparer<T,U> : IEqualityComparer<T>
    {
        private Func<T,U> GetKey { get; set; }

        public KeyEqualityComparer(Func<T,U> getKey) {
            GetKey = getKey;
        }

        public bool Equals(T x, T y)
        {
            return GetKey(x).Equals(GetKey(y));
        }

        public int GetHashCode(T obj)
        {
            return GetKey(obj).GetHashCode();
        }
    }

回答by markt

Here's an interesting article that extends LINQ for this purpose... http://www.singingeels.com/Articles/Extending_LINQ__Specifying_a_Property_in_the_Distinct_Function.aspx

这是一篇有趣的文章,为此目的扩展了 LINQ... http://www.singingeels.com/Articles/Extending_LINQ__Specifying_a_Property_in_the_Distinct_Function.aspx

The default Distinct compares objects based on their hashcode - to easily make your objects work with Distinct, you could override the GetHashcode method.. but you mentioned that you are retrieving your objects from a web service, so you may not be able to do that in this case.

默认的 Distinct 根据它们的哈希码比较对象 - 为了轻松地使您的对象与 Distinct 一起使用,您可以覆盖 GetHashcode 方法..但是您提到您正在从 Web 服务检索您的对象,因此您可能无法这样做在这种情况下。

回答by Richard

implement IEquatable on GalleryImage because it is generated

在 GalleryImage 上实现 IEquatable 因为它是生成的

A different approach would be to generate GalleryImage as a partial class, and then have another file with the inheritance and IEquatable, Equals, GetHash implementation.

另一种方法是将 GalleryImage 生成为部分类,然后使用继承和 IEquatable、Equals、GetHash 实现的另一个文件。

回答by Shimmy Weitzhandler

This idea is being debated here, and while I'm hoping the .NET Core team adopt a method to generate IEqualityComparer<T>s from lambda, I'd suggest you to please vote and comment on that idea, and use the following:

这种想法正在讨论在这里,虽然我希望在.NET核心团队采用生成的方法IEqualityComparer<T>从拉姆达S,我建议你请那个想法进行投票和评论,并使用以下命令:

Usage:

用法:

IEqualityComparer<Contact> comp1 = EqualityComparerImpl<Contact>.Create(c => c.Name);
var comp2 = EqualityComparerImpl<Contact>.Create(c => c.Name, c => c.Age);

class Contact { public Name { get; set; } public Age { get; set; } }

Code:

代码:

public class EqualityComparerImpl<T> : IEqualityComparer<T>
{
  public static EqualityComparerImpl<T> Create(
    params Expression<Func<T, object>>[] properties) =>
    new EqualityComparerImpl<T>(properties);

  PropertyInfo[] _properties;
  EqualityComparerImpl(Expression<Func<T, object>>[] properties)
  {
    if (properties == null)
      throw new ArgumentNullException(nameof(properties));

    if (properties.Length == 0)
      throw new ArgumentOutOfRangeException(nameof(properties));

    var length = properties.Length;
    var extractions = new PropertyInfo[length];
    for (int i = 0; i < length; i++)
    {
      var property = properties[i];
      extractions[i] = ExtractProperty(property);
    }
    _properties = extractions;
  }

  public bool Equals(T x, T y)
  {
    if (ReferenceEquals(x, y))
      //covers both are null
      return true;
    if (x == null || y == null)
      return false;
    var len = _properties.Length;
    for (int i = 0; i < _properties.Length; i++)
    {
      var property = _properties[i];
      if (!Equals(property.GetValue(x), property.GetValue(y)))
        return false;
    }
    return true;
  }

  public int GetHashCode(T obj)
  {
    if (obj == null)
      return 0;

    var hashes = _properties
        .Select(pi => pi.GetValue(obj)?.GetHashCode() ?? 0).ToArray();
    return Combine(hashes);
  }

  static int Combine(int[] hashes)
  {
    int result = 0;
    foreach (var hash in hashes)
    {
      uint rol5 = ((uint)result << 5) | ((uint)result >> 27);
      result = ((int)rol5 + result) ^ hash;
    }
    return result;
  }

  static PropertyInfo ExtractProperty(Expression<Func<T, object>> property)
  {
    if (property.NodeType != ExpressionType.Lambda)
      throwEx();

    var body = property.Body;
    if (body.NodeType == ExpressionType.Convert)
      if (body is UnaryExpression unary)
        body = unary.Operand;
      else
        throwEx();

    if (!(body is MemberExpression member))
      throwEx();

    if (!(member.Member is PropertyInfo pi))
      throwEx();

    return pi;

    void throwEx() =>
      throw new NotSupportedException($"The expression '{property}' isn't supported.");
  }
}