Definisi operator "==" untuk Double

126

Untuk beberapa alasan saya menyelinap ke sumber .NET Framework untuk kelas Doubledan menemukan bahwa deklarasi ==adalah:

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

Logika yang sama berlaku untuk setiap operator.


  • Apa gunanya definisi seperti itu?
  • Bagaimana cara kerjanya?
  • Mengapa itu tidak menciptakan rekursi yang tak terbatas?
Thomas Ayoub
sumber
17
Saya berharap rekursi yang tak berkesudahan.
HimBromBeere
5
Saya cukup yakin itu tidak digunakan untuk perbandingan di mana saja dengan ganda, sebagai gantinya ceqdikeluarkan di IL. Ini hanya di sana untuk mengisi beberapa tujuan dokumentasi, tetapi tidak dapat menemukan sumbernya.
Habib
2
Kemungkinan besar agar operator ini dapat diperoleh melalui Reflection.
Damien_The_Unbeliever
3
Itu tidak akan pernah dipanggil, kompiler memiliki logika kesetaraan dipanggang (ceq opcode) lihat Kapan operator Double == dipanggil?
Alex K.
1
@ZoharPeled membagi dobel dengan nol adalah valid dan akan menghasilkan infinity positif atau negatif.
Magnus

Jawaban:

62

Pada kenyataannya, kompiler akan mengubah ==operator menjadi ceqkode IL, dan operator yang Anda sebutkan tidak akan dipanggil.

Alasan untuk operator dalam kode sumber kemungkinan sehingga dapat dipanggil dari bahasa selain C # yang tidak menerjemahkannya menjadi CEQpanggilan langsung (atau melalui refleksi). Kode dalam operator akan dikompilasi menjadi a CEQ, sehingga tidak ada rekursi yang tak terbatas.

Bahkan, jika Anda memanggil operator melalui refleksi, Anda dapat melihat bahwa operator dipanggil (bukan CEQinstruksi), dan jelas tidak rekursif tanpa batas (karena program berakhir seperti yang diharapkan):

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 yang dihasilkan (dikompilasi oleh 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 

Menariknya - operator yang sama TIDAK ada (baik dalam sumber referensi atau melalui refleksi) untuk jenis yang tidak terpisahkan, hanya Single, Double, Decimal, String, dan DateTime, yang menyangkal teori saya bahwa mereka ada untuk dipanggil dari bahasa lain. Jelas Anda dapat menyamakan dua bilangan bulat dalam bahasa lain tanpa operator ini, jadi kami kembali ke pertanyaan "mengapa mereka ada untuk double"?

D Stanley
sumber
12
Satu-satunya masalah yang dapat saya lihat dengan ini adalah bahwa spesifikasi bahasa C # mengatakan bahwa operator kelebihan beban lebih diutamakan daripada operator built-in. Jadi tentunya, kompiler C # yang sesuai harus melihat bahwa operator kelebihan beban tersedia di sini dan menghasilkan rekursi tak terbatas. Hmm. Bermasalah.
Damien_The_Unbeliever
5
Itu tidak menjawab pertanyaan, imho. Ini hanya menjelaskan kode apa yang diterjemahkan tetapi tidak mengapa. Menurut bagian 7.3.4 Resolusi kelebihan operator biner dari spesifikasi bahasa C # saya juga akan mengharapkan rekursi tak terbatas. Saya berasumsi bahwa sumber referensi ( referenceource.microsoft.com/#mscorlib/system/… ) tidak benar-benar berlaku di sini.
Dirk Vollmar
6
@DStanley - Saya tidak menyangkal apa yang diproduksi. Saya mengatakan saya tidak bisa mendamaikannya dengan spesifikasi bahasa. Itu yang menyusahkan. Saya sedang berpikir untuk meneliti Roslyn dan melihat apakah saya dapat menemukan penanganan khusus di sini, tetapi saya tidak siap untuk melakukan ini saat ini (mesin yang salah)
Damien_The_Unbeliever
1
@Damien_The_Unbeliever Itu sebabnya saya pikir itu adalah pengecualian untuk spesifikasi atau interpretasi yang berbeda dari operator "built-in".
D Stanley
1
Karena @Jon Skeet belum menjawab, atau mengomentari, ini, saya menduga itu adalah bug (yaitu pelanggaran spec).
TheBlastOne
37

Kebingungan utama di sini adalah Anda berasumsi bahwa semua .NET libraries (dalam hal ini, Extended Numerics Library, yang bukan merupakan bagian dari BCL) ditulis dalam standar C #. Ini tidak selalu terjadi, dan berbagai bahasa memiliki aturan yang berbeda.

Dalam standar C #, potongan kode yang Anda lihat akan menghasilkan stack overflow, karena cara kerja resolusi kelebihan operator. Namun, kode tersebut sebenarnya tidak dalam standar C # - pada dasarnya menggunakan fitur tidak terdaftar dari kompiler C #. Alih-alih memanggil operator, ia memancarkan kode ini:

ldarg.0
ldarg.1
ceq
ret

Itu saja :) Tidak ada 100% kode C # yang setara - ini tidak mungkin dilakukan dalam C # dengan tipe Anda sendiri .

