Ich habe die C # -Sprachenspezifikation für die bedingten logischen Operatoren gelesen ||
und &&
auch als kurzschließende logische Operatoren bezeichnet. Für mich schien es unklar, ob diese für nullfähige Boolesche Werte existieren, dh für den Operandentyp Nullable<bool>
(ebenfalls geschrieben bool?
), also habe ich es mit nicht dynamischer Typisierung versucht:
bool a = true;
bool? b = null;
bool? xxxx = b || a; // compile-time error, || can't be applied to these types
Das schien die Frage zu klären (ich konnte die Spezifikation nicht klar verstehen, aber vorausgesetzt, die Implementierung des Visual C # -Compilers war korrekt, jetzt wusste ich es).
Ich wollte es aber auch mit dynamic
Bindung versuchen . Also habe ich stattdessen versucht:
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);
}
}
Das überraschende Ergebnis ist, dass dies ausnahmslos läuft.
Nun, x
und y
es ist nicht überraschend, dass ihre Deklarationen dazu führen, dass beide Eigenschaften abgerufen werden und die resultierenden Werte wie erwartet sind, x
sind true
und y
sind null
.
Aber die Auswertung für xx
von A || B
Blei zu keiner Bindung einmaligen Ausnahme und nur die Eigenschaft A
gelesen wurden, nicht B
. Warum passiert das? Wie Sie sehen können, könnten wir den B
Getter so ändern , dass er ein verrücktes Objekt zurückgibt "Hello world"
, und xx
würden es trotzdem true
ohne Bindungsprobleme auswerten ...
Das Auswerten A && B
(für yy
) führt auch zu keinem Bindungszeitfehler. Und hier werden natürlich beide Eigenschaften abgerufen. Warum ist dies durch den Laufzeitbinder zulässig? Wenn das zurückgegebene Objekt von B
in ein "fehlerhaftes" Objekt (wie a string
) geändert wird , tritt eine verbindliche Ausnahme auf.
Ist das richtig? (Wie können Sie das aus der Spezifikation ableiten?)
Wenn Sie es B
als ersten Operanden versuchen , geben Sie beide aus B || A
und B && A
geben eine Laufzeitbinder-Ausnahme aus ( B | A
und B & A
funktionieren einwandfrei, da bei nicht kurzgeschlossenen Operatoren |
und normal alles normal ist &
).
(Versucht mit dem C # -Compiler von Visual Studio 2013 und der Laufzeitversion .NET 4.5.2.)
quelle
Nullable<Boolean>
Beteiligung, nur Boxed Booleans, die als behandelt werdendynamic
- Ihr Test mitbool?
ist irrelevant. (Natürlich ist dies keine vollständige Antwort, nur der Keim von einem.)A || B
macht insofern einen gewissen Sinn, als Sie nicht bewerten wollen,B
esA
sei denn, es ist falsch, was es nicht ist. Sie kennen also nie wirklich die Art des Ausdrucks. DieA && B
Version ist überraschender - ich werde sehen, was ich in der Spezifikation finden kann.A
isbool
und der Wert vonB
isnull
sindbool && bool?
.&&
Gespräche über die Lösung, als ob sie&
stattdessen wäre, enthält speziell den Fall, in dem sich beide Operanden befindenbool?
- aber der nächste Abschnitt, auf den sie verweist, behandelt den nullbaren Fall nicht. Ich könnte eine Art Antwort hinzufügen, die detaillierter darauf eingeht, aber es würde es nicht vollständig erklären.Antworten:
Zunächst einmal vielen Dank, dass Sie darauf hingewiesen haben, dass die Spezifikation für den nicht dynamischen Fall nullable-bool nicht klar ist. Ich werde das in einer zukünftigen Version beheben. Das Verhalten des Compilers ist das beabsichtigte Verhalten.
&&
und||
sollen nicht auf nullable bools arbeiten.Der dynamische Binder scheint diese Einschränkung jedoch nicht zu implementieren. Stattdessen werden die Komponentenoperationen separat gebunden: das
&
/|
und das?:
. Auf diese Weise kann es durcheinander kommen, ob der erste Operand zufälligtrue
oder istfalse
(was boolesche Werte sind und somit als erster Operand von zulässig sind?:
), aber wenn Sienull
als ersten Operanden angeben (z. B. wenn Sie esB && A
im obigen Beispiel versuchen ), tun Sie dies Holen Sie sich eine Laufzeitbindungsausnahme.Wenn Sie darüber nachdenken, können Sie sehen, warum wir dynamisch
&&
und auf||
diese Weise anstatt als eine große dynamische Operation implementiert haben : Dynamische Operationen werden zur Laufzeit gebunden, nachdem ihre Operanden ausgewertet wurden , sodass die Bindung auf den Laufzeittypen der Ergebnisse basieren kann dieser Bewertungen. Eine solche eifrige Bewertung macht jedoch den Zweck des Kurzschlusses von Betreibern zunichte! Stattdessen generiert der generierte Code für dynamische&&
und||
die Auswertung in Teile und wird wie folgt vorgehen:x
)bool
implizite Konvertierung oder die Operatorentrue
oder zu machenfalse
(fehlgeschlagen, wenn dies nicht möglich ist).x
als Bedingung in einer?:
Operationx
als Ergebnisy
).&
oder|
basierend auf dem Laufzeittyp vonx
und zu bindeny
(fehlgeschlagen, wenn dies nicht möglich ist).Dies ist das Verhalten, das bestimmte "illegale" Kombinationen von Operanden durchlässt: Der
?:
Operator behandelt den ersten Operanden erfolgreich als nicht nullbaren Booleschen Wert , der Operator&
oder|
behandelt ihn erfolgreich als nullbaren Booleschen Wert , und die beiden koordinieren niemals, um zu überprüfen, ob sie übereinstimmen .Es ist also nicht so dynamisch && und || Arbeit an Nullables. Es ist nur so, dass sie auf eine Weise implementiert werden, die im Vergleich zum statischen Fall etwas zu nachsichtig ist. Dies sollte wahrscheinlich als Fehler angesehen werden, aber wir werden ihn niemals beheben, da dies eine bahnbrechende Änderung wäre. Auch würde es kaum jemandem helfen, das Verhalten zu straffen.
Hoffentlich erklärt dies, was passiert und warum! Dies ist ein faszinierender Bereich, und ich bin oft verblüfft über die Konsequenzen der Entscheidungen, die wir bei der Implementierung von Dynamic getroffen haben. Diese Frage war köstlich - danke, dass Sie sie angesprochen haben!
Mads
quelle
dynamic
verpackt ist, können wir natürlich nicht den Unterschied zwischen einembool?
WasHasValue
und einem "Einfachen" erkennenbool
.Ja, ich bin mir ziemlich sicher, dass es so ist.
Abschnitt 7.12 von C # Spezifikation Version 5.0, hat Informationen zu den Vergleichsoperator
&&
und||
und wie dynamische Bindung an sie betrifft. Der relevante Abschnitt:Dies ist der entscheidende Punkt, der Ihre Frage beantwortet, denke ich. Was ist die Auflösung, die zur Laufzeit passiert? In Abschnitt 7.12.2, Benutzerdefinierte bedingte logische Operatoren wird Folgendes erläutert:
In beiden Fällen wird der erste Operand x mit den Operatoren
false
oder in einen Bool konvertierttrue
. Dann wird der entsprechende logische Operator aufgerufen. In diesem Sinne verfügen wir über genügend Informationen, um den Rest Ihrer Fragen zu beantworten.Für den
||
Betreiber wissen wir, dass es folgttrue(A) ? A : |(A, B)
. Wir schließen kurz, sodass wir keine verbindliche Zeitausnahme erhalten. Auch wennA
istfalse
, würden wir noch bekommen nicht eine Laufzeitausnahme zu binden, weil der angegebenen Auflösungsstufe. Wenn dies der FallA
istfalse
, führen wir den|
Operator aus, der erfolgreich Nullwerte verarbeiten kann, gemäß Abschnitt 7.11.4.Aus ähnlichen Gründen funktioniert dieser auch.
&&
wird bewertet alsfalse(x) ? x : &(x, y)
.A
kann erfolgreich in a konvertiert werdenbool
, daher gibt es dort kein Problem. DaB
null ist, wird der&
Operator (Abschnitt 7.3.7) von dem Operator, der a nimmt,bool
zu einem Operator , der diebool?
Parameter übernimmt , aufgehoben , und daher gibt es keine Laufzeitausnahme.Wenn es sich bei beiden bedingten Operatoren
B
um etwas anderes als einen Bool (oder eine Nulldynamik) handelt, schlägt die Laufzeitbindung fehl, da keine Überladung gefunden werden kann, die einen Bool und einen Nicht-Bool als Parameter verwendet. Dies geschieht jedoch nur, wennA
die erste Bedingung für den Bediener (true
für||
,false
für&&
) nicht erfüllt ist . Der Grund dafür ist, dass die dynamische Bindung ziemlich faul ist. Es wird nicht versucht, den logischen Operator zu binden, esA
sei denn, es ist falsch und es muss diesen Pfad gehen, um den logischen Operator auszuwerten. WennA
die erste Bedingung für den Bediener nicht erfüllt ist, schlägt sie mit der Bindungsausnahme fehl.Hoffentlich wissen Sie jetzt bereits, warum dies passiert (oder ich habe es schlecht erklärt). Der erste Schritt beim Auflösen dieses bedingten Operators besteht darin, den ersten Operanden zu verwenden
B
und einen der Bool-Konvertierungsoperatoren (false(B)
odertrue(B)
) zu verwenden, bevor die logische Operation ausgeführt wird. NatürlichB
kann Seinnull
nicht in entwedertrue
oder konvertiert werdenfalse
, und daher tritt die Laufzeitbindungsausnahme auf.quelle
dynamic
die Bindung zur Laufzeit unter Verwendung der tatsächlichen Typen der Instanzen erfolgt, nicht der Typen zur Kompilierungszeit (Ihr erstes Zitat). Ihr zweites Zitat ist irrelevant, da hier kein Typ dasoperator true
und überlädtoperator false
. Eineexplicit operator
Rückkehrbool
ist etwas anderes alsoperator true
undfalse
. Es ist schwierig, die Spezifikation so zu lesen, wie esA && B
(in meinem Beispiel) erlaubt , ohne auch zuzulassen,a && b
wo diea
undb
statisch typisierten nullbaren Booleschen Werte sind, dhbool? a
undbool? b
mit Bindung zur Kompilierungszeit. Dies ist jedoch nicht zulässig.Der Typ Nullable definiert keine bedingten logischen Operatoren || und &&. Ich schlage Ihnen folgenden Code vor:
quelle