ฉันต้องการที่จะเข้าใจสถานการณ์ที่IEqualityComparer<T>
และIEquatable<T>
ควรจะใช้ เอกสาร MSDN ของทั้งคู่มีลักษณะคล้ายกันมาก
T
IEquatable<T>
คอลเลคชั่นList<T>
จะมีบั๊กที่ซ่อนอยู่ในนั้นบ้างไหม?
ฉันต้องการที่จะเข้าใจสถานการณ์ที่IEqualityComparer<T>
และIEquatable<T>
ควรจะใช้ เอกสาร MSDN ของทั้งคู่มีลักษณะคล้ายกันมาก
T
IEquatable<T>
คอลเลคชั่นList<T>
จะมีบั๊กที่ซ่อนอยู่ในนั้นบ้างไหม?
คำตอบ:
IEqualityComparer<T>
T
เป็นอินเตอร์เฟซสำหรับวัตถุที่ดำเนินการเปรียบเทียบวัตถุสองชนิดที่
IEquatable<T>
สำหรับวัตถุประเภทT
เพื่อให้สามารถเปรียบเทียบตัวเองกับประเภทอื่นที่เหมือนกัน
IEqualityComparer<T>
เป็นอินเตอร์เฟซสำหรับวัตถุ(ซึ่งมักจะเป็นระดับที่แตกต่างกันที่มีน้ำหนักเบาจากT
)ที่ให้ฟังก์ชั่นการเปรียบเทียบที่ทำงานบนT
เมื่อตัดสินใจว่าจะใช้IEquatable<T>
หรือIEqualityComparer<T>
มีใครถาม:
มีวิธีที่ต้องการในการทดสอบสองอินสแตนซ์ของ
T
เพื่อความเท่าเทียมกันหรือมีหลายวิธีที่ถูกต้องเท่าเทียมกัน?
หากมีเพียงวิธีเดียวในการทดสอบสองอินสแตนซ์ของT
ความเท่าเทียมกันหรือหากหนึ่งในหลายวิธีที่ต้องการนั้นIEquatable<T>
จะเป็นตัวเลือกที่ถูกต้อง: อินเทอร์เฟซนี้ควรจะดำเนินการด้วยT
ตัวเองเท่านั้นดังนั้นอินสแตนซ์เดียวT
ของ T
วิธีการเปรียบเทียบตัวเองไปอินสแตนซ์ของผู้อื่น
ในทางกลับกันหากมีวิธีการที่เหมาะสมหลายวิธีในการเปรียบเทียบสองT
s เพื่อความเท่าเทียมกันนั้นIEqualityComparer<T>
จะดูเหมาะสมกว่า: อินเตอร์เฟสนี้ไม่ได้หมายถึงการใช้งานด้วยT
ตัวเอง แต่โดยคลาสอื่น ๆ "ภายนอก" ดังนั้นเมื่อทดสอบสองอินสแตนซ์ของT
เพื่อความเท่าเทียมกันเนื่องจากT
ไม่มีความเข้าใจภายในเกี่ยวกับความเสมอภาคคุณจะต้องเลือกอย่างชัดเจนของIEqualityComparer<T>
อินสแตนซ์ที่ดำเนินการทดสอบตามข้อกำหนดเฉพาะของคุณ
ตัวอย่าง:
ลองพิจารณาสองประเภทนี้ (ซึ่งควรจะมีความหมายตามตัวอักษร ):
interface IIntPoint : IEquatable<IIntPoint>
{
int X { get; }
int Y { get; }
}
interface IDoublePoint // does not inherit IEquatable<IDoublePoint>; see below.
{
double X { get; }
double Y { get; }
}
ทำไมประเภทเหล่านี้เพียงประเภทเดียวจึงสืบทอดIEquatable<>
แต่ไม่ใช่ประเภทอื่น
ในทางทฤษฎีมีเพียงวิธีเดียวที่สมเหตุสมผลในการเปรียบเทียบสองอินสแตนซ์ของทั้งสองประเภท: พวกเขาจะเท่ากันถ้าX
และY
คุณสมบัติในทั้งสองกรณีเท่ากัน ตามความคิดนี้ทั้งสองประเภทควรนำมาใช้IEquatable<>
เพราะดูเหมือนว่ามีวิธีอื่นที่มีความหมายในการทำแบบทดสอบความเท่าเทียมกัน
ปัญหาที่นี่คือการเปรียบเทียบจำนวนจุดลอยตัวเพื่อความเท่าเทียมกันอาจจะไม่ทำงานตามที่คาดไว้เนื่องจากข้อผิดพลาดนาทีปัดเศษ มีวิธีการที่แตกต่างกันในการเปรียบเทียบตัวเลขทศนิยมสำหรับความเท่าเทียมกันโดยแต่ละวิธีมีข้อดีและข้อเสียต่างกันและคุณอาจต้องการเลือกวิธีที่เหมาะสม
sealed class DoublePointNearEqualityComparerByTolerance : IEqualityComparer<IDoublePoint>
{
public DoublePointNearEqualityComparerByTolerance(double tolerance) { … }
…
public bool Equals(IDoublePoint a, IDoublePoint b)
{
return Math.Abs(a.X - b.X) <= tolerance && Math.Abs(a.Y - b.Y) <= tolerance;
}
…
}
โปรดทราบว่าหน้าเว็บที่ฉันเชื่อมโยงกับ (ด้านบน) ระบุอย่างชัดเจนว่าการทดสอบความเท่าเทียมกันนี้มีจุดอ่อนบางอย่าง เนื่องจากนี่เป็นการIEqualityComparer<T>
ใช้งานคุณสามารถสลับมันได้ถ้ามันไม่ดีพอสำหรับวัตถุประสงค์ของคุณ
IEqualityComparer<T>
การดำเนินการที่ถูกต้องใด ๆจะต้องใช้ความสัมพันธ์ที่เท่าเทียมกันซึ่งหมายความว่าในทุกกรณีที่วัตถุสองรายการเปรียบเทียบเท่ากับหนึ่งในสามพวกเขาจะต้องเปรียบเทียบกัน คลาสใดที่นำไปใช้IEquatable<T>
หรือIEqualityComparer<T>
เป็นแฟชั่นที่ตรงกันข้ามกับด้านบนจะใช้งานไม่ได้
IEqualityComparer<String>
ที่พิจารณาว่า "สวัสดี" เท่ากับ "Hello" และ "hElLo" จะต้องพิจารณาว่า "Hello" และ "hElLo" เท่ากัน แต่สำหรับวิธีการเปรียบเทียบส่วนใหญ่ที่ไม่มีปัญหา
GetHashCode
ควรอนุญาตให้คุณกำหนดได้อย่างรวดเร็วว่าค่าสองค่าแตกต่างกันหรือไม่ กฎมีคร่าว ๆ ดังนี้: (1) GetHashCode
ต้องสร้างรหัสแฮชเดียวกันสำหรับค่าเดียวกันเสมอ (2) GetHashCode
ควรเร็ว (เร็วกว่าEquals
) (3) GetHashCode
ไม่จำเป็นต้องแม่นยำ (ไม่แม่นยำเท่าEquals
) ซึ่งหมายความว่ามันอาจสร้างรหัสแฮชเดียวกันสำหรับค่าที่แตกต่างกัน ยิ่งคุณแม่นยำมากขึ้นเท่าไหร่ก็ยิ่งดี แต่ก็สำคัญที่จะต้องทำให้มันเร็วขึ้น
คุณมีคำจำกัดความพื้นฐานของสิ่งที่พวกเขาเป็นอยู่แล้ว ในระยะสั้นหากคุณใช้IEquatable<T>
ในระดับT
ที่Equals
วิธีการเกี่ยวกับวัตถุของการพิมพ์ที่T
บอกคุณว่าวัตถุเอง (อย่างใดอย่างหนึ่งที่มีการทดสอบเพื่อความเท่าเทียมกัน) T
จะมีค่าเท่ากับตัวอย่างของประเภทเดียวกันอีก ในขณะที่IEqualityComparer<T>
เป็นสำหรับการทดสอบความเท่าเทียมกันของสองกรณีของโดยทั่วไปนอกขอบเขตของอินสแตนซ์ของT
T
สำหรับสิ่งที่พวกเขากำลังจะสับสนในตอนแรก จากคำนิยามมันควรจะชัดเจนว่าด้วยเหตุนี้IEquatable<T>
(กำหนดไว้ในคลาสT
เอง) ควรเป็นมาตรฐาน de พฤตินัยเพื่อแสดงถึงเอกลักษณ์ของวัตถุ / อินสแตนซ์ HashSet<T>
, Dictionary<T, U>
(พิจารณาGetHashCode
จะถูกแทนเช่นกัน) Contains
ในList<T>
ฯลฯ ทำให้การใช้นี้ การดำเนินการIEqualityComparer<T>
เกี่ยวกับการT
ไม่ได้ช่วยดังกล่าวข้างต้นกรณีทั่วไป ต่อจากนั้นมีค่าน้อยสำหรับการใช้งานIEquatable<T>
ในชั้นอื่น ๆ นอกเหนือจากT
นี้ นี้:
class MyClass : IEquatable<T>
ไม่ค่อยมีเหตุผล
ในทางกลับกัน
class T : IEquatable<T>
{
//override ==, !=, GetHashCode and non generic Equals as well
public bool Equals(T other)
{
//....
}
}
มันควรจะทำอย่างไร
IEqualityComparer<T>
อาจมีประโยชน์เมื่อคุณต้องการการตรวจสอบความเท่าเทียมกันแบบกำหนดเอง แต่ไม่ใช่กฎทั่วไป ตัวอย่างเช่นในชั้นเรียนPerson
ณ จุดหนึ่งคุณอาจต้องทดสอบความเท่าเทียมกันของคนสองคนตามอายุของพวกเขา ในกรณีนี้คุณสามารถทำได้:
class Person
{
public int Age;
}
class AgeEqualityTester : IEqualityComparer<Person>
{
public bool Equals(Person x, Person y)
{
return x.Age == y.Age;
}
public int GetHashCode(Person obj)
{
return obj.Age.GetHashCode;
}
}
เพื่อทดสอบพวกเขาลอง
var people = new Person[] { new Person { age = 23 } };
Person p = new Person() { age = 23 };
print people.Contains(p); //false;
print people.Contains(p, new AgeEqualityTester()); //true
ในทำนองเดียวกันIEqualityComparer<T>
ที่T
ไม่สมเหตุสมผล
class Person : IEqualityComparer<Person>
ใช้งานได้จริง แต่ดูไม่ดีต่อสายตาและเอาชนะตรรกะ
IEquatable<T>
โดยปกติแล้วสิ่งที่คุณต้องการ นอกจากนี้ในอุดมคติคุณสามารถมีเพียงหนึ่งIEquatable<T>
ในขณะที่หลาย ๆIEqualityComparer<T>
เป็นไปได้ตามเกณฑ์ที่แตกต่าง
IEqualityComparer<T>
และIEquatable<T>
เป็นสิ่งที่คล้ายคลึงกับComparer<T>
และIComparable<T>
ซึ่งจะใช้เพื่อการเปรียบเทียบมากกว่าเท่า; หัวข้อที่ดีที่นี่ที่ฉันเขียนคำตอบเดียวกัน :)
public int GetHashCode(Person obj)
ควรกลับมาobj.GetHashCode()
obj.Age.GetHashCode
ควรกลับ จะแก้ไข
person.GetHashCode
ได้ทุกที่ .... เหตุผลที่คุณจะแทนที่ที่? - เราแทนที่เพราะประเด็นทั้งหมดIEqualityComparer
คือการใช้การเปรียบเทียบที่แตกต่างกันตามกฎของเรา - กฎที่เรารู้จักดี
Age
Age.GetHashCode
อายุเป็นประเภทint
แต่สิ่งที่ประเภทเป็นสัญญารหัสกัญชาใน NET different objects can have equal hash but equal objects cant ever have different hashes
เป็นว่า นี่คือสัญญาที่เราเชื่อได้อย่างสุ่มสี่สุ่มห้า แน่นอนว่าผู้เปรียบเทียบที่กำหนดเองของเราควรปฏิบัติตามกฎนี้เช่นกัน หากการเรียกใช้การsomeObject.GetHashCode
แบ่งสิ่งต่าง ๆ แสดงว่าเป็นปัญหาของผู้ใช้งานประเภทsomeObject
ไม่ใช่ปัญหาของเรา
IEqualityComparer สำหรับใช้เมื่อความเท่าเทียมกันของวัตถุสองชิ้นถูกนำไปใช้ภายนอกเช่นหากคุณต้องการกำหนดตัวเปรียบเทียบสำหรับสองประเภทที่คุณไม่ได้มีแหล่งที่มาหรือในกรณีที่ความเท่าเทียมกันระหว่างสองสิ่งนั้นสมเหตุสมผลในบริบทที่ จำกัด
IEquatable สำหรับวัตถุนั้นเอง (สิ่งหนึ่งที่ถูกนำมาเปรียบเทียบเพื่อความเท่าเทียมกัน) เพื่อนำไปใช้
หนึ่งเปรียบเทียบสองT
s อื่น ๆ ที่สามารถเปรียบเทียบตัวเองกับคนอื่น ๆT
s โดยปกติคุณจะต้องใช้งานครั้งละหนึ่งรายการเท่านั้นไม่ใช่ทั้งสองอย่าง
EqualityComparer<T>
แทนการใช้อินเตอร์เฟส "เพราะEqualityComparer<T>
การทดสอบความเท่าเทียมกันโดยใช้IEquatable<T>