Bahkan kemudian, operator yang sebenarnya tidak digunakan ketika mengkompilasi kode C # - kompiler melakukan banyak optimasi, seperti dalam kasus ini, di mana ia menggantikan op_Equalitypanggilan hanya dengan sederhana ceq. Sekali lagi, Anda tidak dapat meniru ini di DoubleExstruct Anda sendiri - itu adalah kompiler ajaib.

Ini tentu saja bukan situasi yang unik di .NET - ada banyak kode yang tidak valid, standar C #. Alasannya biasanya (a) hack kompiler dan (b) bahasa yang berbeda, dengan hack runtime aneh (c) (Saya sedang melihat Anda Nullable,!).

Karena kompiler Roslyn C # adalah sumber oepn, saya benar-benar dapat mengarahkan Anda ke tempat resolusi overload ditentukan:

Tempat di mana semua operator biner diselesaikan

"Pintasan" untuk operator intrinsik

Ketika Anda melihat pintasan, Anda akan melihat bahwa kesetaraan antara hasil ganda dan ganda di operator ganda intrinsik, tidak pernah di ==operator yang sebenarnya ditentukan pada jenis. Sistem tipe .NET harus berpura-pura bahwa itu Doubleadalah tipe seperti yang lain, tetapi C # tidak - doubleadalah primitif dalam C #.

Luaan
sumber
1
Tidak yakin saya setuju bahwa kode dalam sumber referensi hanya "direkayasa ulang". Kode memiliki arahan kompilasi #ifdan artefak lain yang tidak akan ada dalam kode terkompilasi. Plus jika itu direkayasa balik untuk doublelalu mengapa tidak direkayasa balik untuk intatau long? Saya pikir ada alasan untuk kode sumber tetapi percaya bahwa penggunaan ==di dalam operator dikompilasi ke CEQyang mencegah rekursi. Karena operator adalah operator "yang telah ditentukan" untuk tipe itu (dan tidak dapat ditimpa) aturan overload tidak berlaku.
D Stanley
@ Stanley Saya tidak ingin menyiratkan bahwa semua kode direkayasa balik. Dan sekali lagi, doublebukan bagian dari BCL - itu ada di perpustakaan yang terpisah, yang kebetulan dimasukkan dalam spesifikasi C #. Ya, ==akan dikompilasi ke a ceq, tapi itu masih berarti ini adalah hack kompiler yang tidak dapat Anda tiru dalam kode Anda sendiri, dan sesuatu yang bukan bagian dari spesifikasi C # (seperti float64bidang pada Doublestruct). Ini bukan bagian kontrak dari C #, jadi tidak ada gunanya memperlakukannya seperti C # yang valid, bahkan jika itu dikompilasi dengan kompiler C #.
Luaan
@ Sebenarnya saya tidak bisa menemukan bagaimana kerangka nyata diatur, tetapi dalam implementasi referensi. NET 2.0, semua bagian yang rumit hanyalah kompiler intrinsik, diimplementasikan dalam C ++. Masih ada banyak .NET kode asli, tentu saja, tetapi hal-hal seperti "membandingkan dua ganda" tidak akan bekerja dengan baik di .NET murni; itulah salah satu alasan angka floating point tidak termasuk dalam BCL. Yang mengatakan, kode juga diimplementasikan dalam (non-standar) C #, mungkin persis karena alasan yang Anda sebutkan sebelumnya - untuk memastikan kompiler .NET lainnya dapat memperlakukan jenis-jenis tersebut sebagai tipe .NET yang nyata.
Luaan
@ Stanley Tapi oke, poin diambil. Saya menghapus referensi "reverse engineered", dan menulis ulang jawaban untuk secara eksplisit menyebutkan "standar C #", bukan hanya C #. Dan jangan memperlakukan doubledengan cara yang sama seperti intdan long- intdan longmerupakan tipe primitif yang harus didukung oleh semua bahasa .NET. float, decimaldan doubletidak.
Luaan
12

