Por alguna razón, me estaba infiltrando en la fuente de .NET Framework para la clase Double
y descubrí que la declaración de ==
es:
public static bool operator ==(Double left, Double right) {
return left == right;
}
La misma lógica se aplica a todos los operadores.
- ¿Cuál es el punto de tal definición?
- ¿Como funciona?
- ¿Por qué no crea una recursión infinita?
c#
.net
language-lawyer
Thomas Ayoub
fuente
fuente
ceq
se emite en IL. Esto solo está ahí para llenar algún propósito de documentación, aunque no puedo encontrar la fuente.Respuestas:
En realidad, el compilador convertirá al
==
operador en unceq
código IL, y no se llamará al operador que menciona.Es probable que el motivo del operador en el código fuente sea para que pueda llamarse desde lenguajes distintos de C # que no lo traducen
CEQ
directamente a una llamada (o mediante reflexión). El código dentro del operador se compilará en aCEQ
, por lo que no hay recursión infinita.De hecho, si llama al operador a través de la reflexión, puede ver que se llama al operador (en lugar de una
CEQ
instrucción), y obviamente no es infinitamente recursivo (ya que el programa termina como se esperaba):IL resultante (compilado por LinqPad 4):
Curiosamente - NO existen los mismos operadores (ya sea en la fuente de referencia o por medio de la reflexión) para los tipos integrales, solamente
Single
,Double
,Decimal
,String
, yDateTime
, lo que refuta mi teoría de que existen a ser llamado de otros idiomas. Obviamente, puede equiparar dos enteros en otros idiomas sin estos operadores, así que volvemos a la pregunta "¿por qué existen paradouble
"?fuente
La principal confusión aquí es que está asumiendo que todas las bibliotecas .NET (en este caso, la Biblioteca de Numéricos Extendida, que no es parte del BCL) están escritas en C # estándar. Este no es siempre el caso, y diferentes idiomas tienen diferentes reglas.
En C # estándar, el fragmento de código que está viendo provocaría un desbordamiento de la pila, debido a la forma en que funciona la resolución de sobrecarga del operador. Sin embargo, el código no está realmente en C # estándar, básicamente usa características no documentadas del compilador de C #. En lugar de llamar al operador, emite este código:
Eso es todo :) No hay un código C # 100% equivalente, esto simplemente no es posible en C # con su propio tipo.
Incluso entonces, el operador real no se usa al compilar código C #: el compilador realiza un montón de optimizaciones, como en este caso, donde reemplaza la
op_Equality
llamada con solo lo simpleceq
. Una vez más, no puede replicar esto en su propiaDoubleEx
estructura: es la magia del compilador.Ciertamente, esta no es una situación única en .NET: hay mucho código que no es válido, estándar C #. Las razones son generalmente (a) hacks de compilación y (b) un lenguaje diferente, con los hacks de tiempo de ejecución (c) extraños (¡te estoy mirando
Nullable
!).Dado que el compilador Roslyn C # es una fuente abierta, puedo señalarle el lugar donde se decide la resolución de sobrecarga:
El lugar donde se resuelven todos los operadores binarios.
Los "atajos" para operadores intrínsecos
Cuando observe los accesos directos, verá que la igualdad entre doble y doble da como resultado el operador doble intrínseco, nunca en el
==
operador real definido en el tipo. El sistema de tipos .NET tiene que fingir queDouble
es un tipo como cualquier otro, pero C # no,double
es una primitiva en C #.fuente
#if
y otros artefactos que no estarían presentes en el código compilado. Además, si se realizó la ingeniería inversa, ¿pordouble
qué no se realizó la ingeniería inversaint
olong
? Creo que hay una razón para el código fuente, pero creo que el uso de==
dentro del operador se compila en unCEQ
que evita la recurrencia. Dado que el operador es un operador "predefinido" para ese tipo (y no se puede anular), las reglas de sobrecarga no se aplican.double
no es parte de BCL: está en una biblioteca separada, que simplemente está incluida en la especificación de C #. Sí,==
se compila en aceq
, pero eso todavía significa que este es un truco del compilador que no puede replicar en su propio código, y algo que no forma parte de la especificación de C # (al igual que elfloat64
campo en laDouble
estructura). No es una parte contractual de C #, por lo que tiene poco sentido tratarlo como un C # válido, incluso si se compiló con el compilador de C #.double
la misma manera queint
ylong
,int
ylong
son tipos primitivos que todos los lenguajes .NET deben admitir.float
,decimal
Ydouble
no lo son.La fuente de los tipos primitivos puede ser confusa. ¿Has visto la primera línea de la
Double
estructura?Normalmente no puede definir una estructura recursiva como esta:
Los tipos primitivos también tienen su soporte nativo en CIL. Normalmente no se tratan como tipos orientados a objetos. Un doble es solo un valor de 64 bits si se usa como
float64
en CIL. Sin embargo, si se maneja como un tipo .NET habitual, contiene un valor real y contiene métodos como cualquier otro tipo.Entonces, lo que ves aquí es la misma situación para los operadores. Normalmente, si utiliza el tipo de tipo doble directamente, nunca se llamará. Por cierto, su fuente se ve así en CIL:
Como puede ver, no hay un bucle sin fin (el
ceq
instrumento se usa en lugar de llamar alSystem.Double::op_Equality
). Entonces, cuando un doble se trata como un objeto, se llamará al método del operador, que eventualmente lo manejará como elfloat64
tipo primitivo en el nivel CIL.fuente
public struct MyNumber { internal MyNumber m_value; }
. No se puede compilar, por supuesto. El error es el error CS0523: el miembro de estructura 'MyNumber.m_value' del tipo 'MyNumber' provoca un ciclo en el diseño de estructuraEché un vistazo al CIL con JustDecompile. El interno
==
se traduce al código operativo CIL ceq . En otras palabras, es la igualdad primitiva de CLR.Tenía curiosidad por ver si el compilador de C # haría referencia
ceq
o el==
operador al comparar dos valores dobles. En el ejemplo trivial que se me ocurrió (a continuación), se utilizóceq
.Este programa:
genera el siguiente CIL (tenga en cuenta la declaración con la etiqueta
IL_0017
):fuente
Como se indica en la documentación de Microsoft para el espacio de nombres System.Runtime.Versioning: los tipos que se encuentran en este espacio de nombres están destinados para su uso dentro de .NET Framework y no para aplicaciones de usuario. El espacio de nombres System.Runtime.Versioning contiene tipos avanzados que admiten el control de versiones en implementaciones en paralelo de .NET Framework.
fuente
System.Runtime.Versioning
que ver conSystem.Double
?