เหตุใด (object) 0 == (object) 0 จึงแตกต่างจาก ((object) 0) .Equals ((object) 0)?


117

เหตุใดนิพจน์ต่อไปนี้จึงแตกต่างกัน

[1]  (object)0 == (object)0 //false
[2]  ((object)0).Equals((object)0) // true

อันที่จริงฉันสามารถเข้าใจ [1] ได้ทั้งหมดเพราะรันไทม์. NET อาจboxเป็นจำนวนเต็มและเริ่มเปรียบเทียบการอ้างอิงแทน แต่ทำไม [2] จึงแตกต่างกัน?


36
ตกลงตอนนี้คุณเข้าใจคำตอบของคำถามนี้แล้วให้ตรวจสอบความเข้าใจของคุณโดยการทำนายผลลัพธ์ของ: short myShort = 0; int myInt = 0; Console.WriteLine("{0}{1}{2}", myShort.Equals(myInt), myInt.Equals(myShort), myInt == myShort); ตอนนี้ตรวจสอบกับความเป็นจริง คำทำนายของคุณถูกต้องหรือไม่? ถ้าไม่คุณสามารถอธิบายความคลาดเคลื่อนได้หรือไม่
Eric Lippert

1
@Star สำหรับการอ่านที่แนะนำโปรดดูmsdn.microsoft.com/en-us/library/vstudio/…สำหรับโอเวอร์โหลดที่มีอยู่ในเมธอดint16aka shortEquals จากนั้นดูที่msdn.microsoft.com/en-us/library/ms173105.aspx . ฉันไม่ต้องการทำให้ปริศนาของ Eric Lippert เสียไป แต่มันก็น่าจะเข้าใจได้ง่ายเมื่อคุณอ่านหน้าเหล่านั้น
Sam Skuce

2
ฉันคิดว่านี่เป็นคำถามจาวา อย่างน้อยก่อนที่จะเห็น 'E' ในเท่ากับ
seteropere

4
@seteropere Java นั้นแตกต่างกันจริง ๆ : autoboxing ใน Java จะแคชอ็อบเจ็กต์ดังนั้นจึง((Integer)0)==((Integer)0)ประเมินเป็นจริง
Jules

1
IFormattable x = 0; bool test = (object)x == (object)x;นอกจากนี้คุณยังสามารถลอง ไม่มีการชกมวยใหม่เมื่อโครงสร้างอยู่ในช่องแล้ว
Jeppe Stig Nielsen

คำตอบ:


151

เหตุผลที่การโทรมีพฤติกรรมแตกต่างกันคือพวกเขาเชื่อมโยงกับวิธีการที่แตกต่างกันมาก

==กรณีที่จะเชื่อมโยงกับผู้ประกอบการอ้างอิงความเท่าเทียมกันคงที่ มีการintสร้างค่ากล่องอิสระ 2 ค่าดังนั้นจึงไม่ใช่การอ้างอิงเดียวกัน

Object.Equalsในกรณีที่สองคุณผูกกับวิธีการเช่น นี่เป็นวิธีการเสมือนจริงที่จะกรองลงไปInt32.Equalsและจะตรวจสอบจำนวนเต็มกล่อง ค่าจำนวนเต็มทั้งสองเป็น 0 จึงมีค่าเท่ากัน


กรณีไม่เรียก== Object.ReferenceEqualsเพียงแค่สร้างceqคำสั่ง IL เพื่อทำการเปรียบเทียบอ้างอิง
Sam Harwell

8
@ 280Z28 นั่นไม่ใช่แค่เพราะคอมไพเลอร์อินไลน์ใช่ไหม
markmnl

