回答:
通常、私は回答に@Samをコメントすることでこれを解決します(元の投稿を少し編集して、動作を変更せずに少し整理しました)。
以下は、デフォルトのハッシュポリシーに対する[IMNSHO]の重要な修正を含む、@ Samの回答の私のリフです。
class FuncEqualityComparer<T> : IEqualityComparer<T>
{
readonly Func<T, T, bool> _comparer;
readonly Func<T, int> _hash;
public FuncEqualityComparer( Func<T, T, bool> comparer )
: this( comparer, t => 0 ) // NB Cannot assume anything about how e.g., t.GetHashCode() interacts with the comparer's behavior
{
}
public FuncEqualityComparer( Func<T, T, bool> comparer, Func<T, int> hash )
{
_comparer = comparer;
_hash = hash;
}
public bool Equals( T x, T y )
{
return _comparer( x, y );
}
public int GetHashCode( T obj )
{
return _hash( obj );
}
}
GetHashCode
他の人たちは、カスタムIEqualityComparer<T>
実装にはGetHashCode
必ずメソッドを含める必要があるという事実についてすでにコメントしています。しかし、理由を詳細に説明することに煩わされる人はいません。
これが理由です。あなたの質問は、特にLINQ拡張メソッドについて言及しています。これらのほとんどすべてが効率的にハッシュテーブルを使用するため、適切に機能するためにハッシュコードに依存しています。
テイクDistinct
例えば、。それが利用したすべてがメソッドだった場合、この拡張メソッドの影響を考慮してくださいEquals
。アイテムがすでにスキャンされている場合、そのアイテムがすでにスキャンされているかどうかをどのように判断しますかEquals
?すでに確認した値のコレクション全体を列挙し、一致するかどうかを確認します。これにより、Distinct
O(N )アルゴリズムではなく、最悪の場合のO(N 2)アルゴリズムが使用されます。
幸い、そうではありません。単に使用するだけでDistinct
はありません。それも使用します。実際、適切なを提供するがなければ、絶対に正しく機能しません。以下はこれを説明する不自然な例です。Equals
GetHashCode
IEqualityComparer<T>
GetHashCode
次のタイプがあるとします。
class Value
{
public string Name { get; private set; }
public int Number { get; private set; }
public Value(string name, int number)
{
Name = name;
Number = number;
}
public override string ToString()
{
return string.Format("{0}: {1}", Name, Number);
}
}
今、私が持っていList<Value>
て、明確な名前を持つすべての要素を見つけたいとしましょう。これは、Distinct
カスタムの等値比較子を使用するのに最適な使用例です。それではComparer<T>
、Akuの回答のクラスを使用してみましょう。
var comparer = new Comparer<Value>((x, y) => x.Name == y.Name);
さて、Value
同じName
プロパティを持つ要素がたくさんある場合、それらはすべてによって返される1つの値に折りたたむ必要がありDistinct
ますよね?どれどれ...
var values = new List<Value>();
var random = new Random();
for (int i = 0; i < 10; ++i)
{
values.Add("x", random.Next());
}
var distinct = values.Distinct(comparer);
foreach (Value x in distinct)
{
Console.WriteLine(x);
}
出力:
x:1346013431 x:1388845717 x:1576754134 x:1104067189 x:1144789201 x:1862076501 x:1573781440 x:646797592 x:655632802 x:1206819377
うーん、うまくいきませんでしたね。
どうGroupBy
ですか?それを試してみましょう:
var grouped = values.GroupBy(x => x, comparer);
foreach (IGrouping<Value> g in grouped)
{
Console.WriteLine("[KEY: '{0}']", g);
foreach (Value x in g)
{
Console.WriteLine(x);
}
}
出力:
[キー= 'x:1346013431'] x:1346013431 [キー= 'x:1388845717'] x:1388845717 [キー= 'x:1576754134'] x:1576754134 [キー= 'x:1104067189'] x:1104067189 [キー= 'x:1144789201'] x:1144789201 [キー= 'x:1862076501'] x:1862076501 [キー= 'x:1573781440'] x:1573781440 [キー= 'x:646797592'] x:646797592 [キー= 'x:655632802'] x:655632802 [キー= 'x:1206819377'] x:1206819377
繰り返しますが、動作しませんでした。
あなたが考えてみれば、それがために理にかなってDistinct
使用するようにHashSet<T>
内部的に(または同等の)、および用GroupBy
などの使用に何かDictionary<TKey, List<T>>
内部的。これらの方法が機能しない理由をこれで説明できますか?これを試してみましょう:
var uniqueValues = new HashSet<Value>(values, comparer);
foreach (Value x in uniqueValues)
{
Console.WriteLine(x);
}
出力:
x:1346013431 x:1388845717 x:1576754134 x:1104067189 x:1144789201 x:1862076501 x:1573781440 x:646797592 x:655632802 x:1206819377
ええ...理にかなっていますか?
これらの例からうまくいけば、適切なものGetHashCode
をIEqualityComparer<T>
実装に含めることがなぜそれほど重要なのかは明らかです。
oripの答えを拡張する:
ここでできる改善点がいくつかあります。
Func<T, TKey>
代わりにを使用しFunc<T, object>
ます。これにより、実際の値タイプのキーのボックス化が回避されkeyExtractor
ます。where TKey : IEquatable<TKey>
制約を追加します。これにより、Equals
呼び出しでのボクシングが防止されます(パラメーターをobject.Equals
受け取りobject
ます。ボクシングを行わずにパラメーターIEquatable<TKey>
を受け取る実装が必要ですTKey
)。これは明らかに制限が厳しすぎる可能性があるため、制約なしの基本クラスとそれを使用した派生クラスを作成できます。結果のコードは次のようになります。
public class KeyEqualityComparer<T, TKey> : IEqualityComparer<T>
{
protected readonly Func<T, TKey> keyExtractor;
public KeyEqualityComparer(Func<T, TKey> keyExtractor)
{
this.keyExtractor = keyExtractor;
}
public virtual bool Equals(T x, T y)
{
return this.keyExtractor(x).Equals(this.keyExtractor(y));
}
public int GetHashCode(T obj)
{
return this.keyExtractor(obj).GetHashCode();
}
}
public class StrictKeyEqualityComparer<T, TKey> : KeyEqualityComparer<T, TKey>
where TKey : IEquatable<TKey>
{
public StrictKeyEqualityComparer(Func<T, TKey> keyExtractor)
: base(keyExtractor)
{ }
public override bool Equals(T x, T y)
{
// This will use the overload that accepts a TKey parameter
// instead of an object parameter.
return this.keyExtractor(x).Equals(this.keyExtractor(y));
}
}
StrictKeyEqualityComparer.Equals
方法はと同じようKeyEqualityComparer.Equals
です。いTKey : IEquatable<TKey>
制約メイクTKey.Equals
は異なる仕事を?
TKey
任意の型である可能性があるため、コンパイラは仮想メソッドObject.Equals
を使用します。これには、値型パラメーターのボクシングが必要になりますint
。ただし、後者の場合TKey
はの実装IEquatable<TKey>
に制約があるため、TKey.Equals
ボクシングを必要としないメソッドが使用されます。
StringKeyEqualityComparer<T, TKey>
あまりにも必要性を排除したでしょう。
等価性チェックをカスタマイズする場合、99%は比較自体ではなく、比較するキーの定義に関心があります。
これはエレガントな解決策になる可能性があります(Pythonのリストソートメソッドからの概念)。
使用法:
var foo = new List<string> { "abc", "de", "DE" };
// case-insensitive distinct
var distinct = foo.Distinct(new KeyEqualityComparer<string>( x => x.ToLower() ) );
KeyEqualityComparer
クラス:
public class KeyEqualityComparer<T> : IEqualityComparer<T>
{
private readonly Func<T, object> keyExtractor;
public KeyEqualityComparer(Func<T,object> keyExtractor)
{
this.keyExtractor = keyExtractor;
}
public bool Equals(T x, T y)
{
return this.keyExtractor(x).Equals(this.keyExtractor(y));
}
public int GetHashCode(T obj)
{
return this.keyExtractor(obj).GetHashCode();
}
}
申し訳ありませんが、そのようなラッパーはすぐには利用できません。ただし、作成するのは難しくありません。
class Comparer<T>: IEqualityComparer<T>
{
private readonly Func<T, T, bool> _comparer;
public Comparer(Func<T, T, bool> comparer)
{
if (comparer == null)
throw new ArgumentNullException("comparer");
_comparer = comparer;
}
public bool Equals(T x, T y)
{
return _comparer(x, y);
}
public int GetHashCode(T obj)
{
return obj.ToString().ToLower().GetHashCode();
}
}
...
Func<int, int, bool> f = (x, y) => x == y;
var comparer = new Comparer<int>(f);
Console.WriteLine(comparer.Equals(1, 1));
Console.WriteLine(comparer.Equals(1, 2));
private readonly Func<T, int> _hashCodeResolver
が必要です。このメンバーもコンストラクターに渡して、GetHashCode(...)
メソッドで使用する必要があります。
obj.ToString().ToLower().GetHashCode()
代わりにを使用しているのはなぜobj.GetHashCode()
ですか?
IEqualityComparer<T>
この実装では、バックグラウンドで常にハッシュを使用するフレームワーク内の場所(たとえば、LINQのGroupBy、Distinct、Except、Joinなど)とハッシュに関するMSコントラクトが壊れています。MSのドキュメントの抜粋は次のとおりです。「Equalsメソッドが2つのオブジェクトxとyに対してtrueを返す場合、xのGetHashCodeメソッドによって返される値がyに対して返される値と等しくなるようにするための実装が必要です。」参照:msdn.microsoft.com/en-us/library/ms132155
Dan Taoの回答と同じですが、いくつかの改良点があります。
依存しEqualityComparer<>.Default
、それは値型(のためのボクシングを回避するように比較実際の操作を行うためにstruct
実装された複数の)IEquatable<>
。
EqualityComparer<>.Default
中古なので爆発しませんnull.Equals(something)
。
IEqualityComparer<>
Comparerのインスタンスを作成する静的メソッドを持つ静的ラッパーが提供されます-呼び出しが簡単になります。比較する
Equality<Person>.CreateComparer(p => p.ID);
と
new EqualityComparer<Person, int>(p => p.ID);
IEqualityComparer<>
キーに指定するオーバーロードを追加しました。
クラス:
public static class Equality<T>
{
public static IEqualityComparer<T> CreateComparer<V>(Func<T, V> keySelector)
{
return CreateComparer(keySelector, null);
}
public static IEqualityComparer<T> CreateComparer<V>(Func<T, V> keySelector,
IEqualityComparer<V> comparer)
{
return new KeyEqualityComparer<V>(keySelector, comparer);
}
class KeyEqualityComparer<V> : IEqualityComparer<T>
{
readonly Func<T, V> keySelector;
readonly IEqualityComparer<V> comparer;
public KeyEqualityComparer(Func<T, V> keySelector,
IEqualityComparer<V> comparer)
{
if (keySelector == null)
throw new ArgumentNullException("keySelector");
this.keySelector = keySelector;
this.comparer = comparer ?? EqualityComparer<V>.Default;
}
public bool Equals(T x, T y)
{
return comparer.Equals(keySelector(x), keySelector(y));
}
public int GetHashCode(T obj)
{
return comparer.GetHashCode(keySelector(obj));
}
}
}
次のように使用できます。
var comparer1 = Equality<Person>.CreateComparer(p => p.ID);
var comparer2 = Equality<Person>.CreateComparer(p => p.Name);
var comparer3 = Equality<Person>.CreateComparer(p => p.Birthday.Year);
var comparer4 = Equality<Person>.CreateComparer(p => p.Name, StringComparer.CurrentCultureIgnoreCase);
Personは単純なクラスです。
class Person
{
public int ID { get; set; }
public string Name { get; set; }
public DateTime Birthday { get; set; }
}
public class FuncEqualityComparer<T> : IEqualityComparer<T>
{
readonly Func<T, T, bool> _comparer;
readonly Func<T, int> _hash;
public FuncEqualityComparer( Func<T, T, bool> comparer )
: this( comparer, t => t.GetHashCode())
{
}
public FuncEqualityComparer( Func<T, T, bool> comparer, Func<T, int> hash )
{
_comparer = comparer;
_hash = hash;
}
public bool Equals( T x, T y )
{
return _comparer( x, y );
}
public int GetHashCode( T obj )
{
return _hash( obj );
}
}
拡張機能付き:-
public static class SequenceExtensions
{
public static bool SequenceEqual<T>( this IEnumerable<T> first, IEnumerable<T> second, Func<T, T, bool> comparer )
{
return first.SequenceEqual( second, new FuncEqualityComparer<T>( comparer ) );
}
public static bool SequenceEqual<T>( this IEnumerable<T> first, IEnumerable<T> second, Func<T, T, bool> comparer, Func<T, int> hash )
{
return first.SequenceEqual( second, new FuncEqualityComparer<T>( comparer, hash ) );
}
}
(ドイツ語のテキスト)での実装ラムダ式を使用したIEqualityCompareの実装 を使用したは、null値が考慮され、拡張メソッドを使用してIEqualityComparerが生成されます。
LinqユニオンでIEqualityComparerを作成するには、次のように記述するだけです。
persons1.Union(persons2, person => person.LastName)
比較子:
public class LambdaEqualityComparer<TSource, TComparable> : IEqualityComparer<TSource>
{
Func<TSource, TComparable> _keyGetter;
public LambdaEqualityComparer(Func<TSource, TComparable> keyGetter)
{
_keyGetter = keyGetter;
}
public bool Equals(TSource x, TSource y)
{
if (x == null || y == null) return (x == null && y == null);
return object.Equals(_keyGetter(x), _keyGetter(y));
}
public int GetHashCode(TSource obj)
{
if (obj == null) return int.MinValue;
var k = _keyGetter(obj);
if (k == null) return int.MaxValue;
return k.GetHashCode();
}
}
また、型推論をサポートする拡張メソッドを追加する必要があります
public static class LambdaEqualityComparer
{
// source1.Union(source2, lambda)
public static IEnumerable<TSource> Union<TSource, TComparable>(
this IEnumerable<TSource> source1,
IEnumerable<TSource> source2,
Func<TSource, TComparable> keySelector)
{
return source1.Union(source2,
new LambdaEqualityComparer<TSource, TComparable>(keySelector));
}
}
ただ1つの最適化:委任するのではなく、すぐに使えるEqualityComparerを値の比較に使用できます。
実際の比較ロジックは、すでにオーバーロードされているかもしれないGetHashCode()とEquals()に留まるので、これにより実装がよりクリーンになります。
これがコードです:
public class MyComparer<T> : IEqualityComparer<T>
{
public bool Equals(T x, T y)
{
return EqualityComparer<T>.Default.Equals(x, y);
}
public int GetHashCode(T obj)
{
return obj.GetHashCode();
}
}
オブジェクトのGetHashCode()およびEquals()メソッドをオーバーロードすることを忘れないでください。
この投稿は私を助けました: c#2つの一般的な値を比較する
スシル
オリップの答えは素晴らしいです。oripの答えを拡張する:
ソリューションの鍵は「拡張メソッド」を使用して「匿名型」を転送することだと思います。
public static class Comparer
{
public static IEqualityComparer<T> CreateComparerForElements<T>(this IEnumerable<T> enumerable, Func<T, object> keyExtractor)
{
return new KeyEqualityComparer<T>(keyExtractor);
}
}
使用法:
var n = ItemList.Select(s => new { s.Vchr, s.Id, s.Ctr, s.Vendor, s.Description, s.Invoice }).ToList();
n.AddRange(OtherList.Select(s => new { s.Vchr, s.Id, s.Ctr, s.Vendor, s.Description, s.Invoice }).ToList(););
n = n.Distinct(x=>new{Vchr=x.Vchr,Id=x.Id}).ToList();
public static Dictionary<TKey, TValue> Distinct<TKey, TValue>(this IEnumerable<TValue> items, Func<TValue, TKey> selector)
{
Dictionary<TKey, TValue> result = null;
ICollection collection = items as ICollection;
if (collection != null)
result = new Dictionary<TKey, TValue>(collection.Count);
else
result = new Dictionary<TKey, TValue>();
foreach (TValue item in items)
result[selector(item)] = item;
return result;
}
これにより、次のようなラムダを使用してプロパティを選択できるようになります。 .Select(y => y.Article).Distinct(x => x.ArticleID);
私は既存のクラスを知りませんが、次のようなものです:
public class MyComparer<T> : IEqualityComparer<T>
{
private Func<T, T, bool> _compare;
MyComparer(Func<T, T, bool> compare)
{
_compare = compare;
}
public bool Equals(T x, Ty)
{
return _compare(x, y);
}
public int GetHashCode(T obj)
{
return obj.GetHashCode();
}
}
注:実際にはまだコンパイルして実行していないため、タイプミスやその他のバグがある可能性があります。
IEqualityComparer<T>
、GetHashCode
まっすぐに壊れています。