Среда: Visual Studio 2015 RTM. (Я не пробовал старые версии.)
Недавно я отлаживал часть своего кода 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"
Так , предположительно, проблема в Ноде время некоторое условие , которое 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
методом ... который находится внутри класса. Часть класса / структуры кажется важной в зависимости от типа отображаемой переменной, а не от того, какой код необходимо выполнить, чтобы получить результат.
В качестве другого примера этого, если мы используем:
object boxed = NodaConstants.UnixEpoch;
... тогда он работает нормально, отображая правильное значение. Цвет меня смутил.
источник
DebuggerDisplayAttribute
заставит его попробовать немного сложнее.Ответы:
Обновить:
Эта ошибка была исправлена в Visual Studio 2015 Update 2. Дайте мне знать, если у вас все еще возникают проблемы с оценкой ToString для значений структуры с использованием Update 2 или более поздней версии.
Оригинальный ответ:
Вы сталкиваетесь с известным ограничением ошибок / дизайна в Visual Studio 2015 и вызываете ToString для структурных типов. Это также можно наблюдать при работе с
System.DateTimeSpan
.System.DateTimeSpan.ToString()
работает в окнах оценки с Visual Studio 2013, но не всегда работает в 2015 году.Если вы заинтересованы в деталях низкого уровня, вот что происходит:
Для оценки
ToString
отладчик выполняет то, что известно как «оценка функции». В упрощенном виде отладчик приостанавливает все потоки в процессе, кроме текущего, изменяет контекст текущего потока наToString
функцию, устанавливает скрытую контрольную точку останова и затем позволяет процессу продолжаться. При достижении контрольной точки защиты отладчик восстанавливает процесс до его предыдущего состояния, а возвращаемое значение функции используется для заполнения окна.Для поддержки лямбда-выражений нам пришлось полностью переписать оценщик выражений CLR в Visual Studio 2015. На высоком уровне реализация:
Из-за выполнения IL отладчик всегда имеет дело со сложным сочетанием «реальных» и «поддельных» значений. Реальные значения действительно существуют в отлаживаемом процессе. Ложные значения существуют только в процессе отладчика. Чтобы реализовать правильную семантику структуры, отладчик всегда должен делать копию значения при отправке значения структуры в стек IL. Скопированное значение больше не является «реальным» значением и теперь существует только в процессе отладчика. Это означает, что если нам позже понадобится выполнить оценку функции
ToString
, мы не сможем, потому что значение не существует в процессе. Чтобы попытаться получить значение, нам нужно эмулировать выполнениеToString
метод. Хотя мы можем подражать некоторым вещам, есть много ограничений. Например, мы не можем эмулировать нативный код и не можем выполнять вызовы «реальных» значений делегатов или вызовы значений отражения.Имея это в виду, вот что вызывает различные виды поведения, которые вы видите:
NodaTime.Instant.ToString
-> Это потому, что это структурный тип, и реализация ToString не может эмулироваться отладчиком, как описано выше.Thread.Sleep
кажется, занимает нулевое время при вызовеToString
структуры -> Это потому, что эмулятор выполняетсяToString
. Thread.Sleep является нативным методом, но эмулятор знает об этом и просто игнорирует вызов. Мы делаем это, чтобы попытаться получить значение для показа пользователю. Задержка не поможет в этом случае.DisplayAttibute("ToString()")
работает. -> Это сбивает с толку. Единственное различие между неявным вызовомToString
иDebuggerDisplay
состоит в том, что любые тайм-ауты неявнойToString
оценки будут отключать все неявныеToString
оценки для этого типа до следующего сеанса отладки. Вы можете наблюдать это поведение.С точки зрения проблемы проектирования / ошибки, это то, что мы планируем решить в будущем выпуске Visual Studio.
Надеюсь, это прояснит ситуацию. Дайте мне знать, если у вас есть еще вопросы. :-)
источник
innerResult
начинается с нуля, цикл никогда не прекратится, и в конечном итоге время ожидания истечет. Фактически, оценки позволяют запускать по умолчанию только один поток в процессе, поэтому вы увидите одно и то же поведение независимо от того, используется эмулятор или нет.