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.
DebuggerDisplayAttribute
würde es dazu führen, dass es etwas härter wird.