c# - valor - Hacer operadores de cortocircuito || y && existen para booleanos anulables? The RuntimeBinder a veces piensa que sí
tipo de dato null (3)
Leí la especificación del lenguaje C # en los
operadores lógicos condicionales
||
y
&&
, también conocidos como operadores lógicos de cortocircuito.
Para mí, no estaba claro si estos existían para booleanos anulables, es decir, el tipo de operando
Nullable<bool>
(también escrito
bool?
), Así que lo probé con una 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 podía entender la especificación claramente, pero suponiendo que la implementación del compilador de Visual C # era correcta, ahora lo sabía).
Sin embargo, también quería probar con enlace
dynamic
.
Así que intenté 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 se ejecuta sin excepción.
Bueno,
x
no son sorprendentes, sus declaraciones conducen a la recuperación de ambas propiedades, y los valores resultantes son los esperados,
x
es
true
y
es
null
.
Pero la evaluación para
xx
de
A || B
A || B
conduce a ninguna excepción de tiempo de vinculación, y solo se leyó la propiedad
A
, no
B
¿Por qué pasó esto?
Como puede ver, podríamos cambiar el captador
B
para que devuelva un objeto loco, como
"Hello world"
, y
xx
aún se evaluaría como
true
sin problemas de enlace ...
Evaluar
A && B
(para
yy
) tampoco conduce a ningún error de tiempo de enlace.
Y aquí se recuperan ambas propiedades, por supuesto.
¿Por qué está permitido por la carpeta de tiempo de ejecución?
Si el objeto devuelto de
B
se cambia a un objeto "malo" (como una
string
), se produce una excepción de enlace.
¿Es este el comportamiento correcto? (¿Cómo puedes inferir eso de la especificación?)
Si prueba
B
como primer operando, ambos
B || A
B || A
y
B && A
y
B && A
dan
B && A
excepción de carpeta de tiempo de ejecución (
B | A
y
B & A
funcionan bien, ya que todo es normal con operadores que no están en cortocircuito
|
y
&
).
(Probado con el compilador C # de Visual Studio 2013 y la versión de ejecución .NET 4.5.2.)
¿Es este el comportamiento correcto?
Sí, estoy bastante seguro de que es así.
¿Cómo puedes inferir eso de la especificación?
La sección 7.12 de la versión 5.0 de la especificación de C # contiene información sobre los operadores condicionales
&&
y
||
y cómo se relaciona el enlace dinámico con ellos.
La sección relevante:
Si un operando de un operador lógico condicional tiene el tipo dinámico en tiempo de compilación, entonces la expresión está vinculada dinámicamente (§7.2.2). En este caso, el tipo de tiempo de compilación de la expresión es dinámico, y la resolución que se describe a continuación tendrá lugar en tiempo de ejecución utilizando el tipo de tiempo de ejecución de aquellos operandos que tienen el tipo de tiempo de compilación dinámico.
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:
- La operación x && y se evalúa como T.false (x)? x: T. & (x, y), donde T.false (x) es una invocación del operador falso declarado en T, y T. & (x, y) es una invocación del operador seleccionado &
- La operación x || y se evalúa como T. verdadero (x)? x: T. | (x, y), donde T.true (x) es una invocación del operador verdadero declarado en T, y T. | (x, y) es una invocación del operador seleccionado |.
En ambos casos, el primer operando x se convertirá en bool utilizando los operadores
false
o
true
.
Entonces se llama al operador lógico apropiado.
Con esto en mente, tenemos suficiente información para responder el resto de sus preguntas.
Pero la evaluación para xx de A || B no conduce a ninguna excepción de tiempo vinculante, y solo se leyó la propiedad A, no B. ¿Por qué sucede esto?
Para el
||
operador, sabemos que sigue
true(A) ? A : |(A, B)
true(A) ? A : |(A, B)
.
Cortocircuitamos, por lo que no obtendremos una excepción de tiempo vinculante.
Incluso si
A
fuera
false
,
aún
no obtendríamos una excepción de enlace de tiempo de ejecución, debido a los pasos de resolución especificados.
Si
A
es
false
, entonces hacemos el
|
operador, que puede manejar con éxito valores nulos, según la Sección 7.11.4.
Evaluar A && B (para yy) tampoco conduce a ningún error de tiempo de enlace. Y aquí se recuperan ambas propiedades, por supuesto. ¿Por qué está permitido por la carpeta de tiempo de ejecución? Si el objeto devuelto de B se cambia a un objeto "malo" (como una cadena), se produce una excepción de enlace.
Por razones similares, este también funciona.
&&
se evalúa como
false(x) ? x : &(x, y)
false(x) ? x : &(x, y)
.
A
se puede convertir con éxito en un
bool
, por lo que no hay ningún problema allí.
Debido a que
B
es nulo, el operador
&
se levanta (Sección 7.3.7) del que toma un
bool
a uno que toma el
bool?
parámetros, y por lo tanto no hay excepción de tiempo de ejecución.
Para ambos operadores condicionales, si
B
es un bool (o una dinámica nula), el enlace de 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 sucede si
A
no cumple con el primer condicional para el operador (
true
para
||
,
false
para
&&
).
La razón por la que esto sucede es porque el enlace dinámico es bastante vago.
No intentará vincular el operador lógico a menos que
A
sea falso y tenga que seguir ese camino para evaluar el operador lógico.
Una vez que
A
no cumple la primera condición para el operador, fallará con la excepción vinculante.
Si prueba B como primer operando, ambos B || A y B y A dan una excepción de carpeta de tiempo de ejecución.
Con suerte, a estas alturas, ya sabes por qué sucede esto (o hice un mal trabajo explicando).
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)
o
true(B)
) antes de manejar la operación lógica.
Por supuesto,
B
, al ser
null
, no se puede convertir en
true
o
false
, por lo que se produce la excepción de enlace de tiempo de ejecución.
El tipo anulable no define operadores lógicos condicionales || y &&. Te sugiero el siguiente 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;
En primer lugar, gracias por señalar que la especificación no está clara en el caso no dinámico de nullable-bool.
Lo arreglaré en una versión futura.
El comportamiento del compilador es el comportamiento previsto;
&&
y
||
no se supone que trabajen en bools anulables.
Sin embargo, la carpeta dinámica no parece implementar esta restricción.
En cambio, vincula las operaciones de componentes por separado: el
&
/
|
y el
?:
.
Por lo tanto, puede confundirse si el primer operando resulta ser
true
o
false
(que son valores booleanos y, por lo tanto, se permiten como el primer operando de
?:
:), Pero si da
null
como el primer operando (por ejemplo, si intenta
B && A
en el ejemplo anterior), obtienes una excepción de enlace de tiempo de ejecución.
Si lo piensa, puede ver por qué implementamos dinámico
&&
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 evaluar sus operandos
, de modo que el enlace puede basarse en los tipos de tiempo de ejecución de los resultados de esas evaluaciones.
¡Pero una evaluación tan entusiasta anula el propósito de los operadores de cortocircuito!
Entonces, en cambio, el código generado para dinámico
&&
y
||
divide la evaluación en pedazos y procederá de la siguiente manera:
-
Evalúe el operando izquierdo (llamemos al resultado
x
) -
Intente convertirlo en un
bool
mediante conversión implícita, o los operadorestrue
ofalse
(fallar si no puede) -
Utilice
x
como condición en una operación?:
-
En la rama verdadera, use
x
como resultado -
En la rama falsa,
ahora
evalúe el segundo operando (llamemos al resultado
y
) -
Intenta vincular el
&
o|
operador basado en el tipo de tiempo de ejecución dex
ey
(falla si no puede) - Aplicar el operador seleccionado
Este es el comportamiento que permite ciertas combinaciones "ilegales" de operandos: el operador
?:
Trata con éxito el primer operando como un booleano
no anulable
, el
&
o
|
El operador lo trata con éxito como un booleano
anulable
, y los dos nunca se coordinan para verificar que estén de acuerdo.
Entonces no es tan dinámico && y || trabajar en nulables. Es solo que están implementados de una manera 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 radical. Además, difícilmente ayudaría a nadie a endurecer el comportamiento.
¡Ojalá esto explique qué sucede y por qué! Esta es un área intrigante, y a menudo me encuentro desconcertado por las consecuencias de las decisiones que tomamos cuando implementamos la dinámica. Esta pregunta fue deliciosa, ¡gracias por mencionarla!
Locos