Sumber tipe primitif bisa membingungkan. Pernahkah Anda melihat baris pertama dari Doublestruct?

Biasanya Anda tidak dapat mendefinisikan struct rekursif seperti ini:

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

Tipe primitif memiliki dukungan asli mereka di CIL juga. Biasanya mereka tidak diperlakukan seperti tipe berorientasi objek. Double adalah nilai 64-bit jika digunakan seperti float64pada CIL. Namun, jika ditangani sebagai tipe .NET biasa, ini berisi nilai aktual dan berisi metode seperti jenis lainnya.

Jadi yang Anda lihat di sini adalah situasi yang sama untuk operator. Biasanya jika Anda menggunakan tipe tipe ganda secara langsung, itu tidak akan pernah dipanggil. BTW, sumbernya terlihat seperti ini di 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
}

Seperti yang Anda lihat, tidak ada loop tanpa akhir ( ceqinstrumen digunakan alih-alih memanggil System.Double::op_Equality). Jadi ketika ganda diperlakukan seperti objek, metode operator akan dipanggil, yang pada akhirnya akan menanganinya sebagai float64tipe primitif pada level CIL.

György Kőszeg
sumber
1
Bagi mereka yang tidak mengerti bagian pertama dari posting ini (mungkin karena mereka biasanya tidak menulis tipe nilai mereka sendiri), coba kodenya public struct MyNumber { internal MyNumber m_value; }. Itu tidak bisa dikompilasi, tentu saja. Kesalahannya adalah kesalahan CS0523: Anggota Struct 'MyNumber.m_value' dari tipe 'MyNumber' menyebabkan siklus dalam tata letak struct
Jeppe Stig Nielsen
8

Saya melihat CIL dengan JustDecompile. Bagian dalam ==akan diterjemahkan ke kode ce ce CIL . Dengan kata lain, ini adalah persamaan CLR primitif.

Saya ingin tahu apakah kompiler C # akan referensi ceqatau ==operator ketika membandingkan dua nilai ganda. Dalam contoh sepele saya datang dengan (di bawah), itu digunakan ceq.

Program ini:

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");
}

menghasilkan CIL berikut (perhatikan pernyataan dengan label 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
Daniel Pratt
sumber
-2

Seperti yang ditunjukkan dalam dokumentasi Microsoft untuk System.Runtime.Versioning Namespace: Jenis yang ditemukan di namespace ini dimaksudkan untuk digunakan dalam .NET Framework dan bukan untuk aplikasi pengguna. System.Runtime.Nersioning namespace berisi tipe canggih yang mendukung versi di implementasi berdampingan dari .NET Framework.

Thomas Papamihos
sumber
Apa yang memiliki System.Runtime.Versioninghubungannya dengan System.Double?
Koopakiller