什么使Visual Studio调试器停止评估ToString重写?
环境:Visual Studio 2015 RTM。 (我还没有尝试旧版本。)
最近,我一直在调试一些我的Noda Time代码,并且我注意到当我有一个NodaTime.Instant
类型的局部变量(Noda Time中的一个中心struct
类型)时,“本地语言”和“看”窗口似乎不会调用其ToString()
覆盖。 如果我在监视窗口中显式调用ToString()
,我会看到适当的表示形式,但除此之外我只能看到:
variableName {NodaTime.Instant}
这不是很有用。
如果我改变override来返回一个常量字符串,那么这个字符串会显示在调试器中,所以很明显它能够获取它的存在 - 它只是不想在“正常”状态下使用它。
我决定在一个小小的演示程序中在本地重现这一点,这就是我想出的。 (请注意,在这个岗位的早期版本, 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秒减少到900ms,仍然有一个短暂停顿,但是后来我将Class: Foo
视为值。 这似乎并不重要了多久Thread.Sleep
通话中DemoStruct.ToString()
它总是正确显示-和调试器显示值的睡眠会完成之前。 (就好像Thread.Sleep
被禁用了。)
现在,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
强制通过的一些条件 - 即使它不通过超时强制。 (这符合我的预期,即Instant.ToString
的速度足够快以避免超时。)
这可能是一个足够好的解决方案 - 但我仍然想知道发生了什么,以及是否可以简单地更改代码,以避免必须将属性放在Noda Time中的所有不同值类型上。
Curiouser和curiouser
不管调试器有多混淆,只会让它困惑。 让我们创建一个持有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
更改为结构体,我会得到:
instant {NodaTime.Instant}
wrapper {InstantWrapper}
所以它可以评估Instant.ToString()
- 只要这是由另一个ToString
方法调用的......这是在一个类中。 根据所显示变量的类型,类/结构部分似乎很重要,而不是需要执行哪些代码才能获得结果。
作为另一个例子,如果我们使用:
object boxed = NodaConstants.UnixEpoch;
...然后它工作正常,显示正确的值。 让我困惑的颜色。
更新:
此错误已在Visual Studio 2015 Update 2中得到解决。让我知道是否仍遇到使用Update 2或更高版本评估ToString结构值的问题。
原始答案:
您正在使用Visual Studio 2015运行已知的错误/设计限制,并在结构类型上调用ToString。 处理System.DateTimeSpan
时也可以观察到这一点。 System.DateTimeSpan.ToString()
在Visual Studio 2013的评估窗口中工作,但在2015年并不总是有效。
如果您对底层细节感兴趣,请点击这里:
为了评估ToString
,调试器执行所谓的“功能评估”。 以非常简单的术语来说,除了当前线程,调试器暂停进程中的所有线程,将当前线程的上下文更改为ToString
函数,设置隐藏的防护断点,然后允许进程继续。 当保护断点被命中时,调试器将该过程恢复到其以前的状态,并且函数的返回值被用于填充该窗口。
为了支持lambda表达式,我们必须在Visual Studio 2015中完全重写CLR Expression Evaluator。在较高层次上,实现是:
由于执行IL,调试器总是处理复杂的“真实”和“假”值。 实际值实际上存在于正在调试的进程中。 假值只存在于调试器进程中。 为了实现正确的结构语义,调试器在将结构值推送到IL堆栈时总是需要复制值。 复制的值不再是“真正的”值,现在只存在于调试器过程中。 这意味着如果我们稍后需要执行ToString
函数评估,我们不能因为该过程中不存在该值。 尝试获取我们需要的值来模拟ToString
方法的执行。 虽然我们可以效仿一些东西,但有很多限制。 例如,我们不能模拟原生代码,我们不能执行对“真实”委托值或调用反射值的调用。
考虑到所有这些,以下是导致您看到的各种行为的原因:
NodaTime.Instant.ToString
- >这是因为它是struct类型,并且ToString的实现不能由调试器仿真,如上所述。 Thread.Sleep
在结构体上由ToString
调用时似乎需要零时间 - >这是因为模拟器正在执行ToString
。 Thread.Sleep是一种本地方法,但仿真器知道它并忽略该调用。 我们这样做是为了尝试获得向用户展示的价值。 在这种情况下延迟不会有帮助。 DisplayAttibute("ToString()")
起作用。 - >这是令人困惑的。 隐式调用ToString
和DebuggerDisplay
之间唯一的区别在于隐式ToString
评估的任何超时将禁用该类型的所有隐式ToString
评估,直到下一个调试会话。 你可能正在观察那种行为。 就设计问题/错误而言,这是我们计划在未来版本的Visual Studio中解决的问题。
希望能够解决问题。 如果您有更多问题,请告诉我。 :-)
链接地址: http://www.djcxy.com/p/36067.html上一篇: What makes the Visual Studio debugger stop evaluating a ToString override?