Önemi hakkında GetHashCode
Diğerleri, herhangi bir özel IEqualityComparer<T>
uygulamanın gerçekten bir GetHashCode
yöntem içermesi gerektiği gerçeğini zaten yorumlamışlardır ; ama kimse nedenini detaylı bir şekilde açıklamaya zahmet etmedi .
İşte nedeni. Sorunuz özellikle LINQ uzatma yöntemlerinden bahseder; Neredeyse bunların hepsi , verimlilik için dahili olarak karma tablolar kullandığından, düzgün çalışması için karma kodlara güvenir.
Örneğin alın Distinct
. Kullanılan tek şey bir yöntemse, bu uzantı yönteminin sonuçlarını düşünün Equals
. Bir öğenin sıralı olarak taranıp taranmadığını nasıl anlarsınız Equals
? Daha önce baktığınız tüm değerler koleksiyonunu numaralandırır ve bir eşleşme olup olmadığını kontrol edersiniz. Bu, Distinct
O (N ) bir algoritma yerine en kötü durum O (N 2 ) algoritmasının kullanılmasıyla sonuçlanır!
Neyse ki, durum bu değil. Distinct
gelmez sadece kullanmak Equals
; o da kullanır GetHashCode
. Aslında, bir düzgün sağlayan bir olmadan kesinlikle düzgün çalışmazIEqualityComparer<T>
GetHashCode
. Aşağıda bunu gösteren yapmacık bir örnek var.
Aşağıdaki türe sahip olduğumu varsayalım:
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);
}
}
Şimdi bir a'ya sahip olduğumu List<Value>
ve tüm öğeleri farklı bir ada sahip bulmak istediğimi söyleyin . Bu, Distinct
özel bir eşitlik karşılaştırıcısı kullanmak için mükemmel bir kullanım örneğidir . O halde Aku'nun cevabındakiComparer<T>
sınıfı kullanalım :
var comparer = new Comparer<Value>((x, y) => x.Name == y.Name);
Şimdi, Value
aynı Name
özelliğe sahip bir grup öğeye sahipsek , bunların tümü ile döndürülen tek bir değere daraltılmalıdır Distinct
, değil mi? Bakalım...
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);
}
Çıktı:
x: 1346013431
x: 1388845717
x: 1576754134
x: 1104067189
x: 1144789201
x: 1862076501
x: 1573781440
x: 646797592
x: 655632802
x: 1206819377
Hmm, bu işe yaramadı, değil mi?
Peki ya GroupBy
? Hadi deneyelim:
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);
}
}
Çıktı:
[KEY = 'x: 1346013431']
x: 1346013431
[ANAHTAR = 'x: 1388845717']
x: 1388845717
[KEY = 'x: 1576754134']
x: 1576754134
[KEY = 'x: 1104067189']
x: 1104067189
[KEY = 'x: 1144789201']
x: 1144789201
[KEY = 'x: 1862076501']
x: 1862076501
[KEY = 'x: 1573781440']
x: 1573781440
[KEY = 'x: 646797592']
x: 646797592
[KEY = 'x: 655632802']
x: 655632802
[KEY = 'x: 1206819377']
x: 1206819377
Yine: işe yaramadı.
Düşünürseniz, dahili olarak Distinct
bir HashSet<T>
(veya eşdeğerini) GroupBy
kullanmak ve dahili olarak bir gibi bir şey kullanmak mantıklı olacaktır Dictionary<TKey, List<T>>
. Bu, bu yöntemlerin neden işe yaramadığını açıklayabilir mi? Hadi bunu deneyelim:
var uniqueValues = new HashSet<Value>(values, comparer);
foreach (Value x in uniqueValues)
{
Console.WriteLine(x);
}
Çıktı:
x: 1346013431
x: 1388845717
x: 1576754134
x: 1104067189
x: 1144789201
x: 1862076501
x: 1573781440
x: 646797592
x: 655632802
x: 1206819377
Evet ... mantıklı gelmeye mi başladın?
Umarım bu örneklerden GetHashCode
, herhangi bir IEqualityComparer<T>
uygulamaya uygun bir uygulamanın dahil edilmesinin neden bu kadar önemli olduğu açıktır.
Orijinal cevap
Orip'in cevabını genişletmek için :
Burada yapılabilecek birkaç iyileştirme var.
- İlk olarak,
Func<T, TKey>
yerine a alırdım Func<T, object>
; bu, değer türü anahtarların keyExtractor
kendi içinde kutulanmasını önleyecektir .
- İkinci olarak, aslında bir
where TKey : IEquatable<TKey>
sınırlama eklerdim; bu, Equals
çağrıda kutulamayı önleyecektir ( object.Equals
bir object
parametre alır ; IEquatable<TKey>
bir TKey
parametreyi kutulamadan almak için bir uygulamaya ihtiyacınız vardır ). Açıkçası bu çok ciddi bir kısıtlama oluşturabilir, dolayısıyla kısıtlama olmadan bir temel sınıf ve onunla türetilmiş bir sınıf yapabilirsiniz.
Sonuç olarak ortaya çıkan kod şöyle görünebilir:
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));
}
}
IEqualityComparer<T>
yapraklar oGetHashCode
dışarı sadece düz bozuldu.