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

Posible duplicado:
El operador condicional no puede transmitir implícitamente?
¿Por qué null necesita un molde de tipo explícito 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 no se puede comstackr con el error “No se puede determinar el tipo de expresión condicional porque no hay conversión implícita entre ‘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 expulso 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 son buenas, pero no entiendo por qué la primera opción falla con el error de conversión implícita, 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 comstackción de lenguaje estático, pero me gustaría entender qué está pasando.

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

La primera es que C # nunca “maquilla” un tipo para ti. Si C # debe determinar el “mejor” tipo de un conjunto determinado de tipos, siempre elige uno de los tipos que le diste. Nunca dice “ninguno de los tipos que me diste es el mejor tipo, ya que las elecciones que me diste son malas, voy a elegir algo aleatorio que no me has dado para elegir”.

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

La segunda regla es sensata porque el tipo objective puede 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 objective. Pero supongamos que en vez de eso llama a M(b?c:a) ? ¡Puede haber cientos 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 camino.

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

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

Supongamos que hay cientos de sobrecargas, cada una de M1, M2 y M6. ¿Qué haces? ¿Usted dice, OK, si esto es M1 (Foo) entonces M2 (…) y M6 (…) deben ser ambos 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 devolución de M4 y M5 … OK, hemos probado todos esos, así que hemos encontrado un M2 que funciona. ¿Y ahora qué hay de M6? ¿Qué pasa si el “mejor” M2 que encontramos no es compatible con el “mejor” M6? ¿Deberíamos retroceder y seguir probando todas las posibilidades de 100 x 100 hasta que encontremos un par compatible? El problema empeora cada vez más.

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

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

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

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

Entonces T no sería inferido como ILogger; esto sería un error Se deduce que T es el mejor tipo entre los tipos de argumentos suministrados, y no existe el 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, consulte mi serie de artículos sobre ese tema .

Si está interesado en por qué la resolución de sobrecarga que funciona “de 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 comstackdor 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 del lenguaje C # 4 para más detalles)

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

No hay forma de que el comstackdor pueda mirar

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

y decida cuál debería ser el tipo de devolución, ya que no hay conversión implícita entre ellos. No busca ancestros comunes, y aunque lo hiciera, ¿cómo sabría cuál elegir?

Cada vez que cambie una variable de un tipo en una variable de otro tipo, esa es una conversión. La asignación de una instancia de una clase a una variable de cualquier tipo que no sea esa clase requiere una conversión. Esta statement:

 ILogger a = new ConsoleLogger(); 

realizará una conversión implícita de ConsoleLogger a ILogger, lo cual 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 tratará de averiguar qué tipo de texto quería en el resultado. Básicamente hace esto:

  1. Si los tipos de operandos 2 y 3 son los mismos, el operador terciario devuelve ese tipo y omite el rest 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 ni # 2 ni # 3 son verdaderos, genera un error.
  5. De lo contrario, devuelve el tipo para el que sea # 2 o # 3 verdadero.

En particular, no comenzará a buscar 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 al operador terciario y se determina su tipo de devolución, independientemente del tipo de variable en la que está almacenando el resultado. Es un proceso de dos pasos:

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

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