vida unidades una tipos ternario sintaxis resueltos relacional procedimiento problemas para operador niños medidas longitud linea importancia ejercicios ejemplos ejemplo cotidiana conversion condiciones con aritmetico c# compiler-errors implicit-conversion

unidades - sintaxis or en c#



Problema de conversión implícita en una condición ternaria (4)

Posible duplicado:
¿El operador condicional no puede lanzar implícitamente?
¿Por qué null necesita un tipo explícito emitido aquí?

He tenido una búsqueda y no he encontrado una buena explicación de por qué ocurre lo siguiente.
Tengo dos clases que tienen una interfaz en común y he intentado inicializar una instancia de este tipo de interfaz utilizando el operador ternario como se muestra a continuación, pero esto no se compila con el error "No se puede determinar el tipo de expresión condicional porque no hay una conversión implícita ''xxx.Class1'' y ''xxx.Class2'':

public ConsoleLogger : ILogger { .... } public SuppressLogger : ILogger { .... } static void Main(string[] args) { ..... // The following creates the compile error ILogger logger = suppressLogging ? new SuppressLogger() : new ConsoleLogger(); }

Esto funciona si emito explícitamente la primera condición a mi interfaz:

ILogger logger = suppressLogging ? ((ILogger)new SuppressLogger()) : new ConsoleLogger();

y obviamente siempre puedo hacer esto:

ILogger logger; if (suppressLogging) { logger = new SuppressLogger(); } else { logger = new ConsoleLogger(); }

Las alternativas están bien, pero no puedo entender por qué la primera opción falla con el error de conversión implícito ya que, en mi opinión, ambas clases son de tipo ILogger y no estoy realmente buscando hacer una conversión (implícita o explícita). ). Estoy seguro de que este es probablemente un problema de compilación de lenguaje estático, pero me gustaría entender qué está pasando.


Cada vez que cambia una variable de un tipo en una variable de otro tipo, eso es una conversión. Asignar una instancia de una clase a una variable de cualquier tipo que no sea esa clase requiere una conversión. Esta declaración:

ILogger a = new ConsoleLogger();

realizará una conversión implícita de ConsoleLogger a ILogger, que es legal porque ConsoleLogger implementa ILogger. Del mismo modo, esto funcionará:

ILogger a = new ConsoleLogger(); ILogger b = suppress ? new SuppressLogger() : a;

porque hay una conversión implícita entre SuppressLogger e ILogger. Sin embargo, esto no funcionará:

ILogger c = suppress ? new SuppressLogger() : new ConsoleLogger();

porque el operador terciario solo se esforzará por averiguar qué tipo desea en el resultado. Esencialmente hace esto:

  1. Si los tipos de operandos 2 y 3 son iguales, el operador terciario devuelve ese tipo y omite el resto de estos pasos.
  2. Si el operando 2 se puede convertir implícitamente al mismo tipo que el operando 3, podría devolver ese tipo.
  3. Si el operando 3 se puede convertir implícitamente al mismo tipo que el operando 2, podría devolver ese tipo.
  4. Si tanto # 2 como # 3 son verdaderos, o si # 2 o # 3 son verdaderos, genera un error.
  5. De lo contrario, devuelve el tipo para cualquiera de # 2 o # 3 fue verdadero.

En particular, no comenzará la búsqueda a través de todos los tipos que conoce sobre la búsqueda de un tipo de "mínimo denominador común", como una interfaz en común. Además, se evalúa el operador terciario y se determina su tipo de retorno, independientemente del tipo de variable en la que se almacena el resultado. Es un proceso de dos pasos:

  1. Determine el tipo de la expresión?: Y calcúlelo.
  2. Almacene el resultado de # 1 en la variable, realizando las conversiones implícitas según sea necesario.

Escribir uno o ambos de sus operandos es la forma correcta de realizar esta operación si eso es lo que necesita.


El problema es que el lado derecho de la declaración se evalúa sin mirar el tipo de la variable a la que está asignado.

No hay manera de que el compilador pueda mirar

suppressLogging ? new SuppressLogger() : new ConsoleLogger();

y decida cuál debe ser el tipo de retorno, ya que no hay una conversión implícita entre ellos. No busca antepasados ​​comunes, e incluso si lo hiciera, ¿cómo sabría cuál elegir?


