¿Existen operadores de cortocircuito || y && para booleanos nullables? El RuntimeBinder a veces piensa así


He leído la Especificación del lenguaje C # en los operadores lógicos condicionales || y &&, también conocidos como los operadores lógicos de cortocircuito. Para mí no parecía claro si estos existían para booleanos nullables, es decir, el tipo de operando Nullable<bool> (también escrito bool?), así que lo intenté con el tipo no dinámico:

bool a = true;
bool? b = null;
bool? xxxx = b || a;  // compile-time error, || can't be applied to these types

Eso parecía resolver la cuestión (no podía entender la especificación claramente, pero asumiendo que la implementación del compilador Visual de C# era correcta, ahora lo sabía).

Sin embargo, quería probar con dynamic enlace tambié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 son sorprendentes, sus declaraciones conducen a recuperar ambas propiedades, y los valores resultantes son los esperados, x es true y y es null.

Pero la evaluación para xx de A || B no conduce a ninguna excepción de tiempo de enlace, y solo la propiedad A fue read, not B. ¿Por qué sucede esto? Como se puede decir, podríamos cambiar el B getter para devolver un objeto loco, como "Hello world", y xx todavía evaluaría a true sin problemas de unión...

Evaluar A && B (para yy) también conduce a ningún error de tiempo de unión. Y aquí se recuperan ambas propiedades, por supuesto. ¿Por qué está permitido esto por la carpeta de tiempo de ejecución? Si el objeto devuelto desde B se cambia a un objeto "malo" (como un string), una excepción vinculante ocurrir.

Es este comportamiento correcto? (¿Cómo puedes inferir eso de la especificación?)

Si intenta B como primer operando, tanto B || A como B && A dan la excepción de binder en tiempo de ejecución (B | A y B & A funcionan bien ya que todo es normal con operadores que no cortocircuitan | y &).

(Probado con el compilador C# de Visual Studio 2013, y la versión de tiempo de ejecución.NET 4.5.2.)

Author: Jeppe Stig Nielsen, 2014-12-16

3 answers

En primer lugar, gracias por señalar que la especificación no está clara en el caso nullable-bool no dinámico. Lo arreglaré en una versión futura. El comportamiento del compilador es el comportamiento deseado; && y || no se supone que funcionen en bools nullables.

El aglutinante dinámico no parece implementar esta restricción, sin embargo. En su lugar, enlaza las operaciones del componente por separado:&/| y el ?:. Por lo tanto es capaz de salirse con la suya si el primer operando pasa a ser true o false (que son valores booleanos y por lo tanto permitidos como el primer operando de ?:), pero si se da null como el primer operando (por ejemplo, si se intenta B && A en el ejemplo anterior), se obtiene una excepción de enlace en tiempo de ejecución.

Si lo piensa, puede ver por qué implementamos dynamic && 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 sus operandos se evalúan, de modo que el enlace puede basarse en el tiempo de ejecución tipos de resultados de esas evaluaciones. Pero tal evaluación impaciente derrota el propósito de los operadores de cortocircuito! Así que en su lugar, el código generado para dynamic && y || divide la evaluación en pedazos y procederá de la siguiente manera:

  • Evaluar el operando izquierdo (vamos a llamar al resultado x)
  • Intenta convertirlo en un bool mediante conversión implícita, o los operadores true o false (fail if unable)
  • Use x como la condición en un ?: operación
  • En la rama true, use x como resultado
  • En la rama false, ahora evalúe el segundo operando (llamemos al resultado y)
  • Intenta enlazar el operador & o | basado en el tipo de tiempo de ejecución de x y y (fail if unable)
  • Aplicar el operador seleccionado

Este es el comportamiento que permite a través de ciertas combinaciones "ilegales" de operandos: el operador ?: trata con éxito el primer operando como un no nullable booleano, el operador & o | lo trata con éxito como un nullable booleano, y los dos nunca se coordinan para verificar que están de acuerdo.

Así que no es tan dinámico && y || trabajar en nullables. Es solo que sucede que se implementan de una manera que es 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 radical. También difícilmente ayudaría cualquier persona para endurecer el comportamiento.

Esperemos que esto explique lo que sucede y por qué! Este es un área intrigante, y a menudo me encuentro desconcertado por las consecuencias de las decisiones que tomamos cuando implementamos dynamic. Esta pregunta fue deliciosa, ¡gracias por mencionarla!

Mads

 65
Author: Mads Torgersen - MSFT,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-17 18:56:56

Es este comportamiento correcto?

Sí, estoy bastante seguro de que lo es.

¿Cómo puede inferir eso de la especificación?

La sección 7.12 de la Especificación de C# Versión 5.0, tiene información sobre los operadores condicionales && y || y cómo se relaciona el enlace dinámico con ellos. The relevant section:

Si un operando de un operador lógico condicional tiene el tipo de tiempo de compilación dinámico, entonces la expresión está enlazada 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 descrita a continuación tendrá lugar en tiempo de ejecución utilizando el tipo de tiempo de ejecución de los 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 e y se evalúa como T. false (x)? x : T.&(x, y), donde T. falso(x) es una invocación del operador declarado falso en T y T.&(x, y) es una invocación de la empresa seleccionada &
  • La operación x || y se evalúa como T. true(x) ? x: T.|(x, y), donde T. true(x) es una invocación del operador true declarado en T, y T. |(x, y) es una invocación del operador seleccionado/.

En ambos casos, el primer operando x se convertirá en un bool usando 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 de xx de A || B no conduce a ninguna excepción de tiempo de enlace, y solo se leyó la propiedad A, no B. ¿Por qué sucede esto?

Para el operador ||, sabemos que sigue true(A) ? A : |(A, B). Cortocircuitamos, así que no obtendremos una excepción de tiempo vinculante. Incluso si A fuera false, todavía no obtendríamos un enlace de tiempo de ejecución excepción, debido a los pasos de resolución especificados. Si A es false, entonces hacemos el operador |, que puede manejar con éxito los valores nulos, según la Sección 7.11.4.

La evaluación de A && B (para yy) también conduce a ningún error de tiempo de enlace. Y aquí se recuperan ambas propiedades, por supuesto. ¿Por qué está permitido esto 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). A se puede convertir con éxito a un bool, por lo que no hay ningún problema allí. Debido a que B es null, el operador & se eleva (Sección 7.3.7) del que toma un bool a uno que toma los parámetros bool?, y por lo tanto no hay excepción de tiempo de ejecución.

Para ambos operadores condicionales, si B es cualquier cosa que no sea 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 sucede si A no cumple 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 perezoso. No intentará enlazar el operador lógico a menos que A sea false y tenga que ir por 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 intenta B como primero operando, tanto B / / A y B && A dan excepción de binder en 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, siendo null no se puede convertir a true o false, por lo que ocurre la excepción de enlace en tiempo de ejecución.

 6
Author: Christopher Currens,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-17 09:16:45

El tipo Nullable no define los 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;
 -1
Author: Thomas Papamihos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-24 20:33:35