A menudo no puedo dar una respuesta satisfactoria a las preguntas "por qué".
La razón por la que el compilador C# muestra ese comportamiento es porque es (en este caso al menos (*)) una implementación correcta de la especificación C#.
La sección 6.4.5 de la especificación describe cómo se analizan las conversiones definidas por el usuario. Una lectura cuidadosa de esa sección explicará por qué la conversión explícita a int es legal, pero a decimal no.
Específicamente, el párrafo pertinente es:
encontrar el conjunto de aplicable usuario definido y operadores de conversión levantó, U. Este conjunto consta de los operadores de conversión implícitas o explícitas definido por el usuario y levantadas declarados por el clases o estructuras en D que convierten de un tipo que abarca o abarca a S a un tipo que abarca o abarca a T. Si U está vacío, la conversión no está definida y se produce un error en tiempo de compilación.
En su caso, S es TwFix y T es int o decimal. La única conversión explícita definida por el usuario en TwFix devuelve un flotante. int está abarcado por float, pero decimal no está abarcado ni abarca float. Por lo tanto, el conjunto U tiene un miembro en un caso y está vacío en el otro. Por lo tanto, un caso produce un error, como dice la especificación, y el otro no.
Tengo la sensación de que esta respuesta no es satisfactoria. Si no, ¿puede reformular la pregunta para que no contenga la palabra "por qué"? Estoy mucho mejor respondiendo preguntas sobre "qué" o "cómo" que preguntas sobre "por qué".
(*) El compilador ha conocido errores en el código que calcula si un tipo abarca otro con el propósito de determinar qué incorporados en las conversiones son relevantes en el análisis de la semántica de una conversión definida por el usuario particular. En muchos casos, deliberadamente no solucionamos estos errores porque hacerlo introduciría un cambio radical en el código del mundo real sin grandes beneficios. Me gustaría mucho volver a visitar esta sección de la especificación y reescribirla para eliminar el concepto de "tipo abarcador"; es un poco extraño en la especificación. Y, como ha descubierto, produce esta rareza, donde float es explícitamente convertible a decimal y decimal es explícitamente convertible a float, pero como ninguno abarca al otro, al código de conversión explícita definido por el usuario no le gusta. Sin embargo, esto es de muy baja prioridad.
Mi sensación es que la pregunta subyacente es por qué (sí, lo sé) el hecho de que hay una conversión implícita de int a float debería afectar las conversiones que ocurren en la dirección opuesta (TwFix32 para flotar a int). Esto me da la sensación de que * debería * ser una conversión irrelevante en este caso, pero claramente no lo es, porque afecta a la inclusión.Sospecho que la respuesta * real * "por qué" es probable que sea, "porque hace que muchos otros casos funcionen como se esperaba, a costa de este tipo de rareza". –
@Jon: Correcto; si piensa en estas reglas desde la perspectiva del análisis de las conversiones definidas por el usuario en los tipos de * referencia *, tiene más sentido; los tipos de referencia que no son de interfaz normalmente no tienen situaciones como la situación de flotante/decimal. –