IEqualityComparer <T> และ IEquatable <T> แตกต่างกันอย่างไร


151

ฉันต้องการที่จะเข้าใจสถานการณ์ที่IEqualityComparer<T>และIEquatable<T>ควรจะใช้ เอกสาร MSDN ของทั้งคู่มีลักษณะคล้ายกันมาก


1
MSDN sez: "อินเทอร์เฟซนี้อนุญาตให้ใช้งานการเปรียบเทียบความเท่าเทียมกันที่กำหนดเองสำหรับคอลเลกชัน " ซึ่งแน่นอนว่าจะสรุปในคำตอบที่เลือก MSDN ยังแนะนำให้สืบทอดEqualityComparer<T>แทนการใช้อินเตอร์เฟส "เพราะEqualityComparer<T>การทดสอบความเท่าเทียมกันโดยใช้IEquatable<T>
Radarbob

... ข้างต้นแสดงให้เห็นว่าฉันควรจะสร้างคอลเลกชันที่กำหนดเองสำหรับการใด ๆการดำเนินการT IEquatable<T>คอลเลคชั่นList<T>จะมีบั๊กที่ซ่อนอยู่ในนั้นบ้างไหม?
Radarbob

คำอธิบายที่ดีanotherchris.net/csharp/...
Ramil Shavaleev

@RamilShavaleev ลิงก์เสีย
ChessMax

คำตอบ:


117

IEqualityComparer<T>Tเป็นอินเตอร์เฟซสำหรับวัตถุที่ดำเนินการเปรียบเทียบวัตถุสองชนิดที่

IEquatable<T>สำหรับวัตถุประเภทTเพื่อให้สามารถเปรียบเทียบตัวเองกับประเภทอื่นที่เหมือนกัน


1
ความแตกต่างที่เหมือนกันคือระหว่าง IComparable / IComparer
boctulus

3
กัปตันชัดเจน
Stepan Ivanenko

(แก้ไข) IEqualityComparer<T>เป็นอินเตอร์เฟซสำหรับวัตถุ(ซึ่งมักจะเป็นระดับที่แตกต่างกันที่มีน้ำหนักเบาจากT)ที่ให้ฟังก์ชั่นการเปรียบเทียบที่ทำงานบนT
rwong

61

เมื่อตัดสินใจว่าจะใช้IEquatable<T>หรือIEqualityComparer<T>มีใครถาม:

มีวิธีที่ต้องการในการทดสอบสองอินสแตนซ์ของTเพื่อความเท่าเทียมกันหรือมีหลายวิธีที่ถูกต้องเท่าเทียมกัน?

  • หากมีเพียงวิธีเดียวในการทดสอบสองอินสแตนซ์ของTความเท่าเทียมกันหรือหากหนึ่งในหลายวิธีที่ต้องการนั้นIEquatable<T>จะเป็นตัวเลือกที่ถูกต้อง: อินเทอร์เฟซนี้ควรจะดำเนินการด้วยTตัวเองเท่านั้นดังนั้นอินสแตนซ์เดียวTของ Tวิธีการเปรียบเทียบตัวเองไปอินสแตนซ์ของผู้อื่น

  • ในทางกลับกันหากมีวิธีการที่เหมาะสมหลายวิธีในการเปรียบเทียบสองTs เพื่อความเท่าเทียมกันนั้น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>ใช้งานคุณสามารถสลับมันได้ถ้ามันไม่ดีพอสำหรับวัตถุประสงค์ของคุณ


6
วิธีการเปรียบเทียบที่แนะนำสำหรับการทดสอบความเท่าเทียมกันสองจุดนั้นแตกเนื่องจากIEqualityComparer<T>การดำเนินการที่ถูกต้องใด ๆจะต้องใช้ความสัมพันธ์ที่เท่าเทียมกันซึ่งหมายความว่าในทุกกรณีที่วัตถุสองรายการเปรียบเทียบเท่ากับหนึ่งในสามพวกเขาจะต้องเปรียบเทียบกัน คลาสใดที่นำไปใช้IEquatable<T>หรือIEqualityComparer<T>เป็นแฟชั่นที่ตรงกันข้ามกับด้านบนจะใช้งานไม่ได้
supercat

5
ตัวอย่างที่ดีกว่าคือการเปรียบเทียบระหว่างสตริง มันมีเหตุผลที่จะมีวิธีการเปรียบเทียบสตริงซึ่งพิจารณาถึงสตริงที่เท่ากันหากมีลำดับไบต์เดียวกัน แต่มีวิธีการเปรียบเทียบที่มีประโยชน์อื่น ๆซึ่งมีความสัมพันธ์ที่เท่าเทียมกันเช่นการเปรียบเทียบแบบตัวพิมพ์เล็กและตัวพิมพ์ใหญ่ โปรดทราบว่าสิ่งIEqualityComparer<String>ที่พิจารณาว่า "สวัสดี" เท่ากับ "Hello" และ "hElLo" จะต้องพิจารณาว่า "Hello" และ "hElLo" เท่ากัน แต่สำหรับวิธีการเปรียบเทียบส่วนใหญ่ที่ไม่มีปัญหา
supercat

@supercat ขอบคุณสำหรับข้อเสนอแนะที่มีคุณค่า มีโอกาสที่ฉันจะไม่แก้ไขเรื่องคำตอบของฉันในอีกไม่กี่วันข้างหน้าดังนั้นหากคุณต้องการโปรดแก้ไขได้ตามที่เห็นสมควร
stakx - ไม่ได้มีส่วนร่วม

