49

短い質問:

オブジェクトのキープロパティに基づいてリストからオブジェクトの個別のリストを取得するためのLINQtoobjectsの簡単な方法はありますか。

長い質問:

プロパティの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; }
}

Galleryを含むオブジェクトのリストがありますGalleryImage[]

Webサービスの動作方法のため[原文のまま] 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私が見逃している場所の具体的な実装はありますか?

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

4

8 に答える 8

43

(ここには 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);
    }
}
于 2009-04-04T06:38:51.670 に答える
7

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();
        }
    }
于 2009-04-04T05:53:20.170 に答える
6

これは、当面の問題に対して私が思いつくことができる最高のものです。ただし、その場でを作成する良い方法があるかどうかはまだ興味EqualityComparerがあります。

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

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

注:これは@charlieが提案したものと同じですが、ILookupを使用しています-とにかくグループがどうあるべきかだと思います。

于 2009-04-04T05:36:43.393 に答える
4

使い捨ての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...
}
于 2009-04-04T05:46:59.377 に答える
3

キー値でグループ化してから、各グループから最上位のアイテムを選択できます。それはあなたのために働きますか?

于 2009-04-04T05:29:44.270 に答える
1

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

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

于 2009-04-04T06:35:49.477 に答える
0

生成されるので GalleryImage に IEquatable を実装する

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

于 2009-04-04T14:40:40.887 に答える