web-dev-qa-db-ja.com

ラムダ式を使用して簡単な 'EqualityComparer <T>'を作成できますか

重要:これはISNOT A LINQ-TO-SQL質問です。これはオブジェクトに対するLINQです。

短い質問:

オブジェクトへのLINQでオブジェクトのキープロパティに基づくリストからオブジェクトの個別のリストを取得する簡単な方法はありますか?.

長い質問:

キーが1つであるオブジェクトのリストに対して 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オブジェクトのリストがあります。

Webサービスの動作方法が原因で[sic] 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を実装するには、別のクラスを作成する必要があります ここに表示

EqualityComparerの具体的な実装はどこかにありますか?

キーに基づいてセットから「個別の」オブジェクトを取得する簡単な方法があると思いました。

40
Simon_Weaver

(ここには2つの解決策があります-2番目の解決策の終わりを参照してください):

私の MiscUtil ライブラリにはProjectionEqualityComparerクラス(および型推論を利用するための2つのサポートクラス)があります。

使用例は次のとおりです。

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

2番目のソリューション

Distinctだけでこれを行うには、 MoreLINQDistinctBy 拡張を使用できます。

    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);
    }
}
38
Jon Skeet

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();
        }
    }
6
kvb

キーの値でグループ化し、各グループから一番上の項目を選択できます。それでうまくいきますか?

3
Charlie Flowers

これは、手元にある問題に対して私が思いつくことができる最高のものです。しかし、その場でEqualityComparerを作成するための素晴らしい方法があるかどうかはまだ気になります。

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

ルックアップテーブルを作成し、それぞれから「トップ」を取得

注:これは@charlieが提案したものと同じですが、ILookupを使用しています。

3
Simon_Weaver

使い捨て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();
  }
}

これで、カスタムコンパレータで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...
}
1
Samuel

このアイデアは議論されています ここ 、そして私は.NET CoreチームがラムダからIEqualityComparer<T>sを生成する方法を採用することを望んでいますが、投票してコメントすることをお勧めしますアイデア、そして以下を使用します:

使用法:

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

これは、この目的のためにLINQを拡張する興味深い記事です... http://www.singingeels.com/Articles/Extending_LINQ__Specifying_a_Property_in_the_Distinct_Function.aspx

デフォルトのDistinctは、オブジェクトをハッシュコードに基づいて比較します。オブジェクトをDistinctで簡単に機能させるために、GetHashcodeメソッドをオーバーライドできます。この場合。

1
markt

生成されるため、GalleryImageにIEquatableを実装する

別のアプローチは、GalleryImageを部分クラスとして生成し、継承とIEquatable、Equals、GetHash実装を含む別のファイルを作成することです。

0
Richard