什么使Visual Studiodebugging器停止评估ToString重写?

环境:Visual Studio 2015 RTM。 (我没有尝试旧版本。)

最近,我一直在debugging一些Noda Time代码,我注意到当我有一个types为NodaTime.Instant的本地variables(Noda Time中的一个中心structtypes)时,“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。在较高层次上,实现是:

  1. Roslyn为expression式/本地variables生成MSIL代码,以获取要在各种检查窗口中显示的值。
  2. debugging器解释IL得到结果。
  3. 如果有任何“调用”指令,debugging器将执行上述的function评估。
  4. debugging器/ roslyn取得这个结果并将其格式化成用户所显示的树形视图。

由于IL的执行,debugging器总是处理复杂的“真实”和“假”值。 实际值实际上存在于被debugging的进程中。 假值只存在于debugging器进程中。 为了实现正确的结构语义,debugging器在将结构值推送到IL堆栈时总是需要复制值。 复制的值不再是“真正的”值,现在只存在于debugging器过程中。 这意味着如果我们以后需要执行ToString函数评估,我们不能因为这个值在进程中不存在。 尝试获取我们需要的值来模拟ToString方法的执行。 虽然我们可以效仿一些东西,但有很多限制。 例如,我们不能模拟本地代码,我们不能执行调用“真正的”委托值或调用reflection值。

考虑到所有这一切,下面是导致你所看到的各种行为的原因:

  1. debugging器不计算NodaTime.Instant.ToString – >这是因为它是结构types,ToString的实现不能被debugging器仿真,如上所述。
  2. Thread.Sleep似乎需要零时间由ToString调用struct – >这是因为模拟器正在执行ToString 。 Thread.Sleep是一个本地方法,但模拟器知道它,只是忽略了调用。 我们这样做是为了获得一个值给用户看。 在这种情况下延迟不会有帮助。
  3. DisplayAttibute("ToString()")作品。 – >这是混乱。 隐式调用ToStringDebuggerDisplay之间唯一的区别在于隐式ToString评估的任何超时将禁用该types的所有隐式ToString评估,直到下一个debugging会话。 你可能正在观察那个行为。

就devise问题而言,这是我们计划在未来版本的Visual Studio中解决的问题。

希望能够解决问题。 让我知道你是否有更多的问题。 🙂