2009-11-03 14 views
9

falla:¿Hay alguna buena razón por la cual los ternarios en C# son limitados?

object o = ((1==2) ? 1 : "test"); 

tiene éxito:

object o; 
if (1 == 2) 
{ 
    o = 1; 
} 
else 
{ 
    o = "test"; 
} 

El error en la primera declaración es:

Tipo de expresión condicional no se puede determinar porque no hay una conversión implícita entre 'int 'y' cuerda '.

Por qué tiene que ser así, estoy asignando esos valores a una variable de tipo objeto.

Editar: El ejemplo anterior es trivial, sí, pero hay ejemplos en los que esto sería muy útiles:

int? subscriptionID; // comes in as a parameter 

EntityParameter p1 = new EntityParameter("SubscriptionID", DbType.Int32) 
{ 
    Value = ((subscriptionID == null) ? DBNull.Value : subscriptionID), 
} 
+0

Puedes corregir el error tipográfico en el título de tu pregunta, gracias. No tengo suficiente reputación para editarlo yo mismo. –

+0

Gracias por la corrección –

+4

En su segundo ejemplo, podría escribir: Value = subscriptionID ?? DBNull.Value; –

Respuesta

17

uso:

object o = ((1==2) ? (object)1 : "test"); 

La cuestión es que el tipo de retorno del operador condicional no se puede determinar de manera no ambigua. Es decir, entre int y cadena, no hay mejor opción. El compilador siempre usará el tipo de la expresión verdadera e implícitamente lanzará la expresión falsa si es necesario.

Editar: En le segundo ejemplo:

int? subscriptionID; // comes in as a parameter 

EntityParameter p1 = new EntityParameter("SubscriptionID", DbType.Int32) 
{ 
    Value = subscriptionID.HasValue ? (object)subscriptionID : DBNull.Value, 
} 

PS: 'operador ternario'
Eso no se llama la Es es un operador ternario, pero se llama 'operador condicional'.

+0

Para elaborar, '1' es un tipo primitivo, por lo que tiene que convertirlo en un objeto antes de asignarlo. –

+0

¿Las primitivas heredan el objeto en C#? – Wells

+0

@Wells que heredan de ValueType, y como lo estás lanzando a un objeto, hay algo de boxeo allí. – Joseph

2

¿Por qué la característica X de esta manera es a menudo una pregunta muy difícil de responder. Es mucho más fácil responder al comportamiento real.

Mi conjetura sobre por qué. El operador condicional puede usar sucinta y escuetamente una expresión booleana para elegir entre 2 valores relacionados. Deben estar relacionados porque se están utilizando en una sola ubicación. Si el usuario en cambio elige 2 valores no relacionados, quizás haya un error/error sutil en el código y es mejor que el compilador los avise de esto en lugar de lanzar implícitamente al objeto. Que puede ser algo que no esperaban.

0

"int" es un tipo primitivo, no un objeto, mientras que "cadena" se considera más un "objeto primitivo". Cuando haces algo como "objeto o = 1", en realidad estás encajonando el "int" en un "Int32". Aquí hay un enlace a un artículo sobre el boxeo:

http://msdn.microsoft.com/en-us/magazine/cc301569.aspx

En general, el boxeo debe evitarse debido al rendimiento pierde que son difíciles de rastrear.

Cuando utiliza una expresión ternaria, el compilador no mira en absoluto la variable de asignación para determinar cuál es el tipo final. Para desglosar su extracto original en lo que está haciendo el compilador:

Declaración: objeto o = ((1 == 2)?1: "prueba");

Compilador:

  1. ¿Cuáles son los tipos de "1" y "prueba" en '((1 == 2) 1:? "Test")'? ¿Se complementan?
  2. ¿El tipo final de # 1 coincide con el tipo de operador de asignación para 'objeto o'?

Como el compilador no evalúa el n. ° 2 hasta que se completa el n. ° 1, falla.

15

Aunque las otras respuestas son correcta, en el sentido de que hacen declaraciones verdaderas y relevantes, aquí hay algunos puntos sutiles de diseño del lenguaje que aún no se han expresado. Muchos factores diferentes contribuyen al diseño actual del operador condicional.

