อะไรคือความแตกต่างระหว่าง == และ Equals () สำหรับ primitives ใน C #?


180

พิจารณารหัสนี้:

int age = 25;
short newAge = 25;
Console.WriteLine(age == newAge);  //true
Console.WriteLine(newAge.Equals(age)); //false
Console.ReadLine();

ทั้งสองintและshortเป็นประเภทดั้งเดิม แต่การเปรียบเทียบกับ==ผลตอบแทนจริงและเปรียบเทียบกับEqualsผลตอบแทนที่เป็นเท็จ

ทำไม?


9
@OrangeDog โปรดคิดเกี่ยวกับคำถามแล้วลงคะแนนให้ปิด

4
สิ่งนี้ขาดความพยายามย้อนกลับที่เห็นได้ชัด:Console.WriteLine(age.Equals(newAge));
ตอบ

3
สำเนาไม่ได้อธิบายพฤติกรรมนี้ มันเป็นแค่เรื่องEquals()ทั่วไป
Slaks

37
ฉันตอบคำถามตรงนี้ในบล็อกความครอบคลุมเมื่อไม่กี่วันที่ผ่านมา blog.coverity.com/2014/01/13/inconsistent-equality
Eric Lippert

5
@CodesInChaos: สเปคใช้คำว่า "ประเภทดั้งเดิม" สองครั้งโดยที่ไม่เคยนิยาม ความหมายก็คือประเภทดั้งเดิมนั้นเป็นชนิดของมูลค่าในตัว แต่ไม่ชัดเจน ฉันได้แนะนำให้ Mads คำว่าเพียงแค่ถูกครอบงำจากสเปคที่ดูเหมือนว่าจะสร้างความสับสนมากกว่าที่จะลบ
Eric Lippert

คำตอบ:


262

คำตอบสั้น ๆ :

ความเท่าเทียมกันนั้นซับซ้อน

คำตอบโดยละเอียด:

ชนิดพื้นฐานจะลบล้างฐานobject.Equals(object)และคืนค่าจริงถ้ากล่องobjectนั้นเป็นประเภทและค่าเดียวกัน (โปรดทราบว่ามันจะใช้ได้กับประเภทที่สามารถลบได้ด้วยเช่นกันชนิดที่ไม่ใช่ค่า null จะเป็นกล่องสำหรับอินสแตนซ์ของประเภทพื้นฐาน)

เนื่องจากnewAgeเป็น a short, Equals(object)เมธอดของมันจะคืนค่าเป็นจริงถ้าคุณผ่านการย่อบ็อกซ์ด้วยค่าเดียวกัน คุณกำลังส่งกล่องintแล้วมันจะส่งกลับเท็จ

ในทางตรงกันข้าม==ผู้ปฏิบัติงานจะถูกกำหนดให้รับค่าสองints (หรือshorts หรือlongs)
เมื่อคุณเรียกมันด้วยinta และ a shortคอมไพเลอร์จะแปลงค่าshortเป็นintและเปรียบเทียบผลลัพธ์ints โดยค่า

วิธีอื่น ๆ ในการทำให้มันใช้งานได้

ประเภทดั้งเดิมมีEquals()วิธีการของตนเองที่ยอมรับประเภทเดียวกัน
ถ้าคุณเขียนage.Equals(newAge)คอมไพเลอร์จะเลือกint.Equals(int)เป็นเกินที่ดีที่สุดและโดยปริยายแปลงไปshort intมันจะกลับมาtrueเนื่องจากวิธีนี้จะเปรียบเทียบints โดยตรง

shortยังมีshort.Equals(short)วิธีการ แต่intไม่สามารถแปลงเป็นโดยปริยายshortดังนั้นคุณไม่ได้เรียกมัน

คุณสามารถบังคับให้เรียกวิธีนี้โดยใช้นักแสดง:

Console.WriteLine(newAge.Equals((short)age)); // true

วิธีนี้จะโทรshort.Equals(short)โดยตรงโดยไม่ต้องชกมวย หากageมีขนาดใหญ่กว่า 32767 จะเป็นการยกเว้นข้อยกเว้นมากเกินไป

คุณสามารถเรียกshort.Equals(object)โอเวอร์โหลดได้ แต่ผ่านวัตถุที่บรรจุอยู่ในกล่องอย่างชัดเจนเพื่อให้ได้ชนิดเดียวกัน:

Console.WriteLine(newAge.Equals((object)(short)age)); // true

