什么使Visual Studiodebugging器停止评估ToString重写?
环境:Visual Studio 2015 RTM。 (我没有尝试旧版本。)
最近,我一直在debugging一些Noda Time代码,我注意到当我有一个types为NodaTime.Instant
的本地variables(Noda Time中的一个中心struct
types)时,“Locals”和“监视”窗口似乎不会调用其ToString()
覆盖。 如果我在监视窗口中显式地调用ToString()
,我会看到适当的表示,但是我只看到:
variableName {NodaTime.Instant}
这不是很有用。
如果我改变override来返回一个常量string,那么这个string就会显示在debugging器中,所以很明显它能够提取它的存在 – 它只是不想在“normal”状态下使用它。
我决定在一个小小的演示应用程序中重现这一点,下面是我想到的。 (请注意,在这篇文章的早期版本中, DemoStruct
是一个类, DemoStruct
根本不存在 – 我的错,但它解释了一些现在看起来很奇怪的评论…)
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(); } }
在debugging器中,我现在看到:
demoClass {DemoClass} demoStruct {Struct: Bar}
但是,如果我将Thread.Sleep
从1秒减less到900毫秒,还是有一个短暂的停顿,但是我看到Class: Foo
作为值。 Thread.Sleep
调用在DemoStruct.ToString()
看起来并不重要,它始终显示正确 – debugging器在睡眠完成之前显示该值。 (就好像Thread.Sleep
被禁用了。)
现在,Noda Time中的Instant.ToString()
做了相当多的工作,但是肯定不会花费一整秒 – 所以大概会有更多的条件导致debugging器放弃评估一个ToString()
调用。 当然,这也是一个结构。
我已经试过recursion,看看它是否是一个堆栈限制,但似乎并非如此。
那么,我怎样才能从完全评估Instant.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
一个简单的testing应用程序显示我正确的结果:
instant "1970-01-01T00:00:00Z"
所以大概是Noda Time中的问题是DebuggerDisplayAttribute
强制通过的一些条件 – 即使它不通过超时强制。 (这符合我期望Instant.ToString
很容易避免超时的情况。)
这可能是一个足够好的解决scheme – 但是我仍然想知道发生了什么,以及是否可以简单地更改代码,以避免必须将属性放在Noda Time中的所有不同的值types上。
Curiouser和curiouser
无论什么混淆debugging器有时只是混淆。 让我们创build一个持有 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的build议中,如果我将InstantWrapper
更改为一个结构,我会得到:
instant {NodaTime.Instant} wrapper {InstantWrapper}
所以它可以评估Instant.ToString()
– 只要这是由另一个ToString
方法调用…这是在一个类中。 根据所显示的variablestypes,类/结构部分似乎很重要,而不是需要执行什么代码才能得到结果。
作为另一个例子,如果我们使用:
object boxed = NodaConstants.UnixEpoch;
…那么它工作正常,显示正确的价值。 让我困惑的颜色。
更新:
此错误已在Visual Studio 2015更新2中得到解决。让我知道如果您仍然遇到使用Update 2或更高版本评估结构值ToString的问题。
原始答案:
您正在运行Visual Studio 2015的已知错误/devise限制,并在结构types上调用ToString。 处理System.DateTimeSpan
时也可以观察到这一点。 System.DateTimeSpan.ToString()
在Visual Studio 2013的评估窗口中工作,但在2015年并不总是有效。
如果您对低级别的细节感兴趣,请点击这里:
为了评估ToString
,debugging器做了所谓的“function评估”。 在大大简化的条件下,除了当前线程,debugging器暂停进程中的所有线程,将当前线程的上下文更改为ToString
函数,设置隐藏的防护断点,然后允许进程继续。 当守护断点被命中时,debugging器将该过程恢复到其以前的状态,并且函数的返回值被用于填充该窗口。
为了支持lambdaexpression式,我们必须在Visual Studio 2015中完全重写CLR Expression Evaluator。在较高层次上,实现是:
- Roslyn为expression式/本地variables生成MSIL代码,以获取要在各种检查窗口中显示的值。
- debugging器解释IL得到结果。
- 如果有任何“调用”指令,debugging器将执行上述的function评估。
- debugging器/ roslyn取得这个结果并将其格式化成用户所显示的树形视图。
由于IL的执行,debugging器总是处理复杂的“真实”和“假”值。 实际值实际上存在于被debugging的进程中。 假值只存在于debugging器进程中。 为了实现正确的结构语义,debugging器在将结构值推送到IL堆栈时总是需要复制值。 复制的值不再是“真正的”值,现在只存在于debugging器过程中。 这意味着如果我们以后需要执行ToString
函数评估,我们不能因为这个值在进程中不存在。 尝试获取我们需要的值来模拟ToString
方法的执行。 虽然我们可以效仿一些东西,但有很多限制。 例如,我们不能模拟本地代码,我们不能执行调用“真正的”委托值或调用reflection值。
考虑到所有这一切,下面是导致你所看到的各种行为的原因:
- debugging器不计算
NodaTime.Instant.ToString
– >这是因为它是结构types,ToString的实现不能被debugging器仿真,如上所述。 -
Thread.Sleep
似乎需要零时间由ToString
调用struct – >这是因为模拟器正在执行ToString
。 Thread.Sleep是一个本地方法,但模拟器知道它,只是忽略了调用。 我们这样做是为了获得一个值给用户看。 在这种情况下延迟不会有帮助。 -
DisplayAttibute("ToString()")
作品。 – >这是混乱。 隐式调用ToString
和DebuggerDisplay
之间唯一的区别在于隐式ToString
评估的任何超时将禁用该types的所有隐式ToString
评估,直到下一个debugging会话。 你可能正在观察那个行为。
就devise问题而言,这是我们计划在未来版本的Visual Studio中解决的问题。
希望能够解决问题。 让我知道你是否有更多的问题。 🙂