重要:これは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
の具体的な実装はどこかにありますか?
キーに基づいてセットから「個別の」オブジェクトを取得する簡単な方法があると思いました。
(ここには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だけでこれを行うには、 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);
}
}
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();
}
}
キーの値でグループ化し、各グループから一番上の項目を選択できます。それでうまくいきますか?
これは、手元にある問題に対して私が思いつくことができる最高のものです。しかし、その場でEqualityComparer
を作成するための素晴らしい方法があるかどうかはまだ気になります。
Galleries.SelectMany(x => x.Images).ToLookup(x => x.id).Select(x => x.First());
ルックアップテーブルを作成し、それぞれから「トップ」を取得
注:これは@charlieが提案したものと同じですが、ILookupを使用しています。
使い捨て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...
}
このアイデアは議論されています ここ 、そして私は.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.");
}
}
これは、この目的のためにLINQを拡張する興味深い記事です... http://www.singingeels.com/Articles/Extending_LINQ__Specifying_a_Property_in_the_Distinct_Function.aspx
デフォルトのDistinctは、オブジェクトをハッシュコードに基づいて比較します。オブジェクトをDistinctで簡単に機能させるために、GetHashcodeメソッドをオーバーライドできます。この場合。
生成されるため、GalleryImageにIEquatableを実装する
別のアプローチは、GalleryImageを部分クラスとして生成し、継承とIEquatable、Equals、GetHash実装を含む別のファイルを作成することです。