Primero, es deseable que tantas expresiones como sea posible tengan un tipo no ambiguo que pueda determinarse únicamente a partir del contenido de la expresión. Esto es deseable por varias razones. Por ejemplo: hace que construir un motor IntelliSense sea mucho más fácil. Escribe x.M(some-expression. e IntelliSense necesita poder analizar alguna expresión, determinar su tipo y generar un menú desplegable ANTES de que IntelliSense sepa a qué método se refiere x.M. IntelliSense no puede saber a qué se refiere X.M con seguridad si M está sobrecargado hasta que ve todos los argumentos, pero aún no ha escrito el primer argumento.

En segundo lugar, preferimos que la información de tipo fluya "de adentro hacia afuera", precisamente por el escenario que acabo de mencionar: resolución de sobrecarga. Considere lo siguiente:

void M(object x) {} 
void M(int x) {} 
void M(string x) {} 
... 
M(b ? 1 : "hello"); 

¿Qué debe hacer esto? ¿Debería llamar a la sobrecarga del objeto? ¿Debería a veces llamar a la sobrecarga de cadena y a veces llamar a la sobrecarga int? ¿Qué sucede si tiene otra sobrecarga, por ejemplo, M(IComparable x), cuando la elige?

Las cosas se vuelven muy complicadas cuando la información del tipo "fluye en ambos sentidos". Diciendo "Estoy asignando esto a una variable de tipo objeto, por lo tanto, el compilador debe saber que está bien elegir objeto, ya que el tipo" no se lava; a menudo es el caso que no conocemos el tipo de la variable que estamos asignando porque eso es lo que estamos intentando descifrar. La resolución de sobrecarga es exactamente el proceso de calcular los tipos de parámetros, que son las variables a las que se asignan los argumentos, a partir de los tipos de argumentos. Si los tipos de los argumentos dependen de los tipos a los que se les asigna, entonces tenemos una circularidad en nuestro razonamiento.

La información de tipo "fluye en ambos sentidos" para las expresiones lambda; implementar eso de manera eficiente me llevó la mejor parte de un año. He escrito una larga serie de artículos que describen algunas de las dificultades al diseñar e implementar un compilador que puede hacer análisis donde el tipo de información fluye en expresiones complejas basadas en el contexto en el que posiblemente se usa la expresión; la primera parte está aquí:

http://blogs.msdn.com/ericlippert/archive/2007/01/10/lambda-expressions-vs-anonymous-methods-part-one.aspx

se podría decir "bueno, vale, no veo por qué el hecho de que estoy asignando a objeto no puede ser utilizado con seguridad por el compilador, y no veo por qué es necesario para la expresión para tener un tipo inequívoco, pero ¿por qué no es el tipo de objeto de expresión, ya que tanto int como string son convertibles a objeto?"Esto me lleva a mi tercer punto:

En tercer lugar, uno de los principios de diseño sutiles pero consistentes de C# es" no producir tipos por magia ". Cuando se le presente una lista de expresiones de la cual debemos determinar tipo, el tipo que determinamos está siempre en la lista en algún lugar. Nunca inventamos un nuevo tipo y lo elegimos para usted, el tipo que obtiene es siempre uno que usted nos dio para elegir. Si usted dice encontrar el mejor escriba un conjunto de tipos, encontramos el mejor tipo EN ese conjunto de tipos. En el conjunto {int, cadena}, no hay mejor tipo común, la forma en que está, por ejemplo, "Animal, Tortuga, Mamífero, Wallaby" "Esta decisión de diseño se aplica al operador condicional, para escribir escenarios de unificación inferencial, para la inferencia de tipos de matriz tipados implícitamente, y así sucesivamente.

El motivo de esta decisión de diseño es que hace que sea más fácil para los seres humanos normales determinar qué hará el compilador en cualquier situación en la que se deba determinar el mejor tipo; si sabes que se va a elegir un tipo que está justo allí, mirándote a la cara, entonces es mucho más fácil averiguar lo que va a suceder.

También nos evita tener que elaborar muchas reglas complejas sobre cuál es el mejor tipo común de un conjunto de tipos cuando hay conflictos. Supongamos que tiene tipos {Foo, Bar}, donde ambas clases implementan IBlah, y ambas clases heredan de Baz. ¿Cuál es el mejor tipo común, IBlah, que ambos implementan, o Baz, que ambos extienden? No queremos tener que responder esta pregunta; queremos evitarlo por completo

Finalmente, observo que el compilador de C# en realidad obtiene la determinación de los tipos sutilmente incorrectos en algunos casos poco claros. Mi primer artículo sobre el que está aquí:

http://blogs.msdn.com/ericlippert/archive/2006/05/24/type-inference-woes-part-one.aspx

Es discutible que, de hecho, el compilador lo hace bien y la especificación es incorrecta; el diseño de implementación es en mi opinión mejor que el diseño específico.

De todos modos, eso es solo algunas de las razones para el diseño de este aspecto particular del operador ternario. Hay otras sutilezas aquí, por ejemplo, cómo el verificador CLR determina si se garantiza que un conjunto dado de rutas de bifurcación deja el tipo correcto en la pila en todas las rutas posibles. Discutir eso en detalle me llevaría bastante lejos.

+0

Gracias Eric. Acabo de leer el primer párrafo y me preguntaba si intellisense puede identificar la sobrecarga correcta cuando escribe. Por ejemplo, si tengo un método llamado Perform, que puede tomar 1 argumento solo de tipos, string, int, float, Size, etc., ¿se desplazaría intellisense a la información sobre herramientas de sobrecarga correcta usando el tipo que uso? Entonces, si uso Perform (myString), ¿mostraría inmediatamente la información sobre herramientas para Perform (string)? En este momento, solo se desplaza cuando cambia la cantidad de argumentos. –

Cuestiones relacionadas