首页 文章

你能用lambda表达式创建一个简单的'EqualityComparer<T>'

提问于
浏览
33

重要提示:这是 NOT A LINQ-TO-SQL 问题 . 这是对象的LINQ .

Short question:

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

Long question:

我正在尝试在 objects 列表上执行Distinct()操作,该列表具有作为其属性之一的键 .

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

我有一个包含 GalleryImage[]Gallery 对象列表 .

由于webservice的工作方式[原文如此],我有 GalleryImage 对象的重复 . 我认为使用 Distinct() 来获得一个独特的列表是一件简单的事情 .

这是我想要使用的LINQ查询:

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

问题是 EqualityComparer 是一个抽象类 .

我不想:

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

  • 必须编写一个单独的类来实现 IEqualityComparer 作为shown here

EqualityComparer 在某个地方是否有一个我错过的具体实现?

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

8 回答

  • 3

    (这里有两种解决方案 - 见第二种解决方案):

    我的MiscUtil库有一个 ProjectionEqualityComparer 类(以及两个支持类来使用类型推断) .

    以下是使用它的示例:

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

    这是代码(删除了评论)

    // 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

    要仅针对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;
                }
            }
        }
    

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

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

    基于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();
        }
    

    您还可以创建一个派生自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();
            }
        }
    
  • 1

    这是我能想出的最好的问题 . 仍然好奇是否有一个很好的方式来创建一个 EqualityComparer 虽然 .

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

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

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

  • 1

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

  • 0

    扔掉 IEqualityComparer 泛型课怎么样?

    public class ThrowAwayEqualityComparer<T> : IEqualityComparer<T>
    {
      Func<T, T, bool> comparer;
    
      public ThrowAwayEqualityComparer<T>(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();
      }
    }
    

    所以现在你可以使用 Distinct .

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

    您可能能够使用 <GalleryImage> ,但我现在可以访问它 . )

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

    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...
    }
    
  • 0

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

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

  • 2

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

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

  • 4

    这个想法正在争论here,虽然我希望.NET Core团队采用一种方法从lambda生成 IEqualityComparer<T> ,但我建议你对这个想法进行投票和评论,并使用以下内容:

    用法:

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

    码:

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

相关问题