@ 280Z28 งั้นหรอ กรณีที่คล้ายกันคือวิธี Boolean ToString ของพวกเขาดูเหมือนว่าจะมีสตริงที่เข้ารหัสไว้ในฟังก์ชันแทนที่จะส่งคืน Boolean.TrueString และ Boolean.FalseString ที่เปิดเผยต่อสาธารณะ มันไม่เกี่ยวข้อง ประเด็นคือ==ทำสิ่งเดียวกันกับReferenceEquals(บน Object อยู่ดี) ทั้งหมดนี้เป็นเพียงการเพิ่มประสิทธิภาพภายในในด้านของ MS เพื่อหลีกเลี่ยงการเรียกใช้ฟังก์ชันภายในที่ไม่จำเป็นในฟังก์ชันที่ใช้บ่อย
Nyerguds

6
ข้อกำหนดภาษา C # ย่อหน้าที่ 7.10.6 กล่าวว่า: ตัวดำเนินการความเท่าเทียมกันของชนิดการอ้างอิงที่กำหนดไว้ล่วงหน้าคือbool operator ==(object x, object y); bool operator !=(object x, object y);ตัวดำเนินการส่งคืนผลลัพธ์ของการเปรียบเทียบการอ้างอิงทั้งสองเพื่อความเท่าเทียมกันหรือไม่เท่าเทียมกัน ไม่จำเป็นต้องใช้วิธีการในการSystem.Object.ReferenceEqualsพิจารณาผลลัพธ์ ถึง @markmnl: ไม่คอมไพเลอร์ C # ไม่อินไลน์นั่นคือสิ่งที่บางครั้งกระวนกระวายใจ (แต่ไม่ใช่ในกรณีนี้) ดังนั้น 280Z28 ถูกต้องReferenceEqualsวิธีนี้ไม่ได้ใช้จริง
Jeppe Stig Nielsen

@JaredPar: มันน่าสนใจที่สเป็คบอกอย่างนั้นเพราะนั่นไม่ใช่ลักษณะของภาษาจริงๆ ป.ร. ให้ผู้ประกอบการที่กำหนดไว้ข้างต้นและตัวแปรCat Whiskers; Dog Fido; IDog Fred;(สำหรับการเชื่อมต่อที่ไม่เกี่ยวข้องกันICatและIDogและการเรียนที่ไม่เกี่ยวข้องกันCat:ICatและDog:IDog) การเปรียบเทียบWhiskers==FidoและWhiskers==34จะถูกต้องตามกฎหมาย (ครั้งแรกเท่านั้นที่สามารถจะเป็นจริงถ้าหนวดและ Fido ทั้งสอง null ที่สองไม่เคยเป็นจริง ) ในความเป็นจริงคอมไพเลอร์ C # จะปฏิเสธทั้งสองอย่าง Whiskers==Fred;จะถูกห้ามหากCatมีการปิดผนึก แต่อนุญาตหากไม่ได้รับอนุญาต
supercat

26

เมื่อคุณโยน int ค่า0(หรือประเภทค่าอื่น ๆ ) เพื่อobjectค่าจะถูกบรรจุอยู่ในกล่อง การโยนแต่ละครั้งจะobjectสร้างกล่องที่แตกต่างกัน (เช่นอินสแตนซ์วัตถุที่แตกต่างกัน) ตัว==ดำเนินการสำหรับobjectประเภททำการเปรียบเทียบการอ้างอิงดังนั้นจึงส่งคืนค่าเท็จเนื่องจากด้านซ้ายมือและด้านขวามือไม่ใช่อินสแตนซ์เดียวกัน

ในทางกลับกันเมื่อคุณใช้Equalsซึ่งเป็นวิธีการเสมือนจริงจะใช้การใช้งานชนิดบรรจุกล่องจริงนั่นคือInt32.Equalsส่งคืนค่าจริงเนื่องจากวัตถุทั้งสองมีค่าเท่ากัน


18

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

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


==โทเค็นจริงเป็นสองผู้ประกอบการซึ่งหนึ่งในนั้นคือ overloadable และเป็นหนึ่งซึ่งไม่ได้เป็น ลักษณะการทำงานของตัวดำเนินการที่สองแตกต่างอย่างมากจากการโอเวอร์โหลดบน (วัตถุวัตถุ)
supercat

13

