คำจำกัดความของโอเปอเรเตอร์“ ==” สำหรับ Double


126

ด้วยเหตุผลบางอย่างฉันแอบเข้าไปในซอร์ส. NET Framework สำหรับคลาสDoubleและพบว่าการประกาศ==คือ:

public static bool operator ==(Double left, Double right) {
    return left == right;
}

ตรรกะเดียวกันนี้ใช้ได้กับทุกตัวดำเนินการ


  • คำจำกัดความดังกล่าวคืออะไร?
  • มันทำงานอย่างไร?
  • เหตุใดจึงไม่สร้างการเรียกซ้ำแบบไม่สิ้นสุด

17
ฉันคาดหวังว่าจะมีการเรียกซ้ำไม่รู้จบ
HimBromBeere

5
ฉันค่อนข้างแน่ใจว่ามันไม่ได้ใช้เปรียบเทียบกับ double แต่ceqจะออกเป็น IL แทน นี่เป็นเพียงเพื่อเติมเต็มวัตถุประสงค์ของเอกสารบางอย่างไม่สามารถหาแหล่งที่มาได้
Habib

2
เป็นไปได้มากที่จะได้รับตัวดำเนินการนี้ผ่านการสะท้อนกลับ
Damien_The_Unbeliever

3
ที่จะไม่ถูกเรียกคอมไพเลอร์มีตรรกะความเท่าเทียมกันอบใน (ceq opcode) โปรดดูว่าเมื่อใดที่เรียกใช้ตัวดำเนินการ == ของ Double
Alex K.

1
@ZoharPeled หารคู่กับศูนย์นั้นใช้ได้และจะส่งผลให้เป็นค่าอนันต์บวกหรือลบ
Magnus

คำตอบ:


62

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

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

ในความเป็นจริงหากคุณเรียกตัวดำเนินการผ่านการสะท้อนคุณจะเห็นว่าตัวดำเนินการถูกเรียก (แทนที่จะเป็นCEQคำสั่ง) และเห็นได้ชัดว่าไม่มีการวนซ้ำอย่างไม่มีที่สิ้นสุด (เนื่องจากโปรแกรมสิ้นสุดตามที่คาดไว้):

double d1 = 1.1;
double d2 = 2.2;

MethodInfo mi = typeof(Double).GetMethod("op_Equality", BindingFlags.Static | BindingFlags.Public );

bool b = (bool)(mi.Invoke(null, new object[] {d1,d2}));

ผลลัพธ์ IL (รวบรวมโดย LinqPad 4):

IL_0000:  nop         
IL_0001:  ldc.r8      9A 99 99 99 99 99 F1 3F 
IL_000A:  stloc.0     // d1
IL_000B:  ldc.r8      9A 99 99 99 99 99 01 40 
IL_0014:  stloc.1     // d2
IL_0015:  ldtoken     System.Double
IL_001A:  call        System.Type.GetTypeFromHandle
IL_001F:  ldstr       "op_Equality"
IL_0024:  ldc.i4.s    18 
IL_0026:  call        System.Type.GetMethod
IL_002B:  stloc.2     // mi
IL_002C:  ldloc.2     // mi
IL_002D:  ldnull      
IL_002E:  ldc.i4.2    
IL_002F:  newarr      System.Object
IL_0034:  stloc.s     04 // CS$0$0000
IL_0036:  ldloc.s     04 // CS$0$0000
IL_0038:  ldc.i4.0    
IL_0039:  ldloc.0     // d1
IL_003A:  box         System.Double
IL_003F:  stelem.ref  
IL_0040:  ldloc.s     04 // CS$0$0000
IL_0042:  ldc.i4.1    
IL_0043:  ldloc.1     // d2
IL_0044:  box         System.Double
IL_0049:  stelem.ref  
IL_004A:  ldloc.s     04 // CS$0$0000
IL_004C:  callvirt    System.Reflection.MethodBase.Invoke
IL_0051:  unbox.any   System.Boolean
IL_0056:  stloc.3     // b
IL_0057:  ret 

ที่น่าสนใจ - ผู้ประกอบการเดียวกันไม่อยู่ (ทั้งในแหล่งอ้างอิงหรือผ่านการสะท้อน) ประเภทหนึ่งเท่านั้นSingle, Double, Decimal, StringและDateTimeซึ่งหักล้างทฤษฎีของฉันที่พวกเขามีอยู่จะเรียกว่ามาจากภาษาอื่น ๆ เห็นได้ชัดว่าคุณสามารถหาจำนวนเต็มสองจำนวนในภาษาอื่น ๆ ได้โดยไม่ต้องใช้ตัวดำเนินการเหล่านี้ดังนั้นเราจึงกลับไปที่คำถาม "ทำไมจึงมีอยู่double"


