เกี่ยวกับความสำคัญของ GetHashCode
คนอื่น ๆ ได้แสดงความคิดเห็นเกี่ยวกับความจริงที่ว่าการIEqualityComparer<T>ใช้งานแบบกำหนดเองควรมีGetHashCodeวิธีการด้วย แต่ไม่มีใครสนใจที่จะอธิบายว่าทำไมโดยละเอียด
นี่คือเหตุผล คำถามของคุณกล่าวถึงวิธีการขยาย LINQ โดยเฉพาะ สิ่งเหล่านี้เกือบทั้งหมดใช้รหัสแฮชเพื่อให้ทำงานได้อย่างถูกต้องเนื่องจากใช้ตารางแฮชภายในเพื่อประสิทธิภาพ
ลองDistinctยกตัวอย่างเช่น พิจารณาความหมายของวิธีการขยายนี้หากใช้ทั้งหมดเป็นEqualsวิธีการ คุณจะทราบได้อย่างไรว่ามีการสแกนรายการตามลำดับแล้วEqualsหรือไม่หากคุณมีเพียง คุณระบุคอลเล็กชันของค่าทั้งหมดที่คุณได้ดูและตรวจสอบการจับคู่ สิ่งนี้จะส่งผลให้Distinctใช้อัลกอริทึมO (N 2 ) กรณีเลวร้ายที่สุดแทนที่จะเป็น O (N) หนึ่ง!
โชคดีที่ไม่เป็นเช่นนั้น 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คุณสมบัติเดียวกันองค์ประกอบทั้งหมดก็ควรจะยุบรวมเป็นค่าเดียวที่ส่งกลับมา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));
}
}
IEqualityComparer<T>ที่GetHashCodeหลุดออกมาก็เป็นเพียงการหักตรง