2010-11-03 10 views
5

asumir dos clases, ambos descendientes de la misma superclase, como esto:El operador condicional se confunde, pero ¿por qué?

class MySuperClass{} 
class A : MySuperClass{} 
class B : MySuperClass{} 

A continuación, esta asignación no pasará el compilador:

MySuperClass p = myCondition ? new A() : new B(); 

El compilador se queja de que A y B no son compatibles (El tipo de expresión condicional no puede determinarse porque no hay una conversión implícita entre 'A' y 'B' [CS0173]). Pero ambos son del tipo MySuperClass, por lo que en mi opinión esto debería funcionar. No es que sea un gran problema; un elenco simple es todo lo que se necesita para iluminar el compilador. Pero seguramente es un inconveniente en el compilador de C#? ¿No estás de acuerdo?

+0

Esta es una pregunta frecuente. Ver por ejemplo http://stackoverflow.com/questions/2215745 o http://stackoverflow.com/questions/3150086 o http://stackoverflow.com/questions/858080. Ver también mis artículos sobre el tema: http://blogs.msdn.com/b/ericlippert/archive/tags/conditional+operator/ –

Respuesta

10

Los resultados del condicional deben ser del mismo tipo. Ellos no son.

De MSDN, (?: Operador):

O bien el tipo de first_expression y second_expression debe ser la misma, o una conversión implícita debe existir de un tipo a otro.

Desde A y B no son del mismo tipo y que no parecen haber definido una conversión implícita, el compilador se queja.

+3

Sí, intenta agregar un elenco: 'p = myCondition? (MySuperClass) nuevo A(): (MySuperClass) nuevo B(); ' – anthares

+1

@anthares - Realmente solo necesitas lanzar uno de ellos y el compilador ingresa el resto -> p = myCondition? (MySuperClass) nuevo A(): nuevo B() –

+0

Claro que sí. Ambos son MySuperClass. Por cierto; esto funciona: MySuperClass p = myCondition? (MySuperClass) nuevo A(): nuevo B(); Entonces es suficiente con elegir a uno de los dos candidatos ... Como dije; *No es un gran trato*. Pero sigue siendo una curiosidad, ¿eh? – BaBu

3

El compilador no intenta buscar un ancestro común, por lo que necesita un molde explícito para mostrar qué antecesor desea tratar como; En su caso:

MySuperClass p = myCondition ? (MySuperClass)(new A()) : (MySuperClass)(new B()); 

Esto significa que el operador condicional tiene dos lados returnin mismo tipo gthe, que satisface el compilador.

+0

Tiene paréntesis redundantes alrededor de 'new A()' y 'new B()', lo que hace que se vea feo a mis ojos. –

4

Consulte este blog para obtener algunos artículos interesantes sobre por qué el compilador C# hace/no hace cosas que son 'obvias' para usted. El blog está escrito por Eric Lippert, uno de los desarrolladores del compilador C#.

+0

+1: Este es uno de mis blogs favoritos, –

+0

+1 En segundo lugar, me parece muy útil este blog. – pstrjds

+3

Si va a vincular a su blog, al menos haga un esfuerzo por encontrar un artículo relevante. En este caso, los artículos de Eric sobre esta pregunta se encontrarán en http://blogs.msdn.com/b/ericlippert/archive/2010/05/27/cast-operators-do-not-obey-the-distributive-law .aspx y http://blogs.msdn.com/b/ericlippert/archive/2006/05/24/type-inference-woes-part-one.aspx entre otros. – Brian

1

Rowland Shaw lo resumió bastante bien, pero para ver por qué un ancestro común no se usa implícitamente, considere qué pasaría si ambas clases también implementaran una interfaz en particular. El compilador no podría determinar cuál usar para el tipo del operador condicional, por lo que se vería forzado a usar object.

4

mirada a la sección 7.14 de la especificación del lenguaje

El segundo y tercer operandos, x e y , de la:? El control del operador del tipo de la expresión condicional.

· Si x tiene el tipo X y Y tiene tipo Y entonces

o Si una conversión implícita (apartado 6.1) existe de X a Y, pero no de Y a X, entonces Y es el tipo de la expresión condicional .

o Si una conversión implícita (§6.1) existe de Y a X, pero no de X a Y, entonces X es el tipo de la expresión condicional .

o De lo contrario, no se puede determinar el tipo de expresión , y se produce un error en tiempo de compilación .

· Si sólo uno de X e Y tiene un tipo , y tanto X como Y, de areimplicitly convertible a que tipo, a continuación, que es el tipo de la expresión condicional.

· De lo contrario, no se puede determinar el tipo de expresión y se produce un error en tiempo de compilación.

Esencialmente, los operandos deben ser convertible a entre sí, no mutuamente convertibles a algún otro tipo.

Es por eso que necesita hacer una conversión explícita en su ejemplo o en casos tales como nullables (int? foo = isBar ? 42 : (int?)null). El tipo de declaración no afecta la evaluación, el compilador debe resolverlo desde la expresión misma.

3

El operador condicional (como con cualquier otro operador) tiene que definir el tipo que representa su expresión. En el caso del operador condicional, tiene un proceso de dos pasos:

  1. ¿Los operandos son del mismo tipo? Si es así, ese es el tipo de expresión.
  2. ¿Hay una conversión implícita de uno de los tipos de operandos al otro (pero no en ambas direcciones)? Si es así, entonces el "otro" es el tipo de expresión.

No hay búsqueda de ascendencia, ya que la implementación podría llevar a una pendiente resbaladiza de ambigüedad en lo que usted, como desarrollador, podría especificar en esa expresión. ¿Debería todo conducir a object? ¿Qué pasa con los tipos de valores, que luego estarían enmarcados implícitamente? ¿Qué pasa con las interfaces? Si hay más de una interfaz común entre los dos tipos, ¿cuál debería elegirse?

En su caso, como ha descubierto, necesita actualizar uno de los operandos al tipo principal. Una vez que lo haga, se cumple la regla 2. (siempre hay una conversión implícita que va de un tipo más específico a un tipo menos específico).

Tenga en cuenta que solo necesita aplicar el molde a uno de los operandos, no a ambos.

Cuestiones relacionadas