Equals()วิธีเป็นเสมือน
ดังนั้นจึงมักจะเรียกใช้งานคอนกรีตแม้เมื่อ callsite objectจะหล่อไป intแทนที่Equals()เพื่อเปรียบเทียบตามค่าเพื่อให้คุณได้รับการเปรียบเทียบมูลค่า


10

== ใช้: Object.ReferenceEquals

Object.Equals เปรียบเทียบมูลค่า

object.ReferenceEqualsวิธีการเปรียบเทียบอ้างอิง เมื่อคุณจัดสรรวัตถุคุณจะได้รับการอ้างอิงที่มีค่าที่ระบุตำแหน่งหน่วยความจำนอกเหนือจากข้อมูลของวัตถุบนฮีปหน่วยความจำ

object.Equalsวิธีการเปรียบเทียบเนื้อหาของวัตถุ ขั้นแรกจะตรวจสอบว่าการอ้างอิงนั้นเท่ากันหรือไม่เช่นเดียวกับ object.ReferenceEquals แต่จากนั้นก็เรียกใช้วิธี Equals ที่ได้รับมาเพื่อทดสอบความเท่าเทียม ดูนี่:

   System.Object a = new System.Object();
System.Object b = a;
System.Object.ReferenceEquals(a, b);  //returns true

แม้ว่าObject.ReferenceEqualsพฤติกรรมเช่นวิธีการที่ใช้ C # ==ผู้ประกอบการเกี่ยวกับการถูกดำเนินการของผู้ประกอบการ C # ประกอบการอ้างอิงความเท่าเทียมกัน (ซึ่งเป็นตัวแทนโดยใช้==ประเภทถูกดำเนินการที่ไม่เกินถูกกำหนด) ReferenceEqualsใช้คำสั่งพิเศษมากกว่าการโทร นอกจากนี้Object.ReferenceEqualsจะยอมรับตัวถูกดำเนินการซึ่งสามารถจับคู่ได้ก็ต่อเมื่อทั้งคู่เป็นโมฆะและจะยอมรับตัวถูกดำเนินการที่จำเป็นต้องถูกบังคับประเภทObjectและทำให้ไม่สามารถจับคู่อะไรได้ในขณะที่เวอร์ชันความเท่าเทียมกันของการอ้างอิง==จะปฏิเสธที่จะรวบรวมการใช้งานดังกล่าว .
supercat

9

ตัวดำเนินการ C # ใช้โทเค็น==เพื่อแสดงตัวดำเนินการสองตัวที่แตกต่างกัน: ตัวดำเนินการเปรียบเทียบแบบคงที่ - โอเวอร์โหลดได้และตัวดำเนินการเปรียบเทียบการอ้างอิงที่ไม่โอเวอร์โหลด เมื่อพบ==โทเค็นขั้นแรกจะตรวจสอบว่ามีโอเวอร์โหลดการทดสอบความเท่าเทียมกันหรือไม่ซึ่งใช้ได้กับประเภทตัวถูกดำเนินการ ถ้าเป็นเช่นนั้นมันจะเรียกว่าโอเวอร์โหลด มิฉะนั้นจะตรวจสอบว่าประเภทนั้นใช้ได้กับตัวดำเนินการเปรียบเทียบข้อมูลอ้างอิงหรือไม่ ถ้าเป็นเช่นนั้นก็จะใช้ตัวดำเนินการนั้น หากไม่มีตัวดำเนินการใดที่สามารถใช้ได้กับชนิดตัวถูกดำเนินการการคอมไพล์จะล้มเหลว

