Esto está permitido:
int a, b, c;
a = b = c = 16;
string s = null;
while ((s = "Hello") != null) ;
Según tengo entendido, la asignación s = ”Hello”;
solo debe hacer “Hello”
que se asigne s
, pero la operación no debe devolver ningún valor. Si eso fuera cierto, entonces ((s = "Hello") != null)
produciría un error, ya que null
no se compararía con nada.
¿Cuál es el razonamiento detrás de permitir que las declaraciones de asignación devuelvan un valor?
while ((s = GetWord()) != null) Process(s);
no lo es).Respuestas:
Tu comprensión es 100% incorrecta. ¿Puedes explicar por qué crees esta cosa falsa?
En primer lugar, las declaraciones de asignación no producen un valor. Las expresiones de asignación producen un valor. Una expresión de asignación es una declaración legal; solo hay un puñado de expresiones que son declaraciones legales en C #: en espera de una expresión, se pueden usar expresiones de construcción de instancias, incremento, decremento, invocación y asignación cuando se espera una declaración.
Solo hay un tipo de expresión en C # que no produce algún tipo de valor, a saber, una invocación de algo que se escribe como vacío. (O, de manera equivalente, esperar una tarea sin ningún valor de resultado asociado). Cualquier otro tipo de expresión produce un valor o variable o referencia o acceso de propiedad o acceso a eventos, y así sucesivamente.
Tenga en cuenta que todas las expresiones que son legales como declaraciones son útiles por sus efectos secundarios . Esa es la idea clave aquí, y creo que quizás la causa de su intuición de que las tareas deben ser declaraciones y no expresiones. Idealmente, tendríamos exactamente un efecto secundario por enunciado y ningún efecto secundario en una expresión. Se es un poco extraño que código-efectuar lado se puede utilizar en un contexto de expresión en absoluto.
El razonamiento detrás de permitir esta característica es porque (1) es frecuentemente conveniente y (2) es idiomático en lenguajes tipo C.
Uno podría notar que la pregunta ha sido planteada: ¿por qué esto es idiomático en lenguajes tipo C?
Dennis Ritchie ya no está disponible para preguntar, desafortunadamente, pero supongo que una tarea casi siempre deja atrás el valor que se acaba de asignar en un registro. C es un lenguaje muy "cercano a la máquina". Parece plausible y de acuerdo con el diseño de C que haya una función de lenguaje que básicamente significa "seguir usando el valor que acabo de asignar". Es muy fácil escribir un generador de código para esta característica; simplemente sigue usando el registro que almacenó el valor asignado.
fuente
=
/==
typo, que se soluciona fácilmente al no usar el valor de a=
menos que esté entre paréntesis. por ejemplo,if ((x = y))
oif ((x = y) == true)
está permitido peroif (x = y)
no lo está.¿No has proporcionado la respuesta? Es para habilitar exactamente los tipos de construcciones que ha mencionado.
Un caso común en el que se utiliza esta propiedad del operador de asignación es leer líneas de un archivo ...
fuente
return _myBackingField ?? (_myBackingField = CalculateBackingField());
mucho menos desperdicio que verificar nulo y asignar.Mi uso favorito de expresiones de asignación es para propiedades perezosamente inicializadas.
fuente
??=
sintaxis.Por un lado, le permite encadenar sus tareas, como en su ejemplo:
Por otro lado, le permite asignar y verificar un resultado en una sola expresión:
Ambas son posiblemente razones dudosas, pero definitivamente hay personas a las que les gustan estas construcciones.
fuente
return (HttpContext.Current.Items["x"] = myvar);
Además de las razones ya mencionadas (encadenamiento de tareas, establecer y probar dentro de los bucles while, etc.), para usar correctamente la
using
declaración, necesita esta función:MSDN desaconseja declarar el objeto desechable fuera de la instrucción de uso, ya que permanecerá dentro del alcance incluso después de que se haya eliminado (consulte el artículo de MSDN que he vinculado).
fuente
using
. Todos estos son legales:using (X x = new X())
,using (x = new X())
,using (x)
. Sin embargo, en este ejemplo, el contenido de la instrucción using es una sintaxis especial que no depende en absoluto de que la asignación devuelva un valor;Font font3 = new Font("Arial", 10.0f)
no es una expresión y no es válida en ningún lugar que espere expresiones.Me gustaría dar más detalles sobre un punto específico que Eric Lippert hizo en su respuesta y poner el foco en una ocasión particular que nadie más ha tocado. Eric dijo:
Me gustaría decir que la asignación siempre dejará atrás el valor que intentamos asignar a nuestro operando izquierdo. No solo "casi siempre". Pero no lo sé porque no he encontrado este problema comentado en la documentación. Teóricamente podría ser un procedimiento implementado muy efectivo para "dejar atrás" y no reevaluar el operando izquierdo, pero ¿es eficiente?
'Eficiente' sí para todos los ejemplos hasta ahora construidos en las respuestas de este hilo. ¿Pero eficiente en el caso de las propiedades e indexadores que usan get y set accessors? De ningún modo. Considera este código:
Aquí tenemos una propiedad, que ni siquiera es un contenedor para una variable privada. Siempre que se le solicite, volverá verdadero, cada vez que uno intente establecer su valor, no hará nada. Por lo tanto, siempre que se evalúe esta propiedad, él será sincero. Veamos qué pasa:
Adivina lo que imprime? Se imprime
Unexpected!!
. Resulta que, de hecho, se llama al descriptor de acceso, que no hace nada. Pero a partir de entonces, el get getor nunca se llama en absoluto. La asignación simplemente deja atrás elfalse
valor que intentamos asignar a nuestra propiedad. Y estefalse
valor es lo que evalúa la instrucción if.Terminaré con un ejemplo del mundo real que me hizo investigar este problema. Hice un indexador que era un contenedor conveniente para una colección (
List<string>
) que una clase mía tenía como variable privada.El parámetro enviado al indexador era una cadena, que debía tratarse como un valor en mi colección. El get getor simplemente devolvería verdadero o falso si ese valor existiera en la lista o no. Por lo tanto, el get getor era otra forma de usar el
List<T>.Contains
método.Si se llama al conjunto de acceso del indexador con una cadena como argumento y el operando correcto es un bool
true
, agregaría ese parámetro a la lista. Pero si el mismo parámetro se envió al descriptor de acceso y el operando correcto era un boolfalse
, en su lugar eliminaría el elemento de la lista. Por lo tanto, el conjunto de acceso se utilizó como una alternativa conveniente para ambosList<T>.Add
yList<T>.Remove
.Pensé que tenía una "API" ordenada y compacta que envolvía la lista con mi propia lógica implementada como puerta de enlace. Con la ayuda de un indexador solo, podría hacer muchas cosas con unas pocas pulsaciones de teclas. Por ejemplo, ¿cómo puedo intentar agregar un valor a mi lista y verificar que esté allí? Pensé que esta era la única línea de código necesaria:
Pero como mostró mi ejemplo anterior, ni siquiera se llamó al get getor que se supone que ve si el valor realmente está en la lista. El
true
valor siempre se dejó atrás destruyendo efectivamente cualquier lógica que haya implementado en mi get accessor.fuente
Si la asignación no devolviera un valor, la línea
a = b = c = 16
tampoco funcionaría.También poder escribir cosas como
while ((s = readLine()) != null)
puede ser útil a veces.Entonces, la razón detrás de permitir que la asignación devuelva el valor asignado, es permitirle hacer esas cosas.
fuente
=
ocurra en una expresión que no está entre paréntesis (sin contar los parentes de la declaración if / while). gcc da tales advertencias y, por lo tanto, esencialmente ha eliminado esta clase de errores de los programas C / C ++ que se compilan con él. Es una pena que otros escritores de compiladores hayan prestado tan poca atención a esta y otras buenas ideas en gcc.Creo que está malentendido cómo el analizador interpretará esa sintaxis. La asignación se evaluará primero y el resultado se comparará con NULL, es decir, la declaración es equivalente a:
Como otros han señalado, el resultado de una asignación es el valor asignado. Me resulta difícil imaginar la ventaja de tener
((s = "Hello") != null)
y
no ser equivalente ...
fuente
Creo que la razón principal es la similitud (intencional) con C ++ y C. Hacer que el operador de asignación (y muchas otras construcciones de lenguaje) se comporten como sus contrapartes de C ++ solo sigue el principio de la menor sorpresa, y cualquier programador que venga de otro rizado. el lenguaje de soporte puede usarlos sin tener que pensar mucho. Ser fácil de aprender para los programadores de C ++ fue uno de los principales objetivos de diseño para C #.
fuente
Por las dos razones que incluye en su publicación
1) para que pueda hacer
a = b = c = 16
2) para que pueda probar si una tarea tuvo éxito
if ((s = openSomeHandle()) != null)
fuente
El hecho de que 'a ++' o 'printf ("foo")' pueda ser útil como una declaración autónoma o como parte de una expresión más grande significa que C tiene que permitir la posibilidad de que los resultados de la expresión puedan o no ser usado. Dado eso, existe una noción general de que las expresiones que podrían 'devolver' un valor de manera útil también podrían hacerlo. El encadenamiento de asignaciones puede ser ligeramente "interesante" en C, e incluso más interesante en C ++, si todas las variables en cuestión no tienen exactamente el mismo tipo. Es probable que sea mejor evitar estos usos.
fuente
Otro gran ejemplo de caso de uso, lo uso todo el tiempo:
fuente
Una ventaja adicional que no veo dada en las respuestas aquí, es que la sintaxis para la asignación se basa en la aritmética.
Ahora
x = y = b = c = 2 + 3
significa algo diferente en aritmética que un lenguaje de estilo C; en la aritmética de su afirmación, que afirmar que x es igual ay, etc., y en un lenguaje de estilo C Es una instrucción que las marcas x igual a y etc. después de que se ejecute.Dicho esto, todavía hay suficiente relación entre la aritmética y el código que no tiene sentido rechazar lo que es natural en la aritmética a menos que haya una buena razón. (La otra cosa que los lenguajes de estilo C tomaron del uso del símbolo de igualdad es el uso de == para la comparación de igualdad. Sin embargo, aquí porque el == más a la derecha devuelve un valor este tipo de encadenamiento sería imposible).
fuente
a = b = c
medios quea
eb
yc
son la misma cosa. Cuando aprendemos un lenguaje de estilo C, aprendemos eso despuésa = b = c
,a
yb
somos lo mismo quec
. Ciertamente, hay una diferencia en la semántica, como dice mi propia respuesta, pero aun así, cuando era niño, aprendí a programar en un idioma que sí utilizaba=
para la asignación pero no permitía quea = b = c
esto me pareciera irrazonable ...=
para la comparación de igualdad, por lo quea = b = c
tendría que significar lo quea = b == c
significa en los lenguajes de estilo C. Encontré que el encadenamiento permitido en C era mucho más intuitivo porque podía dibujar una analogía con la aritmética.Me gusta usar el valor de retorno de la asignación cuando necesito actualizar un montón de cosas y devolver si hubo cambios o no:
Pero ten cuidado. Puede pensar que puede acortarlo a esto:
Pero esto en realidad dejará de evaluar las declaraciones o después de que encuentre la primera verdad. En este caso, eso significa que deja de asignar valores posteriores una vez que asigna el primer valor que es diferente.
Ver https://dotnetfiddle.net/e05Rh8 para jugar con esto
fuente