เกี่ยวกับความสำคัญของ 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
หลุดออกมาก็เป็นเพียงการหักตรง