Окружающая среда: окончательная первоначальная версия Visual Studio 2015. (Я не пробовал более старые версии.)
Недавно я отлаживал часть своего кода Noda Time, и я заметил, что когда у меня есть локальная переменная типа NodaTime.Instant
(один из центральных типов struct
в Noda Time), окна "Locals" и "Watch" не отображают его переопределение ToString()
. Если я вызываю ToString()
явно в окне просмотра, я вижу соответствующее представление, но в остальном я просто вижу:
variableName {NodaTime.Instant}
что не очень полезно.
Если я изменил переопределение, чтобы возвращать константную строку, строка отображается в отладчике, поэтому она явно может поднять ее там - она просто не хочет использовать ее в своем "нормальном" состоянии.
Я решил воспроизвести это локально в небольшом демонстрационном приложении, и вот то, что я придумал. (Обратите внимание, что в ранней версии этого сообщения DemoStruct
был классом, а DemoClass
вообще не существовал - моя ошибка, но он объясняет некоторые комментарии, которые сейчас выглядят странно...)
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();
}
}
В отладчике теперь я вижу:
demoClass {DemoClass}
demoStruct {Struct: Bar}
Однако, если я уменьшу вызов Thread.Sleep
вниз с 1 секунды до 900 мс, все еще короткая пауза, но тогда я вижу Class: Foo
как значение. Кажется, не имеет значения, как долго вызов Thread.Sleep
находится в DemoStruct.ToString()
, он всегда отображается правильно - и отладчик отображает значение до завершения сна. (Он как будто Thread.Sleep
отключен.)
Теперь Instant.ToString()
в Noda Time выполняет довольно много работы, но это, конечно, не занимает целую секунду, поэтому, по-видимому, есть больше условий, которые заставляют отладчика отказаться от оценки вызова ToString()
. И, конечно же, это структура.
Я пробовал рекурсировать, чтобы увидеть, является ли ограничение на стек, но это не так.
Итак, как я могу решить, что остановить VS от полной оценки Instant.ToString()
? Как отмечено ниже, DebuggerDisplayAttribute
, похоже, помогает, но, не зная почему, я никогда не буду полностью уверен в том, когда мне это нужно, а когда нет.
Обновление
Если я использую DebuggerDisplayAttribute
, все меняется:
// For the sample code in the question...
[DebuggerDisplay("{ToString()}")]
public class DemoClass
дает мне:
demoClass Evaluation timed out
Если я применяю его в Noda Time:
[DebuggerDisplay("{ToString()}")]
public struct Instant
простое тестовое приложение показывает мне правильный результат:
instant "1970-01-01T00:00:00Z"
Таким образом, предположительно проблема в Noda Time - это некоторое условие, которое DebuggerDisplayAttribute
проскальзывает - даже если оно не затягивает таймауты. (Это соответствовало бы моему ожиданию, что Instant.ToString
будет достаточно быстрым, чтобы избежать таймаута.)
Это может быть достаточно хорошее решение, но я все равно хотел бы знать, что происходит, и могу ли я изменить код просто, чтобы избежать необходимости добавлять атрибут во все типы значений в Noda Time.
Любопытный и любопытный
Все, что сбивает с толку, отладчик только иногда путает его. Позвольте создать класс, который содержит Instant
и использует его для собственного метода ToString()
:
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();
}
}
Теперь я вижу:
instant {NodaTime.Instant}
wrapper {1970-01-01T00:00:00Z}
Однако, по предложению Эрена в комментариях, если я изменяю InstantWrapper
как структуру, я получаю:
instant {NodaTime.Instant}
wrapper {InstantWrapper}
Таким образом, он может оценить Instant.ToString()
- до тех пор, пока он вызван другим методом ToString
... который находится внутри класса. Элемент class/struct кажется важным, основываясь на типе отображаемой переменной, а не на том, что нужно коду
для выполнения результата.
В качестве еще одного примера этого, если мы используем:
object boxed = NodaConstants.UnixEpoch;
... тогда он отлично работает, отображая правильное значение. Цвет меня смущает.