12
ปัญหาเดียวที่ฉันเห็นคือข้อกำหนดภาษา C # บอกว่าตัวดำเนินการที่โอเวอร์โหลดมีความสำคัญเหนือตัวดำเนินการในตัว ดังนั้นคอมไพเลอร์ C # ที่สอดคล้องกันควรเห็นว่ามีตัวดำเนินการที่โอเวอร์โหลดอยู่ที่นี่และสร้างการเรียกซ้ำแบบไม่สิ้นสุด อืมมม หนักใจ
Damien_The_Unbeliever

5
นั่นไม่ตอบคำถาม imho เพียงอธิบายว่าโค้ดถูกแปลเป็นอะไร แต่ไม่ใช่เพราะเหตุใด ตามส่วน7.3.4 ตัวดำเนินการไบนารีความละเอียดเกินพิกัดของข้อกำหนดภาษา C # ฉันคาดว่าจะมีการเรียกซ้ำแบบไม่สิ้นสุด ฉันคิดว่าแหล่งอ้างอิง ( referencesource.microsoft.com/#mscorlib/system/… ) ไม่ได้ใช้ที่นี่จริงๆ
Dirk Vollmar

6
@DStanley - ฉันไม่ได้ปฏิเสธสิ่งที่ผลิต ฉันกำลังบอกว่าฉันไม่สามารถปรับให้เข้ากับข้อกำหนดภาษาได้ นั่นคือสิ่งที่น่าหนักใจ ฉันกำลังคิดเกี่ยวกับการเจาะผ่าน Roslyn และดูว่าฉันสามารถหาการจัดการพิเศษที่นี่ได้หรือไม่ แต่ฉันยังไม่พร้อมที่จะทำสิ่งนี้ในปัจจุบัน (ผิดเครื่อง)
Damien_The_Unbeliever

1
@Damien_The_Unbeliever นั่นคือเหตุผลที่ฉันคิดว่ามันเป็นข้อยกเว้นสำหรับข้อมูลจำเพาะหรือการตีความตัวดำเนินการ "ในตัว" ที่แตกต่างกัน
D Stanley

1
เนื่องจาก @Jon Skeet ยังไม่ได้ตอบหรือแสดงความคิดเห็นฉันจึงสงสัยว่ามันเป็นบั๊ก (เช่นการละเมิดข้อกำหนด)
TheBlastOne

37

ความสับสนหลักที่นี่คือคุณสมมติว่าไลบรารี. NET ทั้งหมด (ในกรณีนี้คือ Extended Numerics Library ซึ่งไม่ได้เป็นส่วนหนึ่งของ BCL) เขียนด้วยมาตรฐาน C # ไม่ได้เป็นเช่นนั้นเสมอไปและภาษาต่างๆก็มีกฎที่แตกต่างกัน

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

ldarg.0
ldarg.1
ceq
ret

นั่นแหล่ะ :) ไม่มีรหัส C # ที่เทียบเท่า 100% - สิ่งนี้เป็นไปไม่ได้ใน C # ด้วยประเภทของคุณเอง

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

นี่ไม่ใช่สถานการณ์เฉพาะใน. NET อย่างแน่นอน - มีโค้ดมากมายที่ไม่ถูกต้องมาตรฐาน C # เหตุผลมักจะเป็น (a) คอมไพเลอร์แฮ็กและ (b) ภาษาอื่นโดยมีแฮ็กรันไทม์แปลก (c) (ฉันกำลังมองหาคุณNullable!)

เนื่องจากคอมไพเลอร์ Roslyn C # เป็นแหล่งที่มาของ oepn ฉันจึงสามารถชี้ให้คุณเห็นสถานที่ที่มีการตัดสินใจเลือกความละเอียดเกิน

สถานที่ที่ตัวดำเนินการไบนารีทั้งหมดได้รับการแก้ไข

"ทางลัด" สำหรับตัวดำเนินการภายใน

เมื่อคุณดูทางลัดคุณจะเห็นว่าความเท่าเทียมกันระหว่างผลลัพธ์คู่และผลคู่ในตัวดำเนินการคู่ภายในไม่เคยอยู่ในตัว==ดำเนินการจริงที่กำหนดไว้ในประเภท ระบบชนิด. NET ต้องแสร้งทำเป็นว่าDoubleเป็นประเภทอื่น ๆ แต่ C # ไม่ - doubleเป็นแบบดั้งเดิมใน C #