shortเช่นเดียวกับทางเลือกที่ก่อนหน้านี้จะโยนล้นถ้ามันไม่พอดีใน แตกต่างจากโซลูชันก่อนหน้านี้มันจะบรรจุshortลงในวัตถุเสียเวลาและหน่วยความจำ

รหัสแหล่งที่มา:

นี่คือทั้งสองEquals()วิธีจากซอร์สโค้ดจริง:

    public override bool Equals(Object obj) {
        if (!(obj is Int16)) {
            return false;
        }
        return m_value == ((Int16)obj).m_value;
    }

    public bool Equals(Int16 obj)
    {
        return m_value == obj;
    }

อ่านเพิ่มเติม:

ดูเอริค Lippert


3
@SLaks ถ้าเราโทรlong == int, intโดยปริยายแปลงเป็นlongใช่มั้ย?
Selman Genç

1
และใช่ฉันเขียนทุกอย่างโดยไม่ลองทำ
slaks

1
โปรดจำไว้ว่าในรหัสของคำถามหากมีการเปลี่ยนแปลงint age = 25;ไปconst int age = 25;แล้วผลที่จะมีการเปลี่ยนแปลง นั่นเป็นเพราะการแปลงโดยนัยจากintถึงshortมีอยู่ในกรณีนั้น ดูนัยคงแปลงแสดงออก
Jeppe Stig Nielsen

2
@SLaks ใช่ แต่ถ้อยคำของคำตอบของคุณ "ค่าที่ส่ง" สามารถตีความได้ทั้งสองวิธี (เช่นค่าที่ส่งผ่านโดยนักพัฒนาหรือค่าที่ CLR ส่งผ่านจริงหลังจากยกเลิกการทำกล่อง) ฉันคาดเดาผู้ใช้ทั่วไปที่ไม่รู้คำตอบที่นี่จะอ่านมันเป็นอดีต
JaredPar

2
@ ราเชล: ยกเว้นว่าไม่เป็นความจริง; เริ่มต้น ==ดำเนินการเปรียบเทียบประเภทการอ้างอิงโดยการอ้างอิง สำหรับประเภทค่าและสำหรับประเภทที่มากเกินไป==จะไม่ทำเช่นนั้น
slaks

55

เพราะไม่มีการโอเวอร์โหลดสำหรับที่ยอมรับshort.Equals intดังนั้นสิ่งนี้จึงถูกเรียกว่า:

public override bool Equals(object obj)
{
    return obj is short && this == (short)obj;
}

objไม่ใช่short.. ดังนั้นมันจึงเป็นเท็จ


12

เมื่อคุณผ่านintไปยังshortเท่ากับคุณผ่านobject:

ป้อนคำอธิบายรูปภาพที่นี่ ดังนั้น pseudocode นี้จึงทำงาน:

return obj is short && this == (short)obj;

12

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

Object.Equals
http://msdn.microsoft.com/en-us/library/bsc2ak47(v=vs.110).aspx


10

==ถูกนำมาใช้สำหรับการตรวจสอบสภาพที่เท่ากันก็ถือได้ว่าเป็นผู้ประกอบการ (ผู้ประกอบการบูลีน) เพียงเพื่อเปรียบเทียบสิ่งที่ 2 และที่นี่ชนิดข้อมูลไม่ได้เรื่องที่มีจะเป็นประเภทหล่อทำและEqualsยังถูกนำมาใช้สำหรับการตรวจสอบสภาพเท่ากับ แต่ในกรณีนี้ประเภทข้อมูลควรเหมือนกัน N Equals เป็นวิธีการที่ไม่ใช่ตัวดำเนินการ

ด้านล่างเป็นตัวอย่างเล็ก ๆ ที่นำมาจากสิ่งที่คุณให้ไว้และสิ่งนี้จะชี้แจงความแตกต่างโดยสังเขป

int x=1;
short y=1;
x==y;//true
y.Equals(x);//false

ในตัวอย่างด้านบน X และ Y มีค่าเหมือนกันคือ 1 และเมื่อเราใช้==มันจะคืนค่าจริงเช่นเดียวกับในกรณีของ==ประเภทสั้นจะถูกแปลงเป็น int โดยคอมไพเลอร์และให้ผลลัพธ์

และเมื่อเราใช้Equalsการเปรียบเทียบเสร็จสิ้น แต่การหล่อประเภทไม่ได้กระทำโดยคอมไพเลอร์ดังนั้นจึงส่งคืนค่าเท็จ

พวกโปรดแจ้งให้เราทราบหากฉันผิด


6

