J'ai lu la spécification du langage C # sur les opérateurs logiques conditionnels ||
et &&
, également connus sous le nom d'opérateurs logiques de court-circuit. Pour moi, il ne semblait pas clair si ceux-ci existaient pour les booléens nullables, c'est-à-dire le type d'opérande Nullable<bool>
(également écrit bool?
), alors je l'ai essayé avec un typage non dynamique:
bool a = true;
bool? b = null;
bool? xxxx = b || a; // compile-time error, || can't be applied to these types
Cela semblait régler la question (je ne pouvais pas comprendre clairement la spécification, mais en supposant que l'implémentation du compilateur Visual C # était correcte, je le savais maintenant).
Cependant, je voulais aussi essayer la dynamic
reliure. J'ai donc essayé ceci à la place:
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);
}
}
Le résultat surprenant est que cela fonctionne sans exception.
Eh bien, x
et ce y
n'est pas surprenant, leurs déclarations conduisent à la récupération des deux propriétés et les valeurs résultantes sont comme prévu, x
est true
et y
est null
.
Mais l'évaluation de xx
of A || B
conduit à aucune exception de temps de liaison, et seule la propriété a A
été lue, non B
. Pourquoi cela arrive-t-il? Comme vous pouvez le voir, nous pourrions changer le B
getter pour renvoyer un objet fou, comme "Hello world"
, et xx
nous évaluerions toujours true
sans problèmes de liaison ...
L'évaluation A && B
(pour yy
) ne conduit également à aucune erreur de temps de liaison. Et ici, les deux propriétés sont récupérées, bien sûr. Pourquoi est-ce autorisé par le classeur d'exécution? Si l'objet renvoyé de B
est changé en un objet "incorrect" (comme a string
), une exception de liaison se produit.
Ce comportement est-il correct? (Comment pouvez-vous déduire cela de la spécification?)
Si vous essayez en B
tant que premier opérande, les deux B || A
et B && A
donnent une exception de reliure d'exécution ( B | A
et B & A
fonctionnent correctement car tout est normal avec des opérateurs sans court-circuit |
et &
).
(Essayé avec le compilateur C # de Visual Studio 2013 et la version d'exécution .NET 4.5.2.)
la source
Nullable<Boolean>
impliqué du tout, seuls les booléens encadrés sont traités commedynamic
- votre test avecbool?
n'est pas pertinent. (Bien sûr, ce n'est pas une réponse complète, seulement le germe d'une.)A || B
fait un certain sens, en ce que vous ne voulez pas évaluer àB
moins que ceA
soit faux, ce qui n'est pas le cas. Donc, vous ne connaissez jamais vraiment le type de l'expression. LaA && B
version est plus surprenante - je vais voir ce que je peux trouver dans les spécifications.A
estbool
et la valeur deB
estnull
, alors unbool && bool?
opérateur pourrait être impliqué.&&
parle de le résoudre comme s'il l'était à la&
place, et inclut spécifiquement le cas où les deux opérandes sontbool?
- mais la section suivante à laquelle elle fait référence ne gère pas le cas Nullable. Je pourrais ajouter une sorte de réponse plus détaillée à ce sujet, mais cela ne l'expliquerait pas complètement.Réponses:
Tout d'abord, merci de souligner que la spécification n'est pas claire sur le cas nullable-bool non dynamique. Je corrigerai cela dans une prochaine version. Le comportement du compilateur est le comportement prévu;
&&
et||
ne sont pas censés fonctionner sur les bools nullables.Le classeur dynamique ne semble pas implémenter cette restriction, cependant. Au lieu de cela, il lie les opérations des composants séparément: le
&
/|
et le?:
. Ainsi, il est capable de se débrouiller si le premier opérande se trouve êtretrue
oufalse
(qui sont des valeurs booléennes et donc autorisées comme premier opérande de?:
), mais si vous donneznull
comme premier opérande (par exemple si vous essayezB && A
dans l'exemple ci-dessus), vous faites obtenir une exception de liaison d'exécution.Si vous y réfléchissez, vous pouvez voir pourquoi nous avons implémenté dynamique
&&
et de||
cette façon au lieu de comme une grande opération dynamique: les opérations dynamiques sont liées au moment de l' exécution après que leurs opérandes sont évalués , de sorte que la liaison puisse être basée sur les types d'exécution des résultats de ces évaluations. Mais une évaluation aussi enthousiaste va à l'encontre de l'objectif de court-circuiter les opérateurs! Donc, à la place, le code généré pour dynamique&&
et||
divise l'évaluation en morceaux et se déroulera comme suit:x
)bool
conversion implicite via, ou les opérateurstrue
oufalse
(échouer si impossible)x
comme condition dans une?:
opérationx
comme résultaty
)&
ou en|
fonction du type d'exécution dex
ety
(échoue si impossible)C'est le comportement qui laisse passer certaines combinaisons "illégales" d'opérandes: l'
?:
opérateur traite avec succès le premier opérande comme un booléen non nullable , l' opérateur&
ou le|
traite avec succès comme un booléen nullable , et les deux ne se coordonnent jamais pour vérifier qu'ils sont d'accord .Donc ce n'est pas si dynamique && et || travailler sur les nullables. C'est juste qu'ils se trouvent être implémentés d'une manière un peu trop clémente, par rapport au cas statique. Cela devrait probablement être considéré comme un bogue, mais nous ne le corrigerons jamais, car ce serait un changement radical. De plus, cela n'aiderait guère personne à resserrer le comportement.
Espérons que cela explique ce qui se passe et pourquoi! C'est un domaine intriguant, et je me trouve souvent déconcerté par les conséquences des décisions que nous avons prises lorsque nous avons mis en place une dynamique. Cette question était délicieuse - merci de l'avoir soulevée!
Mads
la source
dynamic
est encadré, nous ne pouvons pas faire la différence entre unbool?
quiHasValue
et un "simple"bool
.Oui, j'en suis presque sûr.
Section 7.12 de C # Spécification Version 5.0, contient des informations concernant les opérateurs conditionnels
&&
et||
et la façon dont la liaison dynamique se rapporte à eux. La section pertinente:C'est le point clé qui répond à votre question, je pense. Quelle est la résolution qui se produit au moment de l'exécution? La section 7.12.2, Opérateurs logiques conditionnels définis par l'utilisateur explique:
Dans les deux cas, le premier opérande x sera converti en booléen à l'aide des opérateurs
false
outrue
. Ensuite, l'opérateur logique approprié est appelé. Dans cet esprit, nous avons suffisamment d'informations pour répondre au reste de vos questions.Pour l'
||
opérateur, nous savons que cela suittrue(A) ? A : |(A, B)
. Nous court-circuitons, donc nous n'obtiendrons pas d'exception de temps contraignante. Même siA
c'était le casfalse
, nous n'obtiendrions toujours pas d'exception de liaison d'exécution, en raison des étapes de résolution spécifiées. SiA
c'est le casfalse
, nous faisons alors l'|
opérateur, qui peut gérer avec succès les valeurs nulles, conformément à la section 7.11.4.Pour des raisons similaires, celui-ci fonctionne également.
&&
est évalué commefalse(x) ? x : &(x, y)
.A
peut être converti avec succès en abool
, il n'y a donc pas de problème. Comme ilB
est nul, l'&
opérateur est levé (section 7.3.7) de celui qui prend abool
à celui qui prend lesbool?
paramètres, et il n'y a donc aucune exception d'exécution.Pour les deux opérateurs conditionnels, s'il
B
s'agit d'autre chose qu'un bool (ou une dynamique nulle), la liaison d'exécution échoue car elle ne peut pas trouver une surcharge qui prend un bool et un non-bool comme paramètres. Cependant, cela ne se produit que siA
ne satisfait pas la première condition de l'opérateur (true
pour||
,false
pour&&
). La raison pour laquelle cela se produit est que la liaison dynamique est assez paresseuse. Il n'essaiera pas de lier l'opérateur logique à moins que ceA
soit faux et il doit emprunter ce chemin pour évaluer l'opérateur logique. Une foisA
échoue à satisfaire la première condition de l'opérateur, elle échouera avec l'exception de liaison.J'espère que vous savez déjà pourquoi cela se produit (ou j'ai fait un mauvais travail en expliquant). La première étape de la résolution de cet opérateur conditionnel consiste à prendre le premier opérande
B
, et à utiliser l'un des opérateurs de conversion booléenne (false(B)
outrue(B)
) avant de traiter l'opération logique. Bien sûr,B
êtrenull
ne peut pas être converti entrue
oufalse
, et donc l'exception de liaison d'exécution se produit.la source
dynamic
la liaison se produise au moment de l'exécution en utilisant les types réels des instances, pas les types au moment de la compilation (votre premier devis). Votre deuxième citation n'est pas pertinente car aucun type ici ne surcharge leoperator true
etoperator false
. Unexplicit operator
retourbool
est autre chose queoperator true
etfalse
. Il est difficile de lire la spécification de quelque manière que ce soit qui le permetA && B
(dans mon exemple), sans également autorisera && b
où lesa
etb
sont statiquement typés booléens nullables, c'estbool? a
-à- dire etbool? b
, avec liaison au moment de la compilation. Pourtant, cela est interdit.Le type Nullable ne définit pas d'opérateurs logiques conditionnels || et &&. Je vous suggère le code suivant:
bool a = true; bool? b = null; bool? xxxxOR = (b.HasValue == true) ? (b.Value || a) : a; bool? xxxxAND = (b.HasValue == true) ? (b.Value && a) : false;
la source