นี่คือสิ่งที่ฉันต้องการและสิ่งที่ฉันทำ อย่างไรก็ตามมีความจำเป็นต้องแทนที่ GetHashCode ซึ่งมันจะกลับเท็จเมื่อค่าแตกต่างกัน คุณจัดการ GetHashCode ของคุณอย่างไร
teapeng

@tapeng: ไม่แน่ใจว่ากรณีการใช้งานเฉพาะที่คุณกำลังพูดถึง โดยทั่วไปแล้วGetHashCodeควรอนุญาตให้คุณกำหนดได้อย่างรวดเร็วว่าค่าสองค่าแตกต่างกันหรือไม่ กฎมีคร่าว ๆ ดังนี้: (1) GetHashCodeต้องสร้างรหัสแฮชเดียวกันสำหรับค่าเดียวกันเสมอ (2) GetHashCodeควรเร็ว (เร็วกว่าEquals) (3) GetHashCodeไม่จำเป็นต้องแม่นยำ (ไม่แม่นยำเท่าEquals) ซึ่งหมายความว่ามันอาจสร้างรหัสแฮชเดียวกันสำหรับค่าที่แตกต่างกัน ยิ่งคุณแม่นยำมากขึ้นเท่าไหร่ก็ยิ่งดี แต่ก็สำคัญที่จะต้องทำให้มันเร็วขึ้น
stakx - ไม่ร่วมให้ข้อมูลใน

27

คุณมีคำจำกัดความพื้นฐานของสิ่งที่พวกเขาเป็นอยู่แล้ว ในระยะสั้นหากคุณใช้IEquatable<T>ในระดับTที่Equalsวิธีการเกี่ยวกับวัตถุของการพิมพ์ที่Tบอกคุณว่าวัตถุเอง (อย่างใดอย่างหนึ่งที่มีการทดสอบเพื่อความเท่าเทียมกัน) Tจะมีค่าเท่ากับตัวอย่างของประเภทเดียวกันอีก ในขณะที่IEqualityComparer<T>เป็นสำหรับการทดสอบความเท่าเทียมกันของสองกรณีของโดยทั่วไปนอกขอบเขตของอินสแตนซ์ของTT

สำหรับสิ่งที่พวกเขากำลังจะสับสนในตอนแรก จากคำนิยามมันควรจะชัดเจนว่าด้วยเหตุนี้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()
hyankov

@HristoYankov obj.Age.GetHashCodeควรกลับ จะแก้ไข
nawfal

โปรดอธิบายว่าเพราะเหตุใดผู้เปรียบเทียบจึงต้องตั้งสมมติฐานเช่นนี้เกี่ยวกับสิ่งที่แฮชของวัตถุเปรียบเทียบ เครื่องมือเปรียบเทียบของคุณไม่ทราบว่าบุคคลคำนวณแฮชของตนอย่างไร
hyankov

@HristoYankov Comparer ของคุณไม่ทราบว่าคนคำนวณแฮช - แน่นอนว่านั่นคือเหตุผลที่เรายังไม่ได้เรียกโดยตรงperson.GetHashCodeได้ทุกที่ .... เหตุผลที่คุณจะแทนที่ที่? - เราแทนที่เพราะประเด็นทั้งหมดIEqualityComparerคือการใช้การเปรียบเทียบที่แตกต่างกันตามกฎของเรา - กฎที่เรารู้จักดี
nawfal

ในตัวอย่างของฉันฉันต้องฐานการเปรียบเทียบของฉันออกคุณสมบัติดังนั้นผมเรียกAge Age.GetHashCodeอายุเป็นประเภทintแต่สิ่งที่ประเภทเป็นสัญญารหัสกัญชาใน NET different objects can have equal hash but equal objects cant ever have different hashesเป็นว่า นี่คือสัญญาที่เราเชื่อได้อย่างสุ่มสี่สุ่มห้า แน่นอนว่าผู้เปรียบเทียบที่กำหนดเองของเราควรปฏิบัติตามกฎนี้เช่นกัน หากการเรียกใช้การsomeObject.GetHashCodeแบ่งสิ่งต่าง ๆ แสดงว่าเป็นปัญหาของผู้ใช้งานประเภทsomeObjectไม่ใช่ปัญหาของเรา
nawfal

11

IEqualityComparer สำหรับใช้เมื่อความเท่าเทียมกันของวัตถุสองชิ้นถูกนำไปใช้ภายนอกเช่นหากคุณต้องการกำหนดตัวเปรียบเทียบสำหรับสองประเภทที่คุณไม่ได้มีแหล่งที่มาหรือในกรณีที่ความเท่าเทียมกันระหว่างสองสิ่งนั้นสมเหตุสมผลในบริบทที่ จำกัด

IEquatable สำหรับวัตถุนั้นเอง (สิ่งหนึ่งที่ถูกนำมาเปรียบเทียบเพื่อความเท่าเทียมกัน) เพื่อนำไปใช้


คุณเป็นคนเดียวที่ยกปัญหา "การเสียบในความเท่าเทียมกัน" (การใช้งานภายนอก) จริง ๆ แล้ว บวก 1
Royi Namir

4

หนึ่งเปรียบเทียบสองTs อื่น ๆ ที่สามารถเปรียบเทียบตัวเองกับคนอื่น ๆTs โดยปกติคุณจะต้องใช้งานครั้งละหนึ่งรายการเท่านั้นไม่ใช่ทั้งสองอย่าง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.