โค้ด(Object)0ไม่ได้เป็นเพียงการอัปเดตInt32ถึงObject: Int32เช่นเดียวกับประเภทค่าทั้งหมดจริง ๆ แล้วแสดงถึงสองประเภทซึ่งหนึ่งในนั้นอธิบายถึงค่าและตำแหน่งที่จัดเก็บ (เช่นศูนย์ตามตัวอักษร) แต่ไม่ได้มาจากสิ่งใดเลยและหนึ่งในนั้นอธิบายถึง กองวัตถุและมาจากObject; เนื่องจากเฉพาะประเภทหลังเท่านั้นที่สามารถอัปเดObjectตได้คอมไพเลอร์ต้องสร้างวัตถุฮีปใหม่ของประเภทหลังนั้น การเรียกใช้แต่ละครั้งของ(Object)0สร้างฮีปอ็อบเจ็กต์ใหม่ดังนั้นตัวถูกดำเนินการทั้งสอง==จึงเป็นอ็อบเจ็กต์ที่แตกต่างกันซึ่งแต่ละอ็อพชันจะห่อหุ้มInt32ค่า 0 โดยอิสระ

คลาสObjectไม่มีโอเวอร์โหลดที่ใช้งานได้ซึ่งกำหนดไว้สำหรับตัวดำเนินการเท่ากับ ดังนั้นคอมไพลเลอร์จะไม่สามารถใช้ตัวดำเนินการทดสอบความเท่าเทียมกันที่โอเวอร์โหลดและจะถอยกลับไปใช้การทดสอบความเท่าเทียมกันของการอ้างอิง เพราะทั้งสองตัวถูกดำเนินการที่จะอ้างถึงวัตถุที่แตกต่างกันก็จะรายงาน== falseการเปรียบเทียบครั้งที่สองทำได้สำเร็จเนื่องจากถามอินสแตนซ์ฮีปอ็อบเจ็กต์หนึ่งInt32ว่าเท่ากับอีกหรือไม่ เนื่องจากอินสแตนซ์นั้นรู้ว่าการเท่ากับอินสแตนซ์อื่นที่แตกต่างกันหมายความว่าอย่างไรจึงสามารถตอบtrueได้


นอกจากนี้ทุกครั้งที่คุณเขียนลิเทอรัล0ในโค้ดของคุณฉันถือว่ามันสร้างวัตถุ int ในฮีปสำหรับสิ่งนั้น ไม่ใช่การอ้างอิงเฉพาะสำหรับค่าศูนย์คงที่ส่วนกลางหนึ่งค่า (เช่นวิธีที่พวกเขาสร้าง String ว่างเปล่าเพื่อหลีกเลี่ยงการสร้างอ็อบเจ็กต์สตริงว่างใหม่เพียงเพื่อเริ่มต้นสตริงใหม่) ดังนั้นฉันค่อนข้างแน่ใจว่าแม้การทำ0.ReferenceEquals(0)จะส่งคืนค่าเท็จเนื่องจาก 0 ทั้งสองเป็นInt32วัตถุที่สร้างขึ้นใหม่
Nyerguds

1
@Nyerguds ฉันค่อนข้างแน่ใจว่าทุกสิ่งที่คุณพูดนั้นไม่ถูกต้องเกี่ยวกับ ints, heap, history, global statics และอื่น ๆ 0.ReferenceEquals(0)จะล้มเหลวเนื่องจากคุณพยายามเรียกเมธอดเกี่ยวกับค่าคงที่ของเวลาในการคอมไพล์ ไม่มีสิ่งใดที่จะวางสายได้ int ที่ไม่มีกล่องคือโครงสร้างที่เก็บไว้ในสแต็ก แม้int i = 0; i.ReferenceEquals(...)จะไม่ทำงาน เพราะไม่ได้รับมรดกจากSystem.Int32 Object
Andrew Backer

@AndrewBacker, System.Int32เป็นstructที่structเป็นที่ตัวเองสืบทอดSystem.ValueType System.ObjectทำไมถึงมีToString()วิธีการและEqualsวิธีการSystem.Int32
Sebastian

1
อย่างไรก็ตาม Nyerguds ผิดที่จะระบุว่า Int32 จะถูกสร้างขึ้นบนฮีปซึ่งไม่ใช่กรณีนี้
Sebastian

