Ś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 structtypó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 DemoStructbyła klasa i DemoClasswcale 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.Sleepwywołanie z 1 sekundy do 900 ms, nadal będzie krótka pauza, ale wtedy widzę Class: Foowartość. Wydaje się, że nie ma znaczenia, jak długo trwa Thread.Sleeppołączenie DemoStruct.ToString(), zawsze jest wyświetlane poprawnie - a debugger wyświetla wartość przed zakończeniem snu. (To tak, jakby Thread.Sleepbył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, DebuggerDisplayAttributewydaje 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.ToStringjest 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ę InstantWrappersię 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ą ToStringmetodą ... 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.
DebuggerDisplayAttributesprawiłoby to, że spróbowałoby trochę trudniej.