Ich glaube, dass C # die Auswertung einer if-Anweisungsbedingung beendet, sobald es in der Lage ist, das Ergebnis zu ermitteln. Also zum Beispiel:
if ( (1 < 0) && check_something_else() )
// this will not be called
Da die Bedingung (1 < 0)
als ausgewertet wird false
, kann die &&
Bedingung nicht erfüllt werden und check_something_else()
wird nicht aufgerufen.
Wie wertet C # eine if-Anweisung mit asynchronen Funktionen aus? Wartet es, bis beide zurückkehren? Also zum Beispiel:
if( await first_check() && await second_check() )
// ???
Wird dies jemals kurzgeschlossen werden?
if
nochawait
Kurzschluss beeinflussen.async
Beeinflussen Sie nicht das Verhalten der Sprache, außer dass Sieawait
die Verwendung zulassen und darauf warten, dass bereits asynchrone Vorgänge ausgeführt werden, ohne sie zu blockieren.Operator &&
Dokumentation hat Sie zu der Annahme gebracht, dass ein Kurzschluss jemals übersprungen werden kann?if
.&&
und||
führen Sie einen Kurzschluss durch, egal wo sie verwendet werden, z. B.some_var = <expression1> && <expression2>
Antworten:
Dies ist super einfach zu überprüfen.
Versuchen Sie diesen Code:
async Task Main() { if (await first_check() && await second_check()) { Console.WriteLine("Here?"); } Console.WriteLine("Tested"); } Task<bool> first_check() => Task.FromResult(false); Task<bool> second_check() { Console.WriteLine("second_check"); return Task.FromResult(true); }
Es gibt "Getestet" und sonst nichts aus.
quelle
await
Anweisungen, die direkt nacheinander platziert werden, im Wesentlichen in der richtigen Reihenfolge ausgeführt werden. Obwohl sie für die asynchrone Programmierung verwendet werden,await
ändert sich die Reihenfolge, in der die s selbst ausgeführt werden, nie. (Dies gilt offensichtlich nicht, wenn sie unterschiedliche Funktionen haben oder so.) Daher wird immer das Ergebnis von "first_check()
Zurück" angezeigt, bevor überhaupt über einen Anruf nachgedacht wirdsecond_check()
. Die Kurzschlussauswertung wird also immer in derselben Reihenfolge durchgeführt, ohne jemals zuvor auszuwertensecond_check()
.await
besteht, dass asynchrone Funktionen synchron erscheinen. Es gibt keinen Grund, warum dies anders sein sollte, wenn sie in booleschen Ausdrücken verwendet werden.Ja, es wird kurzgeschlossen. Ihr Code entspricht:
bool first = await first_check(); if (first) { bool second = await second_check(); if (second) { ... } }
Beachten Sie, wie es selbst dann nicht nennen
second_check
, bis die awaitable zurück vonfirst_check
abgeschlossen. Beachten Sie daher, dass die beiden Prüfungen nicht parallel ausgeführt werden. Wenn Sie das tun wollten, könnten Sie verwenden:var t1 = first_check(); var t2 = second_check(); if (await t1 && await t2) { }
An diesem Punkt:
Wenn Sie Prüfungen parallel ausführen und beenden möchten, sobald eine von ihnen false zurückgibt, möchten Sie wahrscheinlich einen Allzweckcode dafür schreiben, die Aufgaben zunächst sammeln und dann
Task.WhenAny
wiederholt verwenden. (Sie sollten auch überlegen, was mit Ausnahmen geschehen soll, die von Aufgaben ausgelöst werden, die für das Endergebnis aufgrund einer anderen Aufgabe, die false zurückgibt, praktisch irrelevant sind.)quelle
false
. Dieser Punkt wird oft übersehen, wenn jemand verwendetTask.WhenAny
.await
funktioniert.&
statt&&
:if (await first_check() & await second_check()) { ... }
Das ist , weil der&
Bediener nicht Bypass etwas, während&&
stoppt , wenn das Ergebnis klar ist , und ändert nicht durch nachfolgende Operanden (dh , wenn der erste Operanden schonfalse
dann macht es keinen Sinn, den zweiten Operanden zu überprüfen). Das gleiche gilt für|
und||
(logisches ODER gegen Verknüpfungs-ODER), aber hier bedeutet die Verknüpfung, dass die Auswertung stoppt, wenn der erste Operand isttrue
.Ja tut es. Sie können dies selbst mit sharplab.io überprüfen :
public async Task M() { if(await Task.FromResult(true) && await Task.FromResult(false)) Console.WriteLine(); }
Wird vom Compiler effektiv in Folgendes umgewandelt:
TaskAwaiter<bool> awaiter; ... compiler-generated state machine for first task... bool result = awaiter.GetResult(); // second operation started and awaited only if first one returned true if (result) { awaiter = Task.FromResult(false).GetAwaiter(); ...
Oder als einfaches Programm:
Task<bool> first_check() => Task.FromResult(false); Task<bool> second_check() => throw new Exception("Will Not Happen"); if (await first_check() && await second_check()) {}
Zweites Beispiel auf sharplab.io .
quelle
Da ich selbst Compiler geschrieben habe, fühle ich mich qualifiziert, eine logischere Meinung abzugeben, die nicht nur auf einigen Tests basiert.
Heutzutage verwandeln die meisten Compiler den Quellcode in einen AST (Abstract Syntax Tree), mit dem der Quellcode sprachunabhängig dargestellt wird.
AST besteht normalerweise aus Syntaxknoten. Ein Syntaxknoten, der einen Wert erzeugt, wird als Ausdruck bezeichnet, während einer, der nichts erzeugt, eine Anweisung ist.
Angesichts des Codes in der Frage,
Betrachten wir also den Testbedingungsausdruck
await first_check() && await second_check()
Der für einen solchen Code erstellte AST lautet wie folgt:
AndExpression: firstOperand = ( AwaitExpression: operand = ( MethodInvocationExpression: name = "first_check" parameterTypes = [] arguments = [] ) ) secondOperand = ( AwaitExpression: operand = ( MethodInvocationExpression: name = "second_check" parameterTypes = [] arguments = [] ) )
Der AST selbst und die Syntax, mit der ich ihn dargestellt habe, wurden im laufenden Betrieb vollständig erfunden. Ich hoffe, es ist klar. Es sieht so aus, als würde es der StackOverflow-Markup-Engine gefallen, da sie gut aussieht! :) :)
An dieser Stelle muss herausgefunden werden, wie interpretiert wird. Nun, ich kann den meisten Dolmetschern sagen, dass sie Ausdrücke nur hierarchisch bewerten. Daher wird es so ziemlich so gemacht:
Bewerten Sie den Ausdruck
await first_check() && await second_check()
Bewerten Sie den Ausdruck
await first_check()
Bewerten Sie den Ausdruck
first_check()
Löse das Symbol auf
first_check
Argumente auswerten. Da ist niemand. Es soll also eine parameterlose Methode mit dem Namen
first_check
aufgerufen werden.Rufen Sie eine parameterlose Methode mit dem Namen auf,
first_check
deren Ergebnis der Wert des Ausdrucks istfirst_check()
.Es wird erwartet, dass der Wert ein
Task<T>
oder istValueTask<T>
, da dies ein wartender Ausdruck ist.Auf den Ausdruck "Warten" wird gewartet, um den Wert zu erhalten, den er schließlich erzeugen wird.
Produziert der erste Operand des Ausdrucks und
false
? Ja. Der zweite Operand muss nicht ausgewertet werden.An diesem Punkt wissen wir, dass der Wert von
await first_check() && await second_check()
notwendigerweise auchfalse
so sein wird.Einige der Überprüfungen, die ich aufgenommen habe, werden statisch durchgeführt (dh zur Kompilierungszeit). Sie dienen jedoch dazu, die Dinge klarer zu machen - es ist unnötig, über die Kompilierung zu sprechen, da wir uns nur mit der Art und Weise befassen, wie Ausdrücke ausgewertet werden.
Das Wesentliche an dieser ganzen Sache ist, dass es C # egal ist, ob der Ausdruck erwartet wird oder nicht - es ist immer noch der erste Operand eines und-Ausdrucks und wird als solcher zuerst ausgewertet. Dann wird nur
true
ausgewertet , wenn der zweite Operand erzeugt wird. Andernfalls wird angenommen, dass das Ganze und der Ausdruck so sindfalse
, wie es nicht anders sein kann.Dies ist meistens die Art und Weise, wie die überwiegende Mehrheit der Compiler, einschließlich Roslyn (der eigentliche C # -Compiler, der vollständig mit C # geschrieben wurde) und Interpreter funktionieren, obwohl ich einige Implementierungsdetails versteckt habe, die keine Rolle spielen, wie die Art und Weise, wie auf den Ausdruck gewartet wird Ich habe wirklich darauf gewartet, was Sie selbst verstehen können, wenn Sie sich den generierten Bytecode ansehen (Sie können eine Website wie diese verwenden . Ich bin sowieso nicht mit dieser Website verbunden - ich schlage sie nur vor, weil sie Roslyn verwendet und ich denke, es ist eine schöne Werkzeug zu beachten .)
Zur Verdeutlichung ist die Art und Weise, wie Ausdrücke abwarten, ziemlich kompliziert und passt nicht zum Thema dieser Frage. Es würde eine vollständige, getrennte Antwort verdienen, um richtig erklärt zu werden, aber ich halte es nicht für wichtig, da es sich lediglich um ein Implementierungsdetail handelt und der erwartete Ausdruck sich ohnehin nicht anders verhält als normale Ausdrücke.
quelle