2015-08-12 11 views
94

Nachdem ich mein Projekt von VS2013 zu VS2015 migriert wurde, wird das Projekt nicht mehr erstellt. Eine Zusammenstellung Fehler tritt in der folgenden LINQ-Anweisung:Roslyn konnte Code nicht kompilieren

static void Main(string[] args) 
{ 
    decimal a, b; 
    IEnumerable<dynamic> array = new string[] { "10", "20", "30" }; 
    var result = (from v in array 
        where decimal.TryParse(v, out a) && decimal.TryParse("15", out b) && a <= b // Error here 
        orderby decimal.Parse(v) 
        select v).ToArray(); 
} 

Der Compiler einen Fehler zurückgibt:

Error CS0165 Use of unassigned local variable 'b'

Was dieses Problem verursacht? Ist es möglich, es über eine Compiler-Einstellung zu beheben?

+11

@BinaryWorrier: Warum? Es verwendet nur "b", nachdem es über einen "out" -Parameter zugewiesen wurde. –

+1

[Die VS 2015-Dokumentation sagt] (https://msdn.microsoft.com/en-us/library/kx37x362.aspx) "Obwohl Variablen, die als out-Argumente übergeben werden, nicht initialisiert werden müssen, bevor sie übergeben werden, ist die aufgerufene Methode erforderlich, um einen Wert zuzuweisen, bevor die Methode zurückkehrt. " Das sieht also wie ein Fehler aus ja, es wird garantiert von diesem tryParse initialisiert. – Rup

+0

Ja, ich bin auch überrascht. Wie ich weiß, erfordert "out" das Zuweisen einer internen Methode. Also dieser Fehler ein bisschen seltsam. – ramil89

Antwort

111

What does cause this issue?

Sieht für mich wie ein Compiler-Fehler aus. Zumindest hat es getan. Obwohl die decimal.TryParse(v, out a) und decimal.TryParse(v, out b) Ausdrücke dynamisch ausgewertet werden, I erwartet der Compiler noch zu verstehen, dass bis zum Erreichen a <= b, a und b definitiv zugewiesen sind. Selbst mit den Seltsamkeiten, die man beim dynamischen Tippen bekommen kann, würde ich erwarten, dass man nur a <= b nach Auswertung der beiden TryParse Aufrufe auswerten würde.

Es stellt sich jedoch heraus, dass durch Bediener und Umwandlung heikel, es durchaus möglich ist, einen Ausdruck A && B && C zu haben, die A und C aber nicht B bewertet - wenn Sie genug sind gerissen. Siehe das Roslyn bug report für Neal Gafters geniales Beispiel.

Noch schwieriger ist es, mit dynamic zu arbeiten - die Semantik bei dynamischen Operanden ist schwerer zu beschreiben, da zur Auswertung der Überladung Operanden ausgewertet werden müssen, um herauszufinden, um welche Typen es sich handelt kontraintuitiv. Allerdings hat Neal wieder ein Beispiel gefunden, das zeigt, dass der Compilerfehler erforderlich ist ... das ist kein Fehler, es ist ein Fehler behoben. Riesige Mengen an Lob an Neal, um es zu beweisen.

Is it possible to fix it through compiler settings?

Nein, aber es gibt Alternativen, die den Fehler vermeiden.

Erstens Sie es vor dem dynamischen stoppen könnte - wenn Sie wissen, dass Sie immer nur Strings verwenden werden, dann könnten Sie IEnumerable<string>oder die Bereichsvariable v eine Art string geben (das heißt from string v in array). Das wäre meine bevorzugte Option.

Wenn Sie wirklich Notwendigkeit, es dynamisch zu halten, nur b Wert geben beginnen mit:

decimal a, b = 0m; 

Dies wird nicht schaden - wir wissen, dass tatsächlich Ihre dynamische Auswertung gewonnen‘ t machen Sie nichts verrücktes, also werden Sie am Ende noch b einen Wert zuweisen, bevor Sie es verwenden, so dass der Anfangswert irrelevant ist.

Außerdem scheint es, dass Klammern funktioniert auch hinzufügen:

where decimal.TryParse(v, out a) && (decimal.TryParse("15", out b) && a <= b) 

, dass der Punkt ändert sich bei dem verschiedene Stücke von Überladungsauflösung ausgelöst und geschieht der Compiler glücklich zu machen.

Es gibt eine Problem noch verbleibenden - die Regeln der Spezifikation auf eindeutige Zuordnung mit dem && Betreiber muss geklärt werden, zu erklären, dass sie nur dann Anwendung, wenn der && Operator mit zwei bool Operanden in seiner „normalen“ Ausführung verwendet wird . Ich werde versuchen, sicherzustellen, dass dies für den nächsten ECMA-Standard festgelegt ist.

+0

Ja! Die Anwendung von 'IEnumerable ' oder das Hinzufügen von Klammern funktionierte für mich. Jetzt baut der Compiler ohne Fehler. – ramil89

+1

mit 'dezimal a, b = 0m;' könnte den Fehler entfernen, aber dann würde 'a <= b 'immer' 0m' verwenden, da der out-Wert noch nicht berechnet wurde. –

+12

@PawBaltzersen: Was lässt dich das denken? Es wird immer * vor dem Vergleich zugewiesen - es ist nur so, dass der Compiler es aus irgendeinem Grund nicht beweisen kann (im Prinzip ein Fehler). –

21

Dies scheint ein Fehler oder zumindest eine Regression im Roslyn-Compiler zu sein. Der folgende Fehler wurde eingereicht, es zu verfolgen:

https://github.com/dotnet/roslyn/issues/4509

In der Zwischenzeit Jons excellent answer hat ein paar Workarounds.

+0

Auch [dieser Fehler] (https://github.com/dotnet/roslyn/issues/4507), die merkt, dass es jetzt mit LINQ zu tun ist ... – Rawling

16

Seit ich im Fehlerbericht so hart trainiert habe, werde ich versuchen, das selbst zu erklären.


T Stellen einiger benutzerdefinierte Typ mit einem impliziten cast bool die zwischen false und true abwechselt, mit false beginnen. Soweit der Compiler weiß, könnte das dynamic erste Argument der ersten && zu diesem Typ bewerten, also muss es pessimistisch sein.

Wenn dann der Code Kompilierung lassen, dies passieren könnte:

  • Wenn der dynamische Bindemittel das erste && auswertet, hat es die folgenden:
    • das erste Argument Bewerten
    • Es ist ein T - implizit werfen Sie es auf bool.
    • Oh, es ist false, so dass wir das zweite Argument nicht bewerten müssen.
    • Lassen Sie das Ergebnis && als erstes Argument auswerten. (. Nein, nicht false aus irgendeinem Grund)
  • Wenn der dynamische Bindemittel das zweite && auswertet, geschieht Folgendes:
    • Bewerten Sie das erste Argument.
    • Es ist ein T - implizit umgewandelt in bool.
    • Oh, es ist true, so bewerten Sie das zweite Argument.
    • ... Oh Mist, b ist nicht zugewiesen.

In spec Begriffe, kurz gesagt, gibt es spezielle „eindeutige Zuordnung“ Regeln, die uns sagen lassen nicht nur, ob eine Variable „eindeutig zugewiesen“ oder „nicht eindeutig zugewiesen“, sondern auch wenn es "definitiv nach false statement" oder "definitiv nach true statement" zugewiesen ist.

Diese existieren, so dass, wenn sie mit && und || (und ! und ?? und ?:) den Compiler zu tun kann, ob in bestimmten Zweigen eines komplexen Booleschen Ausdruck zugewiesen werden Variablen untersuchen kann.

Diese funktionieren jedoch nur , während die Ausdrücke 'Typen bleiben boolean. Wenn ein Teil des Ausdrucks dynamic (oder ein nicht-boolescher statischer Typ) ist, können wir nicht mehr zuverlässig sagen, dass der Ausdruck true oder false ist - das nächste Mal, wenn wir ihn auf bool umwandeln, um zu entscheiden, welche Verzweigung er nehmen soll, hat er seine geändert Verstand.


Update: Dies wurde nun resolved und documented:

The definite assignment rules implemented by previous compilers for dynamic expressions allowed some cases of code that could result in variables being read that are not definitely assigned. See https://github.com/dotnet/roslyn/issues/4509 for one report of this.

...

Because of this possibility the compiler must not allow this program to be compiled if val has no initial value. Previous versions of the compiler (prior to VS2015) allowed this program to compile even if val has no initial value. Roslyn now diagnoses this attempt to read a possibly uninitialized variable.

+1

Mit VS2013 auf meiner anderen Maschine, habe ich es tatsächlich geschafft, nicht zugewiesen zu lesen Speicher damit. Es ist nicht sehr aufregend :( – Rawling

+0

Sie können nicht initialisierte Variablen mit einem einfachen Delegaten lesen. Erstellen Sie einen Delegaten, der eine Methode mit 'ref' ausgibt. Es macht es gerne, und es wird Variablen zugewiesen, ohne den Wert zu ändern – IllidanS4

+0

Aus reiner Neugierde habe ich dieses Snippet mit C# v4 getestet, aber aus Neugierde - wie entscheidet sich der Compiler, den Operator 'false' /' true' zu ​​benutzen, im Gegensatz zum impliziten Cast-Operator? Impliziter Operator bool 'auf dem ersten Argument, dann den zweiten Operanden aufrufen,' operator false 'auf dem ersten Operanden aufrufen, gefolgt von' implizit operator bool 'auf dem ersten Operand * again * Das macht für mich, den der erste Operand sollte im Wesentlichen auf einen booleschen Wert herunterkürzen, nein? – Rob

15

Dies ist kein Fehler. Ein Beispiel dafür, wie ein dynamischer Ausdruck dieses Formulars eine solche out-Variable unzugewiesen lassen kann, finden Sie unter https://github.com/dotnet/roslyn/issues/4509#issuecomment-130872713.

+1

Da meine Antwort akzeptiert und stark aufgewertet ist, habe ich sie bearbeitet, um die Auflösung anzuzeigen. Danke für all deine Arbeit an diesem Thema - einschließlich der Erklärung meines Fehlers zu mir :) –

+0

Vielen Dank, für Ihren erstaunlichen Beweis. – ramil89

Verwandte Themen