Umgebung: Visual Studio 2015 RTM. (Ich habe ältere Versionen nicht ausprobiert.)
Vor kurzem habe ich einen Teil meines Noda-Zeitcodes debuggt und festgestellt, dass die Fenster "Locals" und "Watch" angezeigt werden, wenn ich eine lokale Variable vom Typ NodaTime.Instant
(einer der zentralen struct
Typen in Noda Time) habe scheint seine ToString()
Überschreibung nicht zu nennen . Wenn ich ToString()
explizit im Überwachungsfenster anrufe, sehe ich die entsprechende Darstellung, aber ansonsten sehe ich nur:
variableName {NodaTime.Instant}
das ist nicht sehr nützlich.
Wenn ich die Überschreibung ändern , um eine konstante String zurück, die Zeichenfolge wird im Debugger angezeigt, so dass es eindeutig in der Lage ist zu holen , dass es da ist - es will einfach nicht , es benutzen in seinem „normalen“ Zustand.
Ich habe beschlossen, dies lokal in einer kleinen Demo-App zu reproduzieren, und hier ist, was ich mir ausgedacht habe. (Beachten Sie, dass in einer frühen Version dieses Beitrags DemoStruct
eine Klasse war und DemoClass
überhaupt nicht existierte - meine Schuld, aber es erklärt einige Kommentare, die jetzt seltsam aussehen ...)
using System;
using System.Diagnostics;
using System.Threading;
public struct DemoStruct
{
public string Name { get; }
public DemoStruct(string name)
{
Name = name;
}
public override string ToString()
{
Thread.Sleep(1000); // Vary this to see different results
return $"Struct: {Name}";
}
}
public class DemoClass
{
public string Name { get; }
public DemoClass(string name)
{
Name = name;
}
public override string ToString()
{
Thread.Sleep(1000); // Vary this to see different results
return $"Class: {Name}";
}
}
public class Program
{
static void Main()
{
var demoClass = new DemoClass("Foo");
var demoStruct = new DemoStruct("Bar");
Debugger.Break();
}
}
Im Debugger sehe ich jetzt:
demoClass {DemoClass}
demoStruct {Struct: Bar}
Wenn ich den Thread.Sleep
Anruf von 1 Sekunde auf 900 ms reduziere, gibt es immer noch eine kurze Pause, aber dann sehe ich Class: Foo
als Wert. Es scheint keine Rolle zu spielen, wie lange der Thread.Sleep
Anruf dauert DemoStruct.ToString()
, er wird immer richtig angezeigt - und der Debugger zeigt den Wert an, bevor der Ruhezustand abgeschlossen wäre. (Es ist, als wäre Thread.Sleep
es deaktiviert.)
Jetzt Instant.ToString()
in Noda macht Time eine ganze Menge Arbeit, aber es dauert sicherlich keine ganze Sekunde - also gibt es vermutlich mehr Bedingungen, die den Debugger dazu veranlassen, die Auswertung eines ToString()
Anrufs aufzugeben . Und natürlich ist es sowieso eine Struktur.
Ich habe versucht, rekursiv zu prüfen, ob es sich um ein Stapellimit handelt, aber das scheint nicht der Fall zu sein.
Wie kann ich also herausfinden, was VS davon abhält, vollständig zu evaluieren Instant.ToString()
? Wie unten erwähnt, DebuggerDisplayAttribute
scheint es zu helfen, aber ohne zu wissen warum , werde ich nie ganz sicher sein, wann ich es brauche und wann nicht.
Aktualisieren
Wenn ich benutze DebuggerDisplayAttribute
, ändern sich die Dinge:
// For the sample code in the question...
[DebuggerDisplay("{ToString()}")]
public class DemoClass
gibt mir:
demoClass Evaluation timed out
Wenn ich es in Noda Time anwende:
[DebuggerDisplay("{ToString()}")]
public struct Instant
Eine einfache Test-App zeigt mir das richtige Ergebnis:
instant "1970-01-01T00:00:00Z"
Vermutlich ist das Problem in der Noda-Zeit eine Bedingung, die DebuggerDisplayAttribute
sich durchsetzt - obwohl sie keine Zeitüberschreitungen erzwingt. (Dies würde meiner Erwartung entsprechen, Instant.ToString
die leicht schnell genug ist, um eine Zeitüberschreitung zu vermeiden.)
Dies mag eine gute Lösung sein - aber ich würde immer noch gerne wissen, was los ist und ob ich den Code einfach ändern kann, um zu vermeiden, dass das Attribut auf alle verschiedenen Werttypen in Noda Time gesetzt werden muss.
Neugieriger und neugieriger
Was den Debugger verwirrt, verwirrt ihn nur manchmal. Erstellen wir eine Klasse, die eine enthält Instant
und diese für ihre eigene ToString()
Methode verwendet:
using NodaTime;
using System.Diagnostics;
public class InstantWrapper
{
private readonly Instant instant;
public InstantWrapper(Instant instant)
{
this.instant = instant;
}
public override string ToString() => instant.ToString();
}
public class Program
{
static void Main()
{
var instant = NodaConstants.UnixEpoch;
var wrapper = new InstantWrapper(instant);
Debugger.Break();
}
}
Jetzt sehe ich:
instant {NodaTime.Instant}
wrapper {1970-01-01T00:00:00Z}
Auf Vorschlag von Eren in Kommentaren InstantWrapper
erhalte ich jedoch Folgendes, wenn ich mich in eine Struktur verwandle:
instant {NodaTime.Instant}
wrapper {InstantWrapper}
Es kann also ausgewertet werden Instant.ToString()
- solange dies von einer anderen ToString
Methode aufgerufen wird ... die sich innerhalb einer Klasse befindet. Der Klassen- / Strukturteil scheint wichtig zu sein, basierend auf dem Typ der angezeigten Variablen und nicht darauf, welcher Code ausgeführt werden muss, um das Ergebnis zu erhalten.
Als weiteres Beispiel hierfür verwenden wir:
object boxed = NodaConstants.UnixEpoch;
... dann funktioniert es einwandfrei und zeigt den richtigen Wert an. Färbe mich verwirrt.
quelle
DebuggerDisplayAttribute
würde es dazu führen, dass es etwas härter wird.Antworten:
Aktualisieren:
Dieser Fehler wurde in Visual Studio 2015 Update 2 behoben. Lassen Sie mich wissen, wenn Sie weiterhin Probleme beim Auswerten von ToString für Strukturwerte mit Update 2 oder höher haben.
Ursprüngliche Antwort:
Sie stoßen mit Visual Studio 2015 auf eine bekannte Fehler- / Entwurfsbeschränkung und rufen ToString für Strukturtypen auf. Dies kann auch beim Umgang mit beobachtet werden
System.DateTimeSpan
.System.DateTimeSpan.ToString()
Funktioniert in den Evaluierungsfenstern mit Visual Studio 2013, funktioniert jedoch nicht immer in 2015.Wenn Sie an den Details auf niedriger Ebene interessiert sind, gehen Sie wie folgt vor:
Zur Auswertung
ToString
führt der Debugger eine sogenannte "Funktionsauswertung" durch. In stark vereinfachter Form hält der Debugger alle Threads im Prozess außer dem aktuellen Thread an, ändert den Kontext des aktuellen Threads in dieToString
Funktion, legt einen versteckten Schutz-Haltepunkt fest und lässt den Prozess dann fortfahren. Wenn der Guard-Haltepunkt erreicht wird, stellt der Debugger den Prozess auf seinen vorherigen Zustand zurück und der Rückgabewert der Funktion wird zum Auffüllen des Fensters verwendet.Um Lambda-Ausdrücke zu unterstützen, mussten wir den CLR Expression Evaluator in Visual Studio 2015 komplett neu schreiben. Auf hoher Ebene lautet die Implementierung:
Aufgrund der Ausführung von IL hat der Debugger immer mit einer komplizierten Mischung aus "echten" und "falschen" Werten zu tun. Während des Debuggens existieren tatsächlich reale Werte. Gefälschte Werte existieren nur im Debugger-Prozess. Um eine ordnungsgemäße Struktursemantik zu implementieren, muss der Debugger immer eine Kopie des Werts erstellen, wenn ein Strukturwert auf den IL-Stapel übertragen wird. Der kopierte Wert ist kein "realer" Wert mehr und existiert nur noch im Debugger-Prozess. Das heißt, wenn wir später eine Funktionsbewertung von durchführen müssen
ToString
, können wir dies nicht, da der Wert im Prozess nicht vorhanden ist. Um zu versuchen, den Wert zu erhalten, müssen wir die Ausführung von emulierenToString
Methode. Während wir einige Dinge emulieren können, gibt es viele Einschränkungen. Beispielsweise können wir keinen nativen Code emulieren und keine Aufrufe an "echte" Delegatwerte oder Aufrufe von Reflexionswerten ausführen.In Anbetracht dessen ist hier, was die verschiedenen Verhaltensweisen verursacht, die Sie sehen:
NodaTime.Instant.ToString
-> Dies liegt daran, dass es sich um einen Strukturtyp handelt und die Implementierung von ToString vom Debugger nicht wie oben beschrieben emuliert werden kann.Thread.Sleep
scheint keine Zeit inToString
Anspruch zu nehmen, wenn von einer Struktur aufgerufen wird -> Dies liegt daran, dass der Emulator ausgeführt wirdToString
. Thread.Sleep ist eine native Methode, aber der Emulator ist sich dessen bewusst und ignoriert den Aufruf einfach. Wir tun dies, um zu versuchen, einen Wert zu erhalten, der dem Benutzer angezeigt wird. Eine Verzögerung wäre in diesem Fall nicht hilfreich.DisplayAttibute("ToString()")
funktioniert. -> Das ist verwirrend. Der einzige Unterschied zwischen dem impliziten Aufruf vonToString
undDebuggerDisplay
besteht darin, dass alle Zeitüberschreitungen der implizitenToString
Auswertung alle implizitenToString
Auswertungen für diesen Typ bis zur nächsten Debug-Sitzung deaktivieren . Möglicherweise beobachten Sie dieses Verhalten.In Bezug auf das Designproblem / den Designfehler planen wir, dies in einer zukünftigen Version von Visual Studio zu beheben.
Hoffentlich klärt das die Dinge auf. Lassen Sie mich wissen, wenn Sie weitere Fragen haben. :-)
quelle
innerResult
der Start mit null beginnt, wird die Schleife niemals beendet und die Auswertung läuft schließlich ab. Bei Auswertungen kann standardmäßig nur ein einzelner Thread im Prozess ausgeführt werden, sodass dasselbe Verhalten angezeigt wird, unabhängig davon, ob der Emulator verwendet wird oder nicht.