Z jakiegoś powodu podkradałem się do źródła .NET Framework dla tej klasy Double
i odkryłem, że deklaracja ==
to:
public static bool operator ==(Double left, Double right) {
return left == right;
}
Ta sama logika obowiązuje dla każdego operatora.
- Jaki jest sens takiej definicji?
- Jak to działa?
- Dlaczego nie tworzy nieskończonej rekurencji?
c#
.net
language-lawyer
Thomas Ayoub
źródło
źródło
ceq
jest wydawany w IL. Jest to tylko po to, aby wypełnić jakieś cele dokumentacyjne, nie mogę jednak znaleźć źródła.Odpowiedzi:
W rzeczywistości kompilator zamieni
==
operator wceq
kod IL, a operator, o którym wspomniałeś, nie zostanie wywołany.Przyczyną istnienia operatora w kodzie źródłowym jest prawdopodobnie to, że można go wywołać z języków innych niż C #, które nie tłumaczą go
CEQ
bezpośrednio na wywołanie (lub przez odbicie). Kod wewnątrz operatora zostanie skompilowany do aCEQ
, więc nie ma nieskończonej rekursji.W rzeczywistości, jeśli wywołasz operator przez odbicie, zobaczysz, że operator jest wywoływany (a nie
CEQ
instrukcja) i oczywiście nie jest nieskończenie rekurencyjny (ponieważ program kończy się zgodnie z oczekiwaniami):Wynikowy IL (skompilowany przez LinqPad 4):
Co ciekawe - nie istnieją same podmioty (zarówno w źródle referencyjnym lub poprzez odbicie) dla integralnych typów, tylko
Single
,Double
,Decimal
,String
, iDateTime
, co obala moją teorię, że istnieją one być wywoływane z innych języków. Oczywiście bez tych operatorów można zrównać dwie liczby całkowite w innych językach, więc wracamy do pytania „dlaczego one istnieją dladouble
”?źródło
Głównym problemem jest to, że zakładasz, że wszystkie biblioteki .NET (w tym przypadku Extended Numerics Library, która nie jest częścią BCL) są napisane w standardowym języku C #. Nie zawsze tak jest, a różne języki mają różne zasady.
W standardowym języku C # fragment kodu, który widzisz, spowodowałby przepełnienie stosu ze względu na sposób działania rozpoznawania przeciążenia operatora. Jednak kod nie jest w rzeczywistości w standardowym języku C # - w zasadzie wykorzystuje nieudokumentowane funkcje kompilatora C #. Zamiast dzwonić do operatora, emituje następujący kod:
To wszystko :) Nie ma 100% odpowiednika kodu C # - to po prostu nie jest możliwe w C # z własnym typem.
Nawet wtedy rzeczywisty operator nie jest używany podczas kompilowania kodu C # - kompilator wykonuje kilka optymalizacji, jak w tym przypadku, gdzie zastępuje
op_Equality
wywołanie prostymceq
. Ponownie, nie możesz tego powtórzyć we własnejDoubleEx
strukturze - to magia kompilatora.Z pewnością nie jest to wyjątkowa sytuacja w .NET - jest mnóstwo nieprawidłowego kodu, standardowy C #. Powody są zwykle (a) hacki kompilatora i (b) inny język, z dziwnymi (c) hackami środowiska uruchomieniowego (patrzę na ciebie
Nullable
!).Ponieważ kompilator Roslyn C # jest źródłem oepn, mogę wskazać miejsce, w którym zdecydowano o rozwiązaniu problemu:
Miejsce, w którym wszystkie operatory binarne są rozwiązywane
„Skróty” dla operatorów wewnętrznych
Kiedy spojrzysz na skróty, zobaczysz, że równość między double i double skutkuje wewnętrznym operatorem double, nigdy faktycznym
==
operatorem zdefiniowanym w typie. System typów .NET musi udawać, żeDouble
jest typem jak każdy inny, ale C # tego nie robi -double
jest prymitywem w C #.źródło
#if
i inne artefakty, które nie byłyby obecne w skompilowanym kodzie. Poza tym, jeśli został poddany inżynierii wstecznej,double
dlaczego nie został zaprojektowany dlaint
lublong
? Myślę, że jest powód dla kodu źródłowego, ale uważam, że użycie==
operatora wewnątrz zostaje skompilowane do a,CEQ
co zapobiega rekursji. Ponieważ operator jest operatorem „predefiniowanym” dla tego typu (i nie można go zastąpić), reguły przeciążenia nie mają zastosowania.double
nie jest częścią BCL - znajduje się w oddzielnej bibliotece, która akurat znajduje się w specyfikacji C #. Tak,==
zostaje skompilowany do aceq
, ale to nadal oznacza, że jest to hack kompilatora, którego nie można replikować we własnym kodzie, i coś, co nie jest częścią specyfikacji C # (podobnie jakfloat64
pole wDouble
strukturze). Nie jest to umowna część C #, więc nie ma sensu traktować go jak prawidłowego C #, nawet jeśli został skompilowany za pomocą kompilatora C #.double
tego w ten sam sposób, coint
ilong
-int
ilong
są to typy prymitywne, które muszą obsługiwać wszystkie języki .NET.float
,decimal
Adouble
nie są.Źródło typów pierwotnych może być mylące. Widziałeś pierwszą linię
Double
struktury?Zwykle nie można zdefiniować struktury rekurencyjnej w ten sposób:
Typy prymitywne mają również natywne wsparcie w CIL. Zwykle nie są traktowane jak typy zorientowane obiektowo. Double to tylko 64-bitowa wartość, jeśli jest używana tak jak
float64
w CIL. Jeśli jednak jest obsługiwany jak zwykły typ .NET, zawiera rzeczywistą wartość i zawiera metody, jak wszystkie inne typy.Więc to, co widzisz tutaj, to ta sama sytuacja dla operatorów. Zwykle jeśli używasz typu podwójnego bezpośrednio, nigdy nie zostanie wywołany. Przy okazji, jego źródło wygląda tak w CIL:
Jak widać, nie ma nieskończonej pętli (
ceq
instrument jest używany zamiast wywoływaniaSystem.Double::op_Equality
). Więc kiedy double jest traktowany jak obiekt, zostanie wywołana metoda operatora, która ostatecznie obsłuży go jakofloat64
typ pierwotny na poziomie CIL.źródło
public struct MyNumber { internal MyNumber m_value; }
. Oczywiście nie można go skompilować. Błąd to błąd CS0523: element struktury „MyNumber.m_value” typu „MyNumber” powoduje cykl w układzie strukturyWziąłem spojrzeć na CIL z JustDecompile. Wewnętrzna
==
jest tłumaczona na kod operacyjny CIL ceq . Innymi słowy, jest to pierwotna równość CLR.Byłem ciekawy, czy kompilator C # będzie odwoływał się
ceq
do==
operatora, czy też porównując dwie podwójne wartości. W trywialnym przykładzie, który wymyśliłem (poniżej), użyłemceq
.Ten program:
generuje następujący CIL (zwróć uwagę na instrukcję z etykietą
IL_0017
):źródło
Jak wskazano w dokumentacji firmy Microsoft dotyczącej przestrzeni nazw System.Runtime.Versioning: Typy znajdujące się w tej przestrzeni nazw są przeznaczone do użytku w ramach platformy .NET Framework, a nie aplikacji użytkownika. Przestrzeń nazw System.Runtime.Versioning zawiera zaawansowane typy obsługujące przechowywanie wersji w równoległe implementacje .NET Framework.
źródło
System.Runtime.Versioning
wspólnegoSystem.Double
?