valores valor validar tipo tener que operador objeto net diferente debe dato comprobar acepta c# nullable logical-operators short-circuiting dynamictype

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 operadores true o false (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 de x e y (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