环境:Visual Studio 2015 RTM。(我还没有尝试过旧版本。)
最近,我一直在调试我的一些NodaTime代码,我注意到当我有一个类型为NodaTime的局部变量时。即时(野田时间的中心结构类型之一),“本地”和“观看”窗口似乎不调用它的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}
然而,如果我减少线程。睡眠调用从1秒下降到900ms,仍然有一个短暂的暂停,但随后我看到Class: Foo作为值。这条线有多长似乎并不重要。睡眠调用在DemoStruct.ToString()中,它总是正确地显示——调试器在睡眠完成之前显示该值。(就像Thread。睡眠被禁用。)
现在,Noda Time中的Instant.ToString()完成了相当多的工作,但肯定不会花费一秒钟的时间——因此,可能有更多的条件导致调试器放弃计算ToString()调用。当然它是一个结构体。
我试着递归看看它是否是堆栈限制,但似乎不是这样。
那么,我如何才能找出是什么阻止VS完全评估Instant.ToString()?如下所述,DebuggerDisplayAttribute似乎有帮助,但不知道为什么,我永远不会完全相信什么时候需要它,什么时候不需要。
更新
如果我使用DebuggerDisplayAttribute,事情会改变:
// For the sample code in the question...
[DebuggerDisplay("{ToString()}")]
public class DemoClass
给我:
demoClass Evaluation timed out
然而,当我把它应用在野田时间:
[DebuggerDisplay("{ToString()}")]
public struct Instant
一个简单的测试应用程序显示了正确的结果:
instant "1970-01-01T00:00:00Z"
因此,Noda Time中的问题可能是一些条件,DebuggerDisplayAttribute强制通过-即使它不强制通过超时。(这将符合我的期望,即时。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}
然而,根据Eren在评论中的建议,如果我将InstantWrapper更改为struct,我得到:
instant {NodaTime.Instant}
wrapper {InstantWrapper}
所以它可以计算Instant.ToString() -只要由另一个ToString方法调用…这是在一个类中。类/结构部分似乎很重要,这取决于所显示变量的类型,而不是代码需要什么 为了得到结果而执行。
另一个例子是,如果我们用:
object boxed = NodaConstants.UnixEpoch;
... 然后它正常工作,显示正确的值。把我弄糊涂了。