ในหลายบริบทที่เมธอดหรืออาร์กิวเมนต์ตัวดำเนินการไม่ใช่ชนิดที่ต้องการคอมไพเลอร์ C # จะพยายามทำการแปลงชนิดโดยนัย หากคอมไพเลอร์สามารถทำให้ข้อโต้แย้งทั้งหมดเป็นที่พึงพอใจแก่ผู้ปฏิบัติงานและวิธีการของพวกเขาโดยการเพิ่มการแปลงโดยนัยมันจะทำเช่นนั้นโดยไม่มีการร้องเรียนแม้ว่าในบางกรณี (โดยเฉพาะอย่างยิ่งกับการทดสอบความเท่าเทียม!)

นอกจากนี้แต่ละประเภทค่าเช่นintหรือshortอธิบายทั้งมูลค่าและชนิดของวัตถุ (*) การแปลงโดยนัยมีอยู่เพื่อแปลงค่าเป็นค่าชนิดอื่นและแปลงค่าชนิดใด ๆ ให้เป็นวัตถุประเภทเดียวกัน แต่วัตถุชนิดต่าง ๆ ไม่สามารถแปลงสภาพเป็นกันและกันโดยปริยาย

หากหนึ่งใช้==ประกอบการที่จะเปรียบเทียบshortและintที่จะถูกแปลงโดยปริยายไปยังshort intถ้าค่าตัวเลขของมันเท่ากับว่าจากintการintที่มันถูกดัดแปลงจะเท่ากับintที่มันจะถูกเปรียบเทียบ หากหนึ่งในความพยายามที่จะใช้Equalsวิธีการในระยะสั้นเพื่อเปรียบเทียบด้วยintแต่การแปลงเท่านั้นโดยปริยายซึ่งจะตอบสนองเกินของวิธีการที่จะแปลงชนิดของวัตถุที่สอดคล้องกับEquals intเมื่อshortมีการถามว่ามันตรงกับวัตถุที่ส่งผ่านหรือไม่มันจะสังเกตว่าวัตถุที่เป็นปัญหานั้นเป็นintมากกว่าshortและดังนั้นจึงสรุปได้ว่ามันไม่สามารถเท่ากับ

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

int i = 16777217;
float f = 16777216.0f;

Console.WriteLine("{0}", i==f);

มีสามวิธีในที่หนึ่งอาจต้องการที่จะเปรียบเทียบเป็นไปint floatหนึ่งอาจต้องการที่จะรู้ว่า:

  1. ค่าที่ใกล้เคียงที่สุดที่เป็นไปได้floatในการintแข่งขันfloatคือ
  2. ส่วนจำนวนทั้งหมดของการfloatแข่งขันintหรือไม่
  3. ทำintและfloatแทนค่าตัวเลขเดียวกัน

หากพยายามเปรียบเทียบintและfloatโดยตรงรหัสที่คอมไพล์จะตอบคำถามแรก ไม่ว่าจะเป็นสิ่งที่โปรแกรมเมอร์ตั้งใจจะอยู่ไกลจากที่เห็นได้ชัด การเปลี่ยนการเปรียบเทียบ(float)i == fจะทำให้ชัดเจนว่าความหมายแรกนั้นตั้งใจไว้หรือ(double)i == (double)fจะทำให้โค้ดตอบคำถามที่สาม (และทำให้ชัดเจนว่าเป็นสิ่งที่ตั้งใจ)

(*) แม้ว่า C # spec จะพิจารณาถึงคุณค่าของประเภทเช่นSystem.Int32ว่าเป็นวัตถุประเภทSystem.Int32มุมมองดังกล่าวจะขัดแย้งกับข้อกำหนดที่โค้ดรันบนแพลตฟอร์มที่สเป็กนับถือค่าและวัตถุที่อาศัยอยู่ในจักรวาลที่แตกต่างกัน นอกจากนี้ถ้าTเป็นชนิดอ้างอิงและxเป็นTแล้วอ้างอิงจากประเภทนี้ควรจะสามารถที่จะอ้างถึงT xดังนั้นหากตัวแปรvประเภทInt32มีObjectการอ้างอิงประเภทObjectควรจะสามารถเก็บการอ้างอิงถึงvหรือเนื้อหา ในความเป็นจริงการอ้างอิงประเภทObjectจะสามารถชี้ไปยังวัตถุที่เก็บข้อมูลที่คัดลอกมาจากvแต่ไม่ถึงvตัวมันเองหรือเนื้อหา ที่จะแนะนำว่าไม่vObjectมิได้เนื้อหาของมันเป็นจริง


