2009-04-04 14 views
27

を使用して簡単な 'EqualityComparer 'を作成できますか?重要:これは です.LINQ-TO-SQL質問です。これはオブジェクトに対するLINQです。 ラムダ式

短い質問:

オブジェクトのキープロパティに基づいてリストからオブジェクトの明確なリストを取得するオブジェクトへのLINQで簡単な方法があります。

ロング質問:

私は彼らの特性の一つとして、鍵を持ってオブジェクトのリストに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オブジェクトのリストを持っています。

ウェブサービスの仕組みのために、私は 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は抽象クラスであるということです。私がしたいいけない

:それが生成されるため

としてIEqualityComparerを実装するために別のクラスを記述する必要がありGalleryImageにIEquatableを実装EqualityComparerの具体的な実装がありますどこかで私が行方不明になっていますか?

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

答えて

33

(2つのソリューションがここにあります - 1秒間の端を参照してください):

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

第二の溶液

だ、あなたが使用することができますDistinctBy内線番号MoreLINQ

0どちらの場合も
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); 
    } 
} 
+0

優れた情報!ありがとう! –

+0

'TSource ignored'パラメータを持つ2番目の静的Createメソッドのポイントは何ですか? (TSourceは無視され、 Func 投影) { return new ProjectionEqualityComparer (投影); } – flesh

+1

@flesh:型を明示的に指定できない場合、型推論が可能になります。匿名型の場合 –

2

キー値でグループ化し、各グループからトップアイテムを選択できます。それはあなたのために働くだろうか?

+0

はい、私はちょうどそれを実際に見ています - ToLookup()を介して。おそらく非効率で遅いですが、この作業には大丈夫です。私のステートメントを上/下に掲示する –

3

これは私が手にしている問題のために思い付くことができる最高です。 まだ好奇心が強いのかどうかは、その場でEqualityComparerを作成する素晴らしい方法です。

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

ルックアップテーブルを作成し、各1つの

ノートから「トップ」を取る:これは推奨さ@charlieと同じですが、ILookupを使用して - 私は、グループがとにかくでなければならない何だと思うています。

+0

私は、フレームワークに何かが欠けているように感じることに同意します。私はそれがIEqualityComparerかどうかはわかりません...本当に両方のメソッドが必要です。 Distinctを使用する簡単な方法があるように感じます。つまり、述語を取るオーバーライドです。 –

+0

述語ではありません。私はTを取るDistinctのオーバーライドを意味し、あなたが独特性のために使用したいオブジェクトを選択させることができます。 –

+0

@charlie - right、thats私は実際には私が既存のDistinct(..)を手に入れようとしていたことを考えています。私は前にこの文脈でそれを使用したことはありませんでした。もちろん、私が期待したものではないことが判明しました。 –

2

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>で逃げることができるかもしれませんが、私は、コンパイラが型を推論できたかどうかわからないんだけど(今それにアクセスすることはできません。)

とANで追加の拡張方法:チャーリー花と答えた上

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

かなり良いです。次に、私が望むDistinctのオーバーライドを実装することもできます。 –

+0

はい、あなたは簡単にそれを行い、あなたが望むものを得ることができます。 – Samuel

+0

しかし、まだIEqualityComparer を実装していません。あなたがそうしたくないように聞こえました。 –

4

ビル、あなたが内部的にグループ化を使用するやりたいために、独自の拡張メソッドを作成することができます。

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

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

...です

デフォルトのDistinctは、ハッシュコードに基づいてオブジェクトを比較します。オブジェクトをDistinctで簡単に動作させるには、GetHashcodeメソッドをオーバーライドできますが、Webサービスからオブジェクトを取得しているためこの場合、それを行う。

0

は、それが

が生成されるため、別のアプローチは、部分クラスとしてGalleryImageを生成することであろうGalleryImageにIEquatableを実装します継承とIEquatable、Equals、GetHashの実装を持つ別のファイルを持っています。

0

この考えは議論されていますhere、そして私が望んでいる間。NETコアチームラムダから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."); 
    } 
}