1
ไม่แน่ใจว่าฉันยอมรับว่ารหัสในแหล่งอ้างอิงเป็นเพียง "วิศวกรรมย้อนกลับ" โค้ดนี้มีคำสั่งคอมไพเลอร์#ifและสิ่งประดิษฐ์อื่น ๆ ที่จะไม่มีอยู่ในโค้ดที่คอมไพล์ ยิ่งไปกว่านั้นถ้ามันถูกออกแบบทางวิศวกรรมย้อนกลับdoubleแล้วทำไมมันถึงไม่ได้รับการออกแบบทางวิศวกรรมย้อนกลับสำหรับintหรือlong? ฉันคิดว่ามีเหตุผลสำหรับซอร์สโค้ด แต่เชื่อว่าการใช้งาน==ภายในตัวดำเนินการจะถูกรวบรวมเพื่อCEQป้องกันการเรียกซ้ำ เนื่องจากโอเปอเรเตอร์เป็นโอเปอเรเตอร์ที่ "กำหนดไว้ล่วงหน้า" สำหรับประเภทนั้น (และไม่สามารถลบล้างได้) จึงไม่ใช้กฎโอเวอร์โหลด
D Stanley

@DStanley ฉันไม่ต้องการบอกเป็นนัยว่ารหัสทั้งหมดถูกออกแบบทางวิศวกรรมย้อนกลับ และอีกครั้งdoubleไม่ได้เป็นส่วนหนึ่งของ BCL - อยู่ในไลบรารีแยกต่างหากซึ่งจะรวมอยู่ในข้อกำหนด C # ใช่==ได้รับการคอมไพล์เป็น a ceqแต่นั่นก็ยังหมายความว่านี่เป็นแฮ็กคอมไพเลอร์ที่คุณไม่สามารถจำลองรหัสของคุณเองได้และสิ่งที่ไม่ได้เป็นส่วนหนึ่งของข้อกำหนด C # (เช่นเดียวกับfloat64ฟิลด์ในโครงสร้างDouble) ไม่ใช่ส่วนที่เป็นสัญญาของ C # ดังนั้นจึงมีจุดเล็ก ๆ น้อย ๆ ในการปฏิบัติเช่น C # ที่ถูกต้องแม้ว่าจะคอมไพล์ด้วยคอมไพเลอร์ C # ก็ตาม
Luaan

@DStanely ฉันไม่พบวิธีการจัดระเบียบเฟรมเวิร์กจริง แต่ในการใช้งานอ้างอิงของ. NET 2.0 ส่วนที่ยุ่งยากทั้งหมดเป็นเพียงอินทรินไซด์ของคอมไพเลอร์ที่ใช้งานใน C ++ แน่นอนว่ายังมีโค้ดเนทีฟของ. NET อยู่มากมาย แต่สิ่งต่างๆเช่น "การเปรียบเทียบสองคู่" จะไม่ได้ผลดีกับ. NET นั่นเป็นสาเหตุหนึ่งที่ทำให้ตัวเลขทศนิยมไม่รวมอยู่ใน BCL อย่างไรก็ตามโค้ดดังกล่าวยังถูกนำไปใช้ใน C # (ที่ไม่ได้มาตรฐาน) ซึ่งอาจจะตรงกับเหตุผลที่คุณกล่าวไว้ก่อนหน้านี้ - เพื่อให้แน่ใจว่าคอมไพเลอร์. NET อื่น ๆ สามารถถือว่าประเภทเหล่านั้นเป็นประเภท. NET จริง
Luaan

@DStanley แต่เอาล่ะชี้ไปแล้ว ฉันลบการอ้างอิง "วิศวกรรมย้อนกลับ" และเปลี่ยนคำตอบเพื่อพูดถึง "มาตรฐาน C #" อย่างชัดเจนแทนที่จะเป็นเพียง C # และอย่าปฏิบัติdoubleเช่นเดียวกับintและlong- intและlongเป็นประเภทดั้งเดิมที่ภาษา. NET ทั้งหมดต้องรองรับ float, decimalและdoubleไม่ได้
Luaan

12

แหล่งที่มาของประเภทดั้งเดิมอาจทำให้สับสนได้ คุณเคยเห็นบรรทัดแรกของโครงสร้างDoubleหรือไม่?

โดยปกติคุณไม่สามารถกำหนดโครงสร้างแบบเรียกซ้ำได้เช่นนี้:

public struct Double : IComparable, IFormattable, IConvertible
        , IComparable<Double>, IEquatable<Double>
{
    internal double m_value; // Self-recursion with endless loop?
    // ...
}