1
the only implicit conversion which would satisfy an overload of the Equals method would be the conversion to the object type corresponding to intไม่ถูกต้อง. แตกต่างจาก Java, C # ไม่ได้มีประเภทดั้งเดิมและกล่องแยกต่างหาก มันถูกบรรจุอยู่ในกล่องจะobjectเพราะที่เกินเฉพาะอื่น ๆ Equals()ของ
slaks

คำถามแรกและคำถามที่สามเหมือนกัน; floatค่าที่แน่นอนก็หายไปแล้วเมื่อการแปลง การส่ง a floatไปยัง a doubleจะไม่สร้างความแม่นยำใหม่อย่างน่าอัศจรรย์
slaks

@SLaks: ตามข้อกำหนด ECMA ซึ่งอธิบายถึงเครื่องเสมือนที่ใช้งาน C # การกำหนดประเภทค่าแต่ละค่าจะสร้างสองประเภทที่แตกต่างกัน C # spec อาจบอกว่าเนื้อหาของที่เก็บชนิดList<String>.Enumeratorและวัตถุ heap ประเภทList<String>.Enumeratorเดียวกัน แต่ข้อมูลจำเพาะ ECMA / CLI บอกว่าพวกเขาแตกต่างกันและแม้เมื่อใช้ใน C # พวกเขาทำงานแตกต่างกัน
supercat

@SLaks: ถ้าiและfถูกแปลงเป็นdoubleก่อนการเปรียบเทียบพวกเขาจะได้ผล 16777217.0 และ 16777216.0 ซึ่งเปรียบเทียบไม่เท่ากัน แปลงi floatจะให้ผลผลิต 16777216.0f fเมื่อเทียบกับที่เท่าเทียมกัน
supercat

@SLaks: bool SelfSame<T>(T p) { return Object.ReferenceEquals((Object)p,(Object)p);}สำหรับตัวอย่างง่ายๆของความแตกต่างระหว่างประเภทการจัดเก็บข้อมูลสถานที่และวัตถุประเภทกล่องให้พิจารณาวิธีการที่ วัตถุชนิดบรรจุกล่องที่สอดคล้องกับประเภทค่าสามารถตอบสนองประเภทพารามิเตอร์ของReferenceEqualsผ่านตัวตนที่รักษา upcast; อย่างไรก็ตามประเภทตำแหน่งที่เก็บข้อมูลนั้นต้องการการแปลงที่ไม่ใช่การเก็บข้อมูลประจำตัว ถ้าหล่อTไปUอัตราผลตอบแทนอ้างอิงถึงสิ่งอื่น ๆ กว่าเดิมTที่จะแนะนำให้ผมว่าไม่ได้จริงๆT U
supercat

5

Equals () เป็นเมธอดของSystem.Object Class
ไวยากรณ์: Public bool เสมือน Equals () การ
แนะนำถ้าเราต้องการเปรียบเทียบสถานะของวัตถุสองตัวเราควรใช้วิธี Equals ()

ตามที่ระบุไว้ข้างต้นคำตอบ==ผู้ประกอบการเปรียบเทียบค่าเหมือนกัน

โปรดอย่าสับสนกับ ReferenceEqual

Reference Equals ()
ไวยากรณ์: public static bool ReferenceEquals ()
กำหนดว่าอินสแตนซ์วัตถุที่ระบุนั้นเป็นอินสแตนซ์เดียวกันหรือไม่


8
นี่ไม่ได้ตอบคำถามเลย
slaks

SLAK ฉันไม่ได้อธิบายด้วยตัวอย่างนี่เป็นพื้นฐานของคำถามข้างต้น
Sugat Mankar

4

สิ่งที่คุณต้องตระหนักคือการทำ==มักจะจบลงด้วยการเรียกวิธีการ คำถามคือว่าการโทร==และEqualsจบลงด้วยการโทร / ทำสิ่งเดียวกัน

ด้วยประเภทการอ้างอิง==จะทำการตรวจสอบที่ 1 เสมอว่าการอ้างอิงนั้นเหมือนกัน ( Object.ReferenceEquals) Equalsในทางกลับกันสามารถแทนที่ได้และอาจตรวจสอบว่าค่าบางค่าเท่ากันหรือไม่

แก้ไข: เพื่อตอบ svick และเพิ่มความคิดเห็น SLaks นี่คือรหัส IL

int i1 = 0x22; // ldc.i4.s ie pushes an int32 on the stack
int i2 = 0x33; // ldc.i4.s 
short s1 = 0x11; // ldc.i4.s (same as for int32)
short s2 = 0x22; // ldc.i4.s 