Esto es una consecuencia de la confluencia de dos características de C #.

La primera es que C # nunca "hace magia" un tipo para ti. Si C # debe determinar un "mejor" tipo de un conjunto dado de tipos, siempre elige uno de los tipos que le diste. Nunca dice "ninguno de los tipos que me diste es el mejor; ya que las elecciones que me dio son todas malas, voy a elegir una cosa al azar que no me dio para elegir".

El segundo es que C # razona desde adentro hacia afuera . No decimos "Oh, veo que está tratando de asignar el resultado del operador condicional a un ILogger; déjeme asegurarme de que ambas ramas funcionen". Ocurre lo contrario: C # dice "déjeme determinar el mejor tipo devuelto por ambas sucursales y verifique que el mejor tipo sea convertible al tipo de destino".

La segunda regla es sensata porque el tipo de destino podría ser lo que estamos tratando de determinar. Cuando dices D d = b ? c : a; D d = b ? c : a; está claro cuál es el tipo de destino. Pero supongamos que en lugar de eso estuvieras llamando M(b?c:a) ? ¡Puede haber un centenar de sobrecargas diferentes de M cada una con un tipo diferente para el parámetro formal! Tenemos que determinar cuál es el tipo del argumento y luego descartar las sobrecargas de M que no son aplicables porque el tipo de argumento no es compatible con el tipo de parámetro formal; No vamos por el otro lado.

Considera lo que pasaría si fuéramos por el otro lado:

M1( b1 ? M2( b3 ? M4( ) : M5 ( ) ) : M6 ( b7 ? M8() : M9() ) );

Supongamos que hay cien sobrecargas de M1, M2 y M6. ¿Qué haces? Dice, OK, si esto es M1 (Foo), entonces M2 (...) y M6 (...) deben ser convertibles a Foo. ¿Son ellos? Vamos a averiguar. ¿Cuál es la sobrecarga de M2? Hay cien posibilidades. Veamos si cada uno de ellos es convertible del tipo de retorno de M4 y M5 ... OK, hemos probado todos esos, así que hemos encontrado un M2 que funciona. Ahora, ¿qué pasa con M6? ¿Qué pasa si el "mejor" M2 que encontramos no es compatible con el "mejor" M6? ¿Debemos dar marcha atrás y volver a intentar todas las posibilidades de 100 x 100 hasta que encontremos un par compatible? El problema se pone cada vez peor.

Razonamos de esta manera para las lambdas y como resultado, la resolución de sobrecarga que involucra a las lambdas es al menos NP-HARD en C # Eso es malo allí mismo; preferiríamos no agregar más problemas NP-HARD para que el compilador los resuelva.

También puede ver la primera regla en acción en otro lugar en el idioma. Por ejemplo, si dijiste: ILogger[] loggers = new[] { consoleLogger, suppressLogger }; obtendrías un error similar; el tipo de elemento de matriz inferido debe ser el mejor tipo de las expresiones escritas dadas. Si no se puede determinar un mejor tipo a partir de ellos, no intentamos encontrar un tipo que no nos haya proporcionado.

Lo mismo ocurre con la inferencia de tipo. Si tú dijiste:

void M<T>(T t1, T t2) { ... } ... M(consoleLogger, suppressLogger);

Entonces T no sería inferido como ILogger; esto seria un error Se infiere que T es el mejor tipo entre los tipos de argumentos proporcionados, y no hay un mejor tipo entre ellos.

Para obtener más detalles sobre cómo esta decisión de diseño influye en el comportamiento del operador condicional, vea mi serie de artículos sobre ese tema .

Si está interesado en saber por qué la resolución de sobrecarga que funciona "desde afuera hacia adentro" es NP-HARD, consulte este artículo .


Usted puede hacer eso:

ILogger logger = suppressLogging ? (ILogger)(new SuppressLogger()) : (ILogger)(new ConsoleLogger());

Cuando tienes una expresión como condition ? a : b condition ? a : b , debe haber una conversión implícita del tipo de a al tipo de b , o al revés, de lo contrario el compilador no puede determinar el tipo de la expresión. En su caso, no hay conversión entre SuppressLogger y ConsoleLogger ...

(Consulte la sección 7.14 en las especificaciones de lenguaje C # 4 para más detalles)