@SebastianGodelet ฉันไม่สนใจเรื่องภายในเลย System.Int32 เองก็ใช้วิธีการเหล่านั้น GetType () เปิดอยู่Objectและนั่นคือสิ่งที่ฉันเลิกกังวลเกี่ยวกับเรื่องนี้มานานแล้ว ไม่จำเป็นต้องไปให้ไกลกว่านี้ AFAIK CLR จัดการทั้งสองประเภทแตกต่างกันและพิเศษ ไม่ใช่แค่การถ่ายทอดทางพันธุกรรม มันisเป็นหนึ่งในสองประเภทของข้อมูลแม้ว่า ฉันไม่ต้องการให้ใครอ่านความคิดเห็นนั้นและไม่สามารถติดตามได้รวมถึงความแปลกประหลาดเกี่ยวกับสตริงว่างซึ่งไม่สนใจการฝึกงานของสตริง
Andrew Backer

3

การตรวจสอบทั้งสองแบบแตกต่างกัน คนแรกที่ตรวจสอบตัวตนที่หนึ่งที่สองสำหรับความเท่าเทียมกัน โดยทั่วไปคำศัพท์สองคำจะเหมือนกันหากอ้างถึงวัตถุเดียวกัน นี่หมายความว่าพวกเขาเท่าเทียมกัน คำสองคำมีค่าเท่ากันถ้าค่าเท่ากัน

ในแง่ของเอกลักษณ์การเขียนโปรแกรมมักจะยุ่งเหยิงด้วยความเท่าเทียมกันในการอ้างอิง หากตัวชี้ของทั้งสองคำมีค่าเท่ากัน (!) วัตถุที่พวกเขาชี้ไปจะเหมือนกันทุกประการ อย่างไรก็ตามหากพอยน์เตอร์แตกต่างกันค่าของอ็อบเจ็กต์ที่ชี้ไปจะยังคงเท่ากัน ใน C # ตัวตนสามารถตรวจสอบได้โดยใช้สถิตย์Object.ReferenceEqualsสมาชิกในขณะที่มีการตรวจสอบความเสมอภาคใช้ไม่คงที่Object.Equalsสมาชิก เนื่องจากคุณหล่อจำนวนเต็มสองจำนวนวัตถุ (ซึ่งเรียกว่า "มวย" BTW) operatior ==ของobjectดำเนินการตรวจสอบครั้งแรกซึ่งเป็นค่าเริ่มต้นแมปไปObject.ReferenceEqualsและการตรวจสอบตัวตน หากความชัดเจนของคุณเรียกEqualsสมาชิกแบบไม่คงที่การจัดส่งแบบไดนามิกจะส่งผลให้เกิดการเรียกร้องInt32.Equalsซึ่งจะตรวจสอบความเท่าเทียมกัน

แนวคิดทั้งสองมีความคล้ายคลึงกัน แต่ไม่เหมือนกัน พวกเขาอาจดูสับสนในตอนแรก แต่ความแตกต่างเล็กน้อยนั้นสำคัญมาก! ลองนึกภาพบุคคล 2 คน ได้แก่ "อลิซ" และ "บ็อบ" ทั้งคู่อาศัยอยู่ในบ้านสีเหลือง จากการสันนิษฐานว่าอลิซและบ็อบอาศัยอยู่ในเขตหนึ่งซึ่งบ้านมีสีแตกต่างกันเท่านั้นพวกเขาทั้งคู่อาศัยอยู่ในบ้านสีเหลืองที่แตกต่างกัน หากเปรียบบ้านทั้งสองหลังคุณจะจำได้ว่าบ้านทั้งสองหลังนั้นเหมือนกันอย่างแน่นอนเพราะทั้งสองหลังมีสีเหลือง! แต่พวกเขาจะไม่ร่วมกันที่บ้านเดียวกันและทำให้บ้านของพวกเขาจะเท่ากันแต่ไม่เหมือนกัน อัตลักษณ์บ่งบอกว่าพวกเขาอาศัยอยู่ในบ้านหลังเดียวกัน

หมายเหตุ : บางภาษากำลังกำหนดตัว===ดำเนินการเพื่อตรวจสอบตัวตน

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