Double için "==" operatörünün tanımı


126

Bazı nedenlerden dolayı sınıf için .NET Framework kaynağına gizlice giriyordum Doubleve beyanının şöyle olduğunu öğrendim ==:

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

Aynı mantık her operatör için geçerlidir .


  • Böyle bir tanımın amacı nedir?
  • O nasıl çalışır?
  • Neden sonsuz bir özyineleme yaratmıyor?

17
Sonsuz bir özyineleme bekliyorum.
HimBromBeere

5
Double ile herhangi bir yerde karşılaştırma yapmak için kullanılmadığından eminim, bunun yerine ceqIL'de verilir. Bu sadece bazı dokümantasyon amacını doldurmak için var, Yine de kaynağı bulamıyorum.
Habib

2
Büyük olasılıkla bu operatör Yansıma yoluyla elde edilebilir.
Damien_The_Unbeliever

3
Bu asla çağrılmayacak, derleyici eşitlik mantığına sahiptir (ceq opcode) bkz . Double's == operatörü ne zaman çağrılır?
Alex K.

1
@ZoharPeled bir ikiliyi sıfıra bölmek geçerlidir ve pozitif veya negatif sonsuzluk ile sonuçlanır.
Magnus

Yanıtlar:


62

Gerçekte, derleyici ==operatörü bir ceqIL koduna çevirecek ve bahsettiğiniz operatör çağrılmayacaktır.

Operatörün kaynak kodundaki nedeni büyük olasılıkla, C # dışındaki dillerden, onu CEQdoğrudan bir çağrıya (veya yansıtma yoluyla) çevirmeyen dillerden çağrılabilir . Kod içinde operatör olacak bir şekilde toplanabilir, CEQböylece sonsuz Yinelemesiz yoktur.

Aslında, operatörü yansıtma yoluyla çağırırsanız, operatörün çağrıldığını (bir CEQtalimat yerine ) ve açıkça sonsuz özyinelemeli olmadığını görebilirsiniz (çünkü program beklendiği gibi sona erer):

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

Ortaya çıkan IL (LinqPad 4 tarafından derlenmiştir):

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 

İlginçtir - Aynı operatörleri ayrılmaz türleri için (her iki yansıma referans kaynağı veya üzeri) mevcut OLMAYAN, sadece Single, Double, Decimal, String, ve DateTimebunlar diğer dillerden çağrılacak var olduğunu benim tezini çürütüyor hangi. Açıkçası, bu operatörler olmadan diğer dillerde iki tamsayıyı eşitleyebilirsiniz, bu yüzden "neden varlar" sorusuna geri dönüyoruz double?


12
Bununla ilgili görebildiğim tek sorun, C # dil spesifikasyonunun aşırı yüklenmiş operatörlerin yerleşik operatörlere göre öncelikli olduğunu söylemesidir. Elbette, uyumlu bir C # derleyicisi burada aşırı yüklenmiş bir operatörün mevcut olduğunu görmeli ve sonsuz özyinelemeyi oluşturmalıdır. Hmm. Sıkan.
Damien_The_Unbeliever

5
Bu sorunun cevabı değil, imho. Yalnızca kodun neye çevrildiğini açıklar, ancak nedenini açıklamaz. Bölüm 7.3.4'e göre , C # dil belirtiminin ikili operatör aşırı yük çözümlemesi ayrıca sonsuz özyineleme beklerim. Referans kaynağının ( referenceource.microsoft.com/#mscorlib/system/… ) burada gerçekten geçerli olmadığını varsayıyorum .
Dirk Vollmar

6
@DStanley - Neyin üretildiğini inkar etmiyorum. Bunu dil spesifikasyonuyla bağdaştıramayacağımı söylüyorum. Sorun olan bu. Roslyn'i incelemeyi düşünüyordum ve burada herhangi bir özel işlem bulabilir miyim diye düşünüyordum ama şu anda bunu yapmak için iyi bir
ayarım