ประเภทดั้งเดิมมีการสนับสนุนดั้งเดิมใน CIL เช่นกัน โดยปกติจะไม่ได้รับการปฏิบัติเหมือนประเภทเชิงวัตถุ คู่เป็นเพียงค่า 64 บิตหากใช้เป็นfloat64CIL อย่างไรก็ตามหากมีการจัดการเป็นชนิด. NET ตามปกติจะมีค่าจริงและมีวิธีการเหมือนกับประเภทอื่น ๆ

สิ่งที่คุณเห็นต่อไปนี้คือสถานการณ์เดียวกันสำหรับตัวดำเนินการ โดยปกติถ้าคุณใช้ประเภท double โดยตรงจะไม่ถูกเรียก BTW แหล่งที่มามีลักษณะเช่นนี้ใน CIL:

.method public hidebysig specialname static bool op_Equality(float64 left, float64 right) cil managed
{
    .custom instance void System.Runtime.Versioning.NonVersionableAttribute::.ctor()
    .custom instance void __DynamicallyInvokableAttribute::.ctor()
    .maxstack 8
    L_0000: ldarg.0
    L_0001: ldarg.1
    L_0002: ceq
    L_0004: ret
}

อย่างที่คุณเห็นไม่มีการวนซ้ำแบบไม่มีที่สิ้นสุด ( ceqเครื่องมือนี้ใช้แทนการเรียกSystem.Double::op_Equality) ดังนั้นเมื่อคู่ได้รับการปฏิบัติเหมือนออบเจ็กต์เมธอดตัวดำเนินการจะถูกเรียกซึ่งในที่สุดก็จะจัดการมันเป็นfloat64ประเภทดั้งเดิมในระดับ CIL


1
สำหรับผู้ที่ไม่เข้าใจในส่วนแรกของโพสต์นี้ (อาจจะเพราะพวกเขามักจะไม่ได้เขียนประเภทค่าของตัวเอง) public struct MyNumber { internal MyNumber m_value; }ลองรหัส ไม่สามารถรวบรวมได้แน่นอน ข้อผิดพลาดคือข้อผิดพลาด CS0523: สมาชิกโครงสร้าง 'MyNumber.m_value' ประเภท 'MyNumber' ทำให้เกิดวงจรในโครงร่างโครงสร้าง
Jeppe Stig Nielsen

8

ฉันดูCILด้วย JustDecompile ภายใน==ได้รับการแปลเป็นรหัสCIL ceq op กล่าวอีกนัยหนึ่งคือความเท่าเทียมกันของ CLR ดั้งเดิม

ฉันอยากรู้ว่าคอมไพเลอร์ C # จะอ้างอิงceqหรือตัว==ดำเนินการเมื่อเปรียบเทียบค่าสองค่าสองค่า ในตัวอย่างเล็ก ๆ น้อย ๆ ที่ฉันมาด้วย (ด้านล่าง) ceqก็ใช้

โปรแกรมนี้:

void Main()
{
    double x = 1;
    double y = 2;

    if (x == y)
        Console.WriteLine("Something bad happened!");
    else
        Console.WriteLine("All is right with the world");
}

สร้าง CIL ต่อไปนี้ (สังเกตคำสั่งที่มีป้ายกำกับIL_0017):

IL_0000:  nop
IL_0001:  ldc.r8      00 00 00 00 00 00 F0 3F
IL_000A:  stloc.0     // x
IL_000B:  ldc.r8      00 00 00 00 00 00 00 40
IL_0014:  stloc.1     // y
IL_0015:  ldloc.0     // x
IL_0016:  ldloc.1     // y
IL_0017:  ceq
IL_0019:  stloc.2
IL_001A:  ldloc.2
IL_001B:  brfalse.s   IL_002A
IL_001D:  ldstr       "Something bad happened!"
IL_0022:  call        System.Console.WriteLine
IL_0027:  nop
IL_0028:  br.s        IL_0035
IL_002A:  ldstr       "All is right with the world"
IL_002F:  call        System.Console.WriteLine
IL_0034:  nop
IL_0035:  ret

-2

ตามที่ระบุในเอกสารของ Microsoft สำหรับ System.Runtime.Versioning Namespace: ชนิดที่พบในเนมสเปซนี้มีไว้สำหรับใช้ภายใน. NET Framework ไม่ใช่สำหรับแอปพลิเคชันผู้ใช้ System.Runtime.Versioning namespace มีชนิดขั้นสูงที่รองรับการกำหนดเวอร์ชันใน การใช้งานร่วมกันของ. NET Framework


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