elvis - null propagation c#
Operador ''='' encadenamiento en C#- seguramente esta prueba deberÃa pasar? (3)
Estaba escribiendo un establecedor de propiedades y tuve una onda cerebral sobre por qué no tenemos que return
el resultado de un set
cuando una propiedad podría estar involucrada en el encadenamiento del operador =
es decir:
var a = (b.c = d);
(He agregado los corchetes para mayor claridad, pero no hay diferencia en la práctica)
Comencé a pensar: ¿de dónde deriva el compilador de C # el valor que se asigna a en el ejemplo anterior?
La lógica dice que debería ser el resultado de la operación (bc = d)
, pero como se implementó con un void set_blah(value)
, no puede ser.
Así que las únicas otras opciones son:
- Vuelva a leer
bc
después de la asignación y use ese valor Reutilizar
d
Editar (desde la respuesta y los comentarios de Eric): hay una tercera opción, que es lo que hace C #: usar el valor escrito en
bc
después de que se hayan realizado las conversiones
Ahora, en mi opinión, la lectura correcta de la línea de código anterior es
establece a al resultado de configurar
bc
end
Creo que es una lectura razonable del código, así que pensé que probaría si eso es realmente lo que sucede con una prueba un tanto artificial, pero pregúntese si cree que debería aprobarse o no:
public class TestClass
{
private bool _invertedBoolean;
public bool InvertedBoolean
{
get
{
return _invertedBoolean;
}
set
{
//don''t ask me why you would with a boolean,
//but consider rounding on currency values, or
//properties which clone their input value instead
//of taking the reference.
_invertedBoolean = !value;
}
}
}
[TestMethod]
public void ExampleTest()
{
var t = new TestClass();
bool result;
result = (t.InvertedBoolean = true);
Assert.IsFalse(result);
}
Esta prueba falla .
Un examen más detallado de la IL que se genera para el código muestra que el true
valor se carga en la pila, se clona con un comando de dup
y luego ambos se eliminan en dos asignaciones sucesivas.
Esta técnica funciona perfectamente para los campos, pero para mí parece terriblemente ingenuo para las propiedades donde cada una es en realidad una llamada de método donde no se garantiza que el valor de la propiedad final real sea el valor de entrada.
Ahora sé que muchas personas odian las tareas anidadas, etc., pero el hecho es que el lenguaje te permite hacerlas y, por lo tanto, deberían funcionar como se espera.
Quizás estoy siendo muy fuerte, pero para mí esto sugiere una implementación incorrecta de este patrón por parte del compilador (.Net 4 btw). Pero entonces, ¿mi expectativa / lectura del código es incorrecta?
El operador de asignación está documentado para devolver el resultado de evaluar su segundo operando (en este caso, b
). No importa que también asigne este valor a su primer operando, y esta asignación se realiza llamando a un método que devuelve void
.
La especificación dice:
14.14.1 Asignación simple
El operador = se llama el operador de asignación simple. En una asignación simple, el operando de la derecha será una expresión de un tipo que se convertirá implícitamente al tipo del operando de la izquierda. La operación asigna el valor del operando derecho a la variable, propiedad o elemento de indexador dado por el operando izquierdo. El resultado de una expresión de asignación simple es el valor asignado al operando izquierdo. El resultado tiene el mismo tipo que el operando de la izquierda y siempre se clasifica como un valor.
Así que en realidad lo que pasa es:
- Se evalúa
d
(llamemos al valor producidoval
) - el resultado se asigna a
bc
- el resultado del operador de la asignación es
val
-
a
se le asigna el valorval
- el resultado del segundo operador de asignación también es
val
(pero como toda la expresión termina aquí, no se usa)
El resultado de una asignación x = {expr}
se define como el valor evaluado de {expr} .
§14.14.1 Asignación simple (ECMA334 v4)
... El resultado de una expresión de asignación simple es el valor asignado al operando izquierdo. El resultado tiene el mismo tipo que el operando de la izquierda y siempre se clasifica como un valor. ...
Y tenga en cuenta que el valor asignado es el valor ya evaluado de d
. De ahí que la implementación aquí es:
var tmp = (TypeOfC)d;
b.c = tmp;
a = tmp;
aunque también esperaría que con las optimizaciones habilitadas, use la instrucción dup
lugar de una variable local.
Me parece interesante que su expectativa sea que la asignación loca , es decir, asignar dos valores diferentes porque uno de ellos es una propiedad extremadamente rara con un comportamiento inusual, es el estado deseable .
Como ha deducido, hacemos todo lo posible para evitar ese estado. Eso es algo bueno . Cuando dices "x = y = z", si es posible, deberíamos garantizar que x e y terminen asignándoles el mismo valor, el de z, incluso si y es una locura que no tiene el valor que tienes. darle. "x = y = z" debería ser lógicamente como "y = z, x = z", excepto que z solo se evalúa una vez. y no entra en absoluto en el asunto cuando se asigna a x; ¿Por qué debería?
Además, por supuesto, al hacer "x = y = z" no podemos "reutilizar" constantemente y porque y podría ser una propiedad de solo escritura . ¿Qué pasa si no hay getter para leer el valor?
Además, observo que usted dice "esto funciona para los campos", no si el campo es volátil no lo hace. No tiene garantía alguna de que el valor que asignó sea el valor que toma el campo si es un campo volátil. No tiene garantía de que el valor que leyó de un campo volátil en el pasado sea el valor del campo ahora.
Para más ideas sobre este tema, vea mi artículo: