Leí la Especificación del lenguaje C # sobre los operadores lógicos condicionales || y &&, también conocidos como operadores lógicos de cortocircuito. Para mí, no parecía claro si estos existían para valores booleanos que aceptan valores NULL, es decir, el tipo de operando Nullable<bool>(también escrito bool?), así que lo probé con escritura no dinámica:
bool a = true;
bool? b = null;
bool? xxxx = b || a; // compile-time error, || can't be applied to these types
Eso pareció resolver la pregunta (no pude entender la especificación claramente, pero suponiendo que la implementación del compilador de Visual C # fuera correcta, ahora lo sabía).
Sin embargo, también quería probar con la dynamicencuadernación. Así que probé esto en su lugar:
static class Program
{
static dynamic A
{
get
{
Console.WriteLine("'A' evaluated");
return true;
}
}
static dynamic B
{
get
{
Console.WriteLine("'B' evaluated");
return null;
}
}
static void Main()
{
dynamic x = A | B;
Console.WriteLine((object)x);
dynamic y = A & B;
Console.WriteLine((object)y);
dynamic xx = A || B;
Console.WriteLine((object)xx);
dynamic yy = A && B;
Console.WriteLine((object)yy);
}
}
El resultado sorprendente es que esto funciona sin excepción.
Bueno, xy yno es de extrañar, sus declaraciones llevan a que se recuperen ambas propiedades, y los valores resultantes son los esperados, xes truey yes null.
Pero la evaluación xxde no A || Bcondujo a ninguna excepción de tiempo de vinculación, y solo Ase leyó la propiedad , no B. ¿Por qué pasó esto? Como puede ver, podríamos cambiar el Bgetter para devolver un objeto loco, como "Hello world", y xxaún evaluaríamos truesin problemas de vinculación ...
La evaluación A && B(para yy) tampoco conduce a ningún error de tiempo de vinculación. Y aquí se recuperan ambas propiedades, por supuesto. ¿Por qué lo permite el archivador en tiempo de ejecución? Si el objeto devuelto Bse cambia a un objeto "incorrecto" (como a string), se produce una excepción de vinculación.
¿Es este el comportamiento correcto? (¿Cómo puedes inferir eso de la especificación?)
Si lo intenta Bcomo primer operando, ambos B || Ay B && Adan una excepción del enlazador en tiempo de ejecución ( B | Ay B & Afuncionan bien, ya que todo es normal con operadores sin cortocircuito |y &).
(Probado con el compilador C # de Visual Studio 2013 y la versión en tiempo de ejecución .NET 4.5.2.)
fuente

Nullable<Boolean>involucrados en absoluto, solo booleanos en caja tratados comodynamic: su prueba conbool?es irrelevante. (Por supuesto, esta no es una respuesta completa, solo el germen de una.)A || Bhace que una cierta cantidad de sentido, en la que no se quiere evaluarBa menosAque es falso, que no lo es. Así que nunca se sabe el tipo de expresión. LaA && Bversión es más sorprendente: veré qué puedo encontrar en la especificación.Aesbooly el valor deBesnull, entonces unbool && bool?operador podría estar involucrado.&&habla de resolverlo como si fuera en su&lugar, y específicamente incluye el caso donde están ambos operandosbool?, pero la siguiente sección a la que se refiere no maneja el caso que acepta valores NULL. Podría agregar una especie de respuesta con más detalles sobre eso, pero no lo explicaría completamente.Respuestas:
En primer lugar, gracias por señalar que la especificación no es clara en el caso de bool que acepta valores NULL no dinámico. Lo arreglaré en una versión futura. El comportamiento del compilador es el comportamiento previsto;
&&y||se supone que no funcionan en bools que aceptan valores NULL.Sin embargo, el enlazador dinámico no parece implementar esta restricción. En cambio, vincula las operaciones del componente por separado: el
&/|y el?:. Por lo tanto, puede pasar si el primer operando estrueofalse(que son valores booleanos y, por lo tanto, se permiten como el primer operando de?:), pero si danullcomo primer operando (por ejemplo, si lo intentaB && Aen el ejemplo anterior), lo hace obtener una excepción de enlace en tiempo de ejecución.Si lo piensa, puede ver por qué implementamos dinámica
&&y de||esta manera en lugar de como una gran operación dinámica: las operaciones dinámicas se vinculan en tiempo de ejecución después de que se evalúan sus operandos , de modo que la vinculación puede basarse en los tipos de tiempo de ejecución de los resultados de esas evaluaciones. ¡Pero una evaluación tan entusiasta frustra el propósito de cortocircuitar a los operadores! Entonces, en cambio, el código generado para dinámica&&y||divide la evaluación en partes y procederá de la siguiente manera:x)boolconversión implícita o en los operadorestrueofalse(fallar si no puede)xcomo condición en una?:operaciónxcomo resultadoy)&or|según el tipo de tiempo de ejecución dexyy(fallar si no puede)Este es el comportamiento que deja pasar ciertas combinaciones "ilegales" de operandos: el
?:operador trata con éxito el primer operando como un booleano no anulable , el operador&or|lo trata con éxito como un booleano anulable y los dos nunca se coordinan para comprobar que están de acuerdo .Entonces no es tan dinámico && y || trabajar en nullables. Es solo que se implementan de una manera un poco demasiado indulgente, en comparación con el caso estático. Esto probablemente debería considerarse un error, pero nunca lo arreglaremos, ya que sería un cambio importante. Además, no ayudaría a nadie a endurecer el comportamiento.
¡Ojalá esto explique lo que sucede y por qué! Esta es un área intrigante y, a menudo, me desconciertan las consecuencias de las decisiones que tomamos cuando implementamos Dynamic. Esta pregunta fue deliciosa, ¡gracias por mencionarla!
Mads
fuente
dynamicestá en caja, no podemos distinguir entre unbool?cuálHasValuey un "simple"bool.Sí, estoy bastante seguro de que lo es.
Sección 7.12 de C # Specification Version 5.0, tiene información con respecto a los operadores condicionales
&&y||y cómo unión dinámica se refiere a ellos. La sección relevante:Este es el punto clave que responde a su pregunta, creo. ¿Cuál es la resolución que ocurre en tiempo de ejecución? La sección 7.12.2, Operadores lógicos condicionales definidos por el usuario explica:
En ambos casos, el primer operando x se convertirá en un bool usando los operadores
falseotrue. Entonces se llama al operador lógico apropiado. Con esto en mente, tenemos suficiente información para responder el resto de sus preguntas.Para el
||operador, sabemos que siguetrue(A) ? A : |(A, B). Hacemos un cortocircuito, por lo que no obtendremos una excepción de tiempo obligatorio. Incluso si loAfuerafalse, todavía no obtendríamos una excepción de enlace en tiempo de ejecución, debido a los pasos de resolución especificados. SiAes asífalse, hacemos el|operador, que puede manejar con éxito valores nulos, según la Sección 7.11.4.Por razones similares, este también funciona.
&&se evalúa comofalse(x) ? x : &(x, y).Ase puede convertir correctamente a abool, por lo que no hay ningún problema allí. Debido a queBes nulo, el&operador se eleva (Sección 7.3.7) del que tomaboola uno que toma losbool?parámetros y, por lo tanto, no hay excepción de tiempo de ejecución.Para ambos operadores condicionales, si no
Bes un bool (o una dinámica nula), el enlace en tiempo de ejecución falla porque no puede encontrar una sobrecarga que tome un bool y un no bool como parámetros. Sin embargo, esto solo ocurre siAno se cumple el primer condicional del operador (truepara||,falsepara&&). La razón por la que esto sucede es porque el enlace dinámico es bastante lento. No intentará vincular el operador lógico a menos queAsea falso y tenga que seguir ese camino para evaluar el operador lógico. Una vez queAno cumpla con la primera condición para el operador, fallará con la excepción de vinculación.Con suerte, a estas alturas ya sabes por qué sucede esto (o hice un mal trabajo al explicar). El primer paso para resolver este operador condicional es tomar el primer operando,
By usar uno de los operadores de conversión bool (false(B)otrue(B)) antes de manejar la operación lógica. Por supuesto,Bbeingnullno se puede convertir entrueofalse, por lo que ocurre la excepción de enlace en tiempo de ejecución.fuente
dynamicel enlace ocurra en tiempo de ejecución utilizando los tipos reales de las instancias, no los tipos de tiempo de compilación (su primera cita). Su segunda cita es irrelevante ya que ningún tipo aquí sobrecarga eloperator trueyoperator false. Unexplicit operatorregresobooles algo más queoperator trueyfalse. Es difícil leer la especificación de cualquier manera que lo permitaA && B(en mi ejemplo), sin permitir tambiéna && bdóndeaybson booleanos anulables de tipo estático, es decir ,bool? aybool? b, con enlace en tiempo de compilación. Sin embargo, eso no está permitido.El tipo que acepta valores NULL no define operadores lógicos condicionales || y &&. Te sugiero que sigas el código:
bool a = true; bool? b = null; bool? xxxxOR = (b.HasValue == true) ? (b.Value || a) : a; bool? xxxxAND = (b.HasValue == true) ? (b.Value && a) : false;fuente