Errores graves con conversiones levantadas / nullables desde int, permitiendo la conversión desde decimal
Creo que esta pregunta me traerá fama instantánea aquí en Stack Overflow.
Supongamos que tiene el siguiente tipo:
// represents a decimal number with at most two decimal places after the period
struct NumberFixedPoint2
{
decimal number;
// an integer has no fractional part; can convert to this type
public static implicit operator NumberFixedPoint2(int integer)
{
return new NumberFixedPoint2 { number = integer };
}
// this type is a decimal number; can convert to System.Decimal
public static implicit operator decimal(NumberFixedPoint2 nfp2)
{
return nfp2.number;
}
/* will add more nice members later */
}
Se ha escrito de tal manera que solo se permiten conversiones seguras que no pierden precisión. Sin embargo, cuando pruebo este código:
static void Main()
{
decimal bad = 2.718281828m;
NumberFixedPoint2 badNfp2 = (NumberFixedPoint2)bad;
Console.WriteLine(badNfp2);
}
Me sorprende esto compila y, cuando se ejecuta, escribe 2
. La conversión de int
(de valor 2
) a NumberFixedPoint2
es importante aquí. (Se prefiere una sobrecarga de WriteLine
que toma un System.Decimal
, en caso de que alguien se pregunte.)
¿Por qué en la Tierra se permite la conversión de decimal
a NumberFixedPoint2
? (Por cierto, en el código anterior, si NumberFixedPoint2
se cambia de una estructura a una clase, nada cambia.)
¿Sabe si la Especificación del lenguaje C# dice que una conversión implícita de int
a un tipo personalizado "implica" la existencia de una conversión explícita "directa" de decimal
a ese tipo personalizado?
Se vuelve mucho peor. Pruebe este código en su lugar:
static void Main()
{
decimal? moreBad = 7.3890560989m;
NumberFixedPoint2? moreBadNfp2 = (NumberFixedPoint2?)moreBad;
Console.WriteLine(moreBadNfp2.Value);
}
Como ves, nosotros tienen (levantadas) Nullable<>
conversiones aquí. Pero sí, eso compila.
Cuando se compila en x86 "platform", este código escribe un valor numérico impredecible. Cuál varía de vez en cuando. Como ejemplo, en una ocasión tuve 2289956
. ¡Ese es un error serio!
Cuando se compila para la plataforma x64, el código anterior bloquea la aplicación con un System.InvalidProgramException
con el mensaje Common Language Runtime detectó un programa no válido. Según la documentación de la clase InvalidProgramException
:
Generalmente esto indica un error en el compilador que generó el programa.
¿Alguien (como Eric Lippert, o alguien que ha trabajado con conversiones elevadas en el compilador de C#) sabe la causa de estos errores? Como, ¿cuál es una condición suficiente para que no nos topemos con ellos en nuestro código? Porque el tipo NumberFixedPoint2
es en realidad algo que tenemos en código real (administrar el dinero de otras personas y cosa).
2 answers
Su segunda parte (usando tipos nullables) parece ser muy similar a este error conocido en el compilador actual. De la respuesta sobre el problema Connect:
Si bien actualmente no tenemos planes para abordar este problema en la próxima versión de Visual Studio, planeamos investigar una solución en Roslyn
Como tal, esperamos que este error se corrija en una versión futura de Visual Studio y los compiladores.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-20 19:07:41
Solo estoy respondiendo a la primera parte de la pregunta para empezar. (Sugiero que la segunda parte debe ser una pregunta separada; es más probable que sea un error.)
Solo hay una explícita conversión de decimal
a int
, pero esa conversión está siendo implícitamente llamada en tu código. La conversión sucede en este IL:
IL_0010: stloc.0
IL_0011: ldloc.0
IL_0012: call int32 [mscorlib]System.Decimal::op_Explicit(valuetype [mscorlib]System.Decimal)
IL_0017: call valuetype NumberFixedPoint2 NumberFixedPoint2::op_Implicit(int32)
Creo que este es el comportamiento correcto de acuerdo con la especificación, aunque es sorprendente1. Vamos a trabajar nuestro camino a través de la sección 6.4.5 de la especificación de C# 4 (Conversiones Explícitas definidas por el usuario). No voy a copiar todo el texto, ya que sería tedioso, solo cuáles son los resultados relevantes en nuestro caso. Del mismo modo, no voy a usar subíndices, ya que no funcionan bien con la fuente de código aquí:)
- Determinar los tipos
S0
yT0
:S0
esdecimal
, yT0
esNumberFixedPoint2
. - Encuentre el conjunto de tipos,
D
, a partir del cual se considerarán los operadores de conversión definidos como usados: solo{ decimal, NumberFixedPoint2 }
- Encuentre el conjunto de operadores de conversión definidos por el usuario y elevados aplicables,
U
.decimal
abarcaint
(sección 6.4.3) porque hay una conversión implícita estándar deint
adecimal
. Así que el operador de conversión explícito es enU
, y es de hecho el único miembro deU
- Encuentre el tipo de fuente más específico,
Sx
, de los operadores enU
- El operador no convierte desde
S
(decimal
) así que la primera bala es out - El operador no convierte desde un tipo que abarca
S
(decimal
encompassesint
, not the other way round) so the second bullet is out - Eso solo deja la tercera bala, que habla del "tipo más abarcador" - bueno, solo tenemos un tipo, así que está bien:
Sx
esint
.
- El operador no convierte desde
- Encuentre el tipo de destino más específico,
Tx
, de los operadores enU
- El operador se convierte directamente a
NumberFixedPoint2
por lo queTx
esNumberFixedPoint2
.
- El operador se convierte directamente a
- Encuentre el operador de conversión más específico:
-
U
contiene exactamente un operador, que de hecho convierte deSx
aTx
, por lo que es el operador más específico
-
- Finalmente, aplicar la conversión:
-
Si
S
no esSx
, entonces se realiza una conversión explícita estándar deS
aSx
. (Así que esdecimal
aint
.) - Se invoca el operador de conversión definido por el usuario más específico (su operador)
-
T
esTx
así que no hay necesidad de la conversión en la tercera bala
-
Si
La línea en negrita es el bit que confirma que una conversión explícita estándar es realmente factible, cuando solo se especifica una conversión explícita de un tipo diferente.
1 Bueno, lo encontré sorprendente, al menos. No soy consciente de haber visto esto antes.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-20 19:15:11