Środowisko: Visual Studio 2015 RTM. (Nie próbowałem starszych wersji).
Ostatnio debugowałem część mojego kodu Noda Time i zauważyłem, że kiedy mam lokalną zmienną typu NodaTime.Instant
(jeden z głównych struct
typów w Noda Time), okna „Locals” i „Watch” nie wydaje się nazywać jego ToString()
zastąpienia. Jeśli dzwonię ToString()
jawnie w oknie zegarka, widzę odpowiednią reprezentację, ale poza tym widzę tylko:
variableName {NodaTime.Instant}
co nie jest zbyt przydatne.
Jeśli zmienię przesłonięcie, aby zwracać stały ciąg, ciąg jest wyświetlany w debuggerze, więc jest w stanie wyraźnie zauważyć, że tam jest - po prostu nie chce go używać w stanie „normalnym”.
Postanowiłem odtworzyć to lokalnie w małej aplikacji demonstracyjnej i oto, co wymyśliłem. (Zauważ, że we wczesnej wersji tego postu DemoStruct
była klasa i DemoClass
wcale nie istniała - moja wina, ale wyjaśnia niektóre komentarze, które wyglądają teraz dziwnie ...)
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();
}
}
W debuggerze widzę teraz:
demoClass {DemoClass}
demoStruct {Struct: Bar}
Jeśli jednak zmniejszę Thread.Sleep
wywołanie z 1 sekundy do 900 ms, nadal będzie krótka pauza, ale wtedy widzę Class: Foo
wartość. Wydaje się, że nie ma znaczenia, jak długo trwa Thread.Sleep
połączenie DemoStruct.ToString()
, zawsze jest wyświetlane poprawnie - a debugger wyświetla wartość przed zakończeniem snu. (To tak, jakby Thread.Sleep
było wyłączone.)
Teraz Instant.ToString()
w Noda Time wykonuje sporo pracy, ale z pewnością nie zajmuje to całej sekundy - więc przypuszczalnie jest więcej warunków, które powodują, że debugger rezygnuje z oceny ToString()
połączenia. I oczywiście jest to i tak struktura.
Próbowałem rekursywnie, aby sprawdzić, czy jest to limit stosu, ale wydaje się, że tak nie jest.
Jak więc ustalić, co powstrzymuje VS przed pełną oceną Instant.ToString()
? Jak wspomniano poniżej, DebuggerDisplayAttribute
wydaje się pomagać, ale nie wiedząc, dlaczego , nigdy nie będę całkowicie pewny, kiedy będę tego potrzebować, a kiedy nie.
Aktualizacja
Jeśli użyję DebuggerDisplayAttribute
, rzeczy się zmienią:
// For the sample code in the question...
[DebuggerDisplay("{ToString()}")]
public class DemoClass
daje mi:
demoClass Evaluation timed out
Podczas gdy stosuję go w Czasie Nody:
[DebuggerDisplay("{ToString()}")]
public struct Instant
prosta aplikacja testowa pokazuje właściwy wynik:
instant "1970-01-01T00:00:00Z"
Prawdopodobnie problemem w Czasie Nody jest pewien warunek, który DebuggerDisplayAttribute
powoduje wymuszenie - nawet jeśli nie wymusza przekroczenia limitu czasu. (Byłoby to zgodne z moimi oczekiwaniami, które Instant.ToString
jest wystarczająco szybkie, aby uniknąć przekroczenia limitu czasu).
To może być wystarczająco dobre rozwiązanie - ale nadal chciałbym wiedzieć, co się dzieje i czy mogę zmienić kod po prostu, aby uniknąć konieczności przypisywania atrybutu do wszystkich różnych typów wartości w Noda Time.
Curiouser i curiouser
Cokolwiek jest mylące, debugger tylko myli je czasami. Stwórzmy klasę, która posiadaInstant
i używa go dla własnej ToString()
metody:
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();
}
}
Teraz widzę:
instant {NodaTime.Instant}
wrapper {1970-01-01T00:00:00Z}
Jednak na sugestię Erena w komentarzach, jeśli zmienię InstantWrapper
się na struct, otrzymuję:
instant {NodaTime.Instant}
wrapper {InstantWrapper}
Więc można ocenić Instant.ToString()
- tak długo, jak to jest wywoływana przez inną ToString
metodą ... co jest w klasie. Wydaje się, że część class / struct jest ważna na podstawie typu wyświetlanej zmiennej, a nie tego, jaki kod należy wykonać, aby uzyskać wynik.
Jako kolejny przykład tego, jeśli użyjemy:
object boxed = NodaConstants.UnixEpoch;
... to działa dobrze, wyświetlając właściwą wartość. Kolor mnie zmieszany.
DebuggerDisplayAttribute
sprawiłoby to, że spróbowałoby trochę trudniej.