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 dynamic
encuadernació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, x
y y
no es de extrañar, sus declaraciones llevan a que se recuperen ambas propiedades, y los valores resultantes son los esperados, x
es true
y y
es null
.
Pero la evaluación xx
de no A || B
condujo a ninguna excepción de tiempo de vinculación, y solo A
se leyó la propiedad , no B
. ¿Por qué pasó esto? Como puede ver, podríamos cambiar el B
getter para devolver un objeto loco, como "Hello world"
, y xx
aún evaluaríamos true
sin 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 B
se 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 B
como primer operando, ambos B || A
y B && A
dan una excepción del enlazador en tiempo de ejecución ( B | A
y B & A
funcionan 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 || B
hace que una cierta cantidad de sentido, en la que no se quiere evaluarB
a menosA
que es falso, que no lo es. Así que nunca se sabe el tipo de expresión. LaA && B
versión es más sorprendente: veré qué puedo encontrar en la especificación.A
esbool
y el valor deB
esnull
, 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 estrue
ofalse
(que son valores booleanos y, por lo tanto, se permiten como el primer operando de?:
), pero si danull
como primer operando (por ejemplo, si lo intentaB && A
en 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
)bool
conversión implícita o en los operadorestrue
ofalse
(fallar si no puede)x
como condición en una?:
operaciónx
como resultadoy
)&
or|
según el tipo de tiempo de ejecución dex
yy
(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
dynamic
está en caja, no podemos distinguir entre unbool?
cuálHasValue
y 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
false
otrue
. 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 loA
fuerafalse
, todavía no obtendríamos una excepción de enlace en tiempo de ejecución, debido a los pasos de resolución especificados. SiA
es 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)
.A
se puede convertir correctamente a abool
, por lo que no hay ningún problema allí. Debido a queB
es nulo, el&
operador se eleva (Sección 7.3.7) del que tomabool
a 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
B
es 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 siA
no se cumple el primer condicional del operador (true
para||
,false
para&&
). 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 queA
sea falso y tenga que seguir ese camino para evaluar el operador lógico. Una vez queA
no 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,
B
y usar uno de los operadores de conversión bool (false(B)
otrue(B)
) antes de manejar la operación lógica. Por supuesto,B
beingnull
no se puede convertir entrue
ofalse
, por lo que ocurre la excepción de enlace en tiempo de ejecución.fuente
dynamic
el 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 true
yoperator false
. Unexplicit operator
regresobool
es algo más queoperator true
yfalse
. Es difícil leer la especificación de cualquier manera que lo permitaA && B
(en mi ejemplo), sin permitir tambiéna && b
dóndea
yb
son booleanos anulables de tipo estático, es decir ,bool? a
ybool? 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