1
@Damien_The_Unbeliever Bu yüzden bunun ya özelliğin bir istisnası ya da "yerleşik" operatörlerin farklı bir yorumu olduğunu düşünüyorum.
D Stanley

1
@Jon Skeet henüz yanıt vermediği veya bu konuda yorum yapmadığı için, bunun bir hata olduğundan şüpheleniyorum (yani teknik özellik ihlali).
TheBlastOne

37

Buradaki ana kafa karışıklığı, tüm .NET kitaplıklarının (bu durumda, BCL'nin bir parçası olmayan Genişletilmiş Sayısal Kitaplığı ) standart C # ile yazıldığını varsaymanızdır . Bu her zaman böyle değildir ve farklı dillerin farklı kuralları vardır.

Standart C # 'da, gördüğünüz kod parçası, operatör aşırı yük çözümünün çalışma şekli nedeniyle yığın taşmasına neden olur. Bununla birlikte, kod aslında standart C #'da değildir - temelde C # derleyicisinin belgelenmemiş özelliklerini kullanır. Operatörü aramak yerine şu kodu verir:

ldarg.0
ldarg.1
ceq
ret

İşte bu :)% 100 eşdeğer bir C # kodu yoktur - bu kendi türünüzle C #'da mümkün değildir .

O zaman bile, C # kodunu derlerken gerçek operatör kullanılmaz - derleyici, bu durumda olduğu gibi, op_Equalityaramayı sadece basit olanla değiştirdiği bir dizi optimizasyon yapar ceq. Yine, bunu kendi DoubleExyapınızda kopyalayamazsınız - bu derleyici sihridir.

Bu kesinlikle .NET'te benzersiz bir durum değildir - geçerli olmayan birçok kod vardır, standart C #. Sebepler genellikle (a) derleyici hackleri ve (b) tuhaf (c) çalışma zamanı hackleriyle farklı bir dildir (sana bakıyorum Nullable!).

Roslyn C # derleyicisi oepn kaynağı olduğundan, aslında sizi aşırı yük çözümünün kararlaştırıldığı yere yönlendirebilirim:

Tüm ikili operatörlerin çözümlendiği yer

İç operatörler için "kısayollar"

Kısayollara baktığınızda, çift ve çift arasındaki eşitliğin içsel çift operatörde olduğunu, asla== tipte tanımlanan gerçek operatörde olmadığını göreceksiniz . .NET türü sistem, bunun Doublediğerleri gibi bir tür olduğunu varsaymak zorundadır , ancak C # yoktur - doubleC # 'da ilkeldir.


1
Referans kaynağındaki kodun sadece "ters mühendislik" olduğunu kabul ettiğimden emin değilim. Kod, derleyici yönergelerine #ifve derlenmiş kodda bulunmayan diğer yapılara sahiptir. Artı eğer o zaman için tersine mühendislik yapılmışsa doubleneden intveya için tersine mühendislik yapılmadı long? Kaynak kodun bir nedeni olduğunu düşünüyorum, ancak ==operatörün iç kullanımının CEQözyinelemeyi engelleyen bir şekilde derlendiğine inanıyorum . Operatör bu tür için "önceden tanımlanmış" bir operatör olduğundan (ve geçersiz kılınamadığından) aşırı yük kuralları uygulanmaz.
D Stanley

@DStanley Tüm kodun tersine mühendislik yapıldığını ima etmek istemedim . Ve yine, doubleBCL'nin bir parçası değil - ayrı bir kütüphanede, sadece C # spesifikasyonuna dahil ediliyor. Evet, ==alırlar a'ya derlenir ceq, ancak bu yine de bunun kendi kodunuzda kopyalayamayacağınız bir derleyici hack olduğu ve C # spesifikasyonunun parçası olmayan bir şey olduğu anlamına gelir ( yapıdaki float64alan gibi Double). Bu, C # 'ın sözleşmeye dayalı bir parçası değildir, bu nedenle C # derleyicisiyle derlenmiş olsa bile, onu geçerli C # gibi ele almanın pek bir anlamı yoktur.
Luaan