s1 == i1 // ceq
i1 == s1 // ceq
i1 == i2 // ceq
s1 == s2 // ceq
// no difference between int and short for those 4 cases,
// anyway the shorts are pushed as integers.

i1.Equals(i2) // calls System.Int32.Equals
s1.Equals(s2) // calls System.Int16.Equals
i1.Equals(s1) // calls System.Int32.Equals: s1 is considered as an integer
// - again it was pushed as such on the stack)
s1.Equals(i1) // boxes the int32 then calls System.Int16.Equals
// - int16 has 2 Equals methods: one for in16 and one for Object.
// Casting an int32 into an int16 is not safe, so the Object overload
// must be used instead.

ดังนั้นวิธีการเปรียบเทียบสองints กับ == โทร? คำแนะนำ: ไม่มีoperator ==วิธีการInt32แต่มีหนึ่งสำหรับ String
svick

2
นี่ไม่ได้ตอบคำถามเลย
slaks

@SLaks: แน่นอนไม่ตอบคำถามเฉพาะเกี่ยวกับการเปรียบเทียบแบบสั้นและยาวคุณตอบไปแล้ว ฉันยังรู้สึกว่ามันน่าสนใจที่จะอธิบายว่า==ไม่เพียง แต่ทำเวทมนต์ในที่สุดมันก็เรียกวิธีการ (โปรแกรมเมอร์ส่วนใหญ่อาจไม่เคยดำเนินการ / แทนที่ผู้ประกอบการใด ๆ ) บางทีฉันอาจเพิ่มความคิดเห็นในคำถามของคุณแทนที่จะเพิ่มคำตอบของฉันเอง อย่าลังเลที่จะอัพเดทของคุณถ้าคุณรู้สึกว่าสิ่งที่ฉันพูดนั้นเกี่ยวข้องกัน
user276648

โปรดทราบว่า==ในประเภทดึกดำบรรพ์ไม่ได้เป็นตัวดำเนินการโอเวอร์โหลด แต่มีคุณสมบัติภาษาที่แท้จริงซึ่งรวบรวมไปยังceqคำสั่ง IL
slaks

3

== ในดั้งเดิม

Console.WriteLine(age == newAge);          // true

ในการเปรียบเทียบแบบดั้งเดิม == โอเปอเรเตอร์ทำงานค่อนข้างชัดเจนใน C # มีโอเปอร์เรเตอร์ == มากมายพร้อมใช้งาน

  • string == string
  • int == int
  • uint == uint
  • ยาว == ยาว
  • อื่น ๆ อีกมากมาย

ดังนั้นในกรณีนี้ไม่มีการแปลงโดยปริยายจากintไปshortแต่shortจะintเป็นไปได้ ดังนั้น newAge จะถูกแปลงเป็น int และการเปรียบเทียบเกิดขึ้นซึ่งผลตอบแทนจริงเนื่องจากทั้งสองมีค่าเท่ากัน ดังนั้นจึงเท่ากับ:

Console.WriteLine(age == (int)newAge);          // true

.Equals () ในดั้งเดิม

Console.WriteLine(newAge.Equals(age));         //false

ที่นี่เราต้องดูว่าวิธีการ Equals () คืออะไรเราเรียก Equals พร้อมกับตัวแปรชนิดสั้น ๆ ดังนั้นจึงมีความเป็นไปได้สามประการ:

  • เท่ากับ (วัตถุวัตถุ) // วิธีคงที่จากวัตถุ
  • เท่ากับ (วัตถุ) // วิธีเสมือนจากวัตถุ
  • เท่ากับ (สั้น) // ใช้ IEquatable.Equals (สั้น)

ประเภทแรกไม่ใช่กรณีที่นี่เนื่องจากจำนวนอาร์กิวเมนต์มีความแตกต่างกันที่เราเรียกด้วยอาร์กิวเมนต์ชนิดเดียวเท่านั้น ประการที่สามจะถูกกำจัดด้วยดังกล่าวข้างต้นโดยปริยายการแปลง int เป็นระยะสั้นเป็นไปไม่ได้ ดังนั้นที่นี่Equals(object)เรียกว่าประเภทที่สอง short.Equals(object)คือ:

bool Equals(object z)
{
  return z is short && (short)z == this;
}

ดังนั้นที่นี่สภาพได้รับการทดสอบz is shortซึ่งเป็นเท็จเนื่องจาก z เป็น int ดังนั้นมันกลับเท็จ

นี่คือรายละเอียดบทความจาก Eric Lippert

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