@DStanely, gerçek çerçevenin nasıl düzenlendiğini bulamadım, ancak .NET 2.0'ın referans uygulamasında, tüm zorlu kısımlar yalnızca C ++ 'da uygulanmış derleyici içselleri. Elbette hala çok sayıda .NET yerel kodu var, ancak "iki çiftin karşılaştırılması" gibi şeyler saf .NET'te pek işe yaramaz; Bu, kayan nokta sayılarının BCL'ye dahil edilmemesinin nedenlerinden biridir. Bununla birlikte, kod aynı zamanda (standart olmayan) C # ile de uygulanmıştır, muhtemelen tam olarak daha önce bahsettiğiniz nedenle - diğer .NET derleyicilerinin bu türleri gerçek .NET türleri olarak ele alabilmesini sağlamak için.
Luaan

@DStanley Ama tamam, nokta alındı. "Tersine mühendislik" referansı kaldırdım ve yanıtı yalnızca C # yerine "standart C #" dan açıkça bahsetmek için yeniden adlandırdım. Ve tedavi etmeyin doubleaynı şekilde intve long- intve longo ilkel türleridir tüm .NET dilleri desteklemesi gerekir. float, decimalVe doubledeğildir.
Luaan

12

İlkel türlerin kaynağı kafa karıştırıcı olabilir. Yapının ilk satırını gördünüz mü Double?

Normalde şöyle bir özyinelemeli yapı tanımlayamazsınız:

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

İlkel türlerin CIL'de de yerel desteği vardır. Normalde nesne yönelimli türler gibi ele alınmazlar. Bir double, float64CIL'deki gibi kullanılırsa sadece 64 bitlik bir değerdir . Ancak, normal bir .NET türü olarak ele alınırsa, gerçek bir değer içerir ve diğer türler gibi yöntemler içerir.

Yani burada gördüğünüz şey operatörler için aynı durum. Normalde, doğrudan çift türü kullanırsanız, asla çağrılmaz. BTW, kaynağı CIL'de şöyle görünür:

.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
}

Gördüğünüz gibi sonsuz döngü yok ( ceqenstrüman çağırmak yerine kullanılır System.Double::op_Equality). Bu nedenle, bir double bir nesne gibi ele alındığında, operatör yöntemi çağrılacak ve bu da sonunda onu float64CIL düzeyinde ilkel tür olarak ele alacaktır .


1
Bu yazının ilk bölümünü anlamayanlar için (belki de genellikle kendi değer türlerini yazmadıkları için) kodu deneyin public struct MyNumber { internal MyNumber m_value; }. Elbette derlenemez. Hatadır hata CS0523: Struct üyesi 'MyNumber' yapı düzeninde bir döngü neden türündeki 'MyNumber.m_value'
Jeppe Stig Nielsen

8

Ben bir göz attım CIL JustDecompile ile. İç kısım ==CIL ceq işlem koduna çevrilir . Başka bir deyişle, ilkel CLR eşitliği.

İki çift değeri karşılaştırırken C # derleyicisinin ceqmi yoksa ==operatöre mi başvuracağını merak ediyordum . Bulduğum önemsiz örnekte (aşağıda), kullanıldı ceq.

Bu program:

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

aşağıdaki CIL'yi oluşturur (etiketli ifadeye dikkat edin 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

System.Runtime.Versioning Ad Alanı için Microsoft belgelerinde belirtildiği gibi: Bu ad alanında bulunan türlerin kullanıcı uygulamaları için değil, .NET Framework içinde kullanılması amaçlanmıştır. System.Runtime.Versioning ad alanı, sürüm oluşturmayı destekleyen gelişmiş türleri içerir. .NET Framework'ün yan yana uygulamaları.


Ne System.Runtime.Versioningalakası var System.Double?
Koopakiller
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.