【发布时间】:2015-10-12 07:24:02
【问题描述】:
环境:Visual Studio 2015 RTM。 (我没有尝试过旧版本。)
最近,我一直在调试我的一些 Noda Time 代码,并且我注意到当我有一个 NodaTime.Instant 类型的局部变量时(野田时间的中心 struct 类型之一) ,“Locals”和“Watch”窗口似乎没有调用其ToString() 覆盖。如果我在监视窗口中显式调用ToString(),我会看到适当的表示,否则我只会看到:
variableName {NodaTime.Instant}
这不是很有用。
如果我将覆盖更改为返回一个常量字符串,则该字符串 会显示在调试器中,因此它可以清楚地识别出它的存在 - 它只是不想在它的“正常”状态。
我决定在一个小演示应用程序中在本地重现这一点,这就是我想出的。 (请注意,在这篇文章的早期版本中,DemoStruct 是一个类,而 DemoClass 根本不存在 - 我的错,但它解释了一些现在看起来很奇怪的 cmets......)
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 被禁用了。)
现在,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 很容易足够快以避免超时。)
这可能是一个足够好的解决方案 - 但我仍然想知道发生了什么,以及我是否可以更改代码以避免必须将属性放在所有不同的值上输入野田时间。
好奇者和好奇者
无论什么让调试器感到困惑,有时只会让它感到困惑。让我们创建一个持有 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}
但是,根据 cmets 中 Eren 的建议,如果我将 InstantWrapper 更改为结构,我会得到:
instant {NodaTime.Instant}
wrapper {InstantWrapper}
所以它可以评估Instant.ToString() - 只要它是由另一个ToString 方法调用的......它在一个类中。根据所显示变量的类型,类/结构部分似乎很重要,而不是代码需要什么
为了得到结果而被执行。
作为另一个例子,如果我们使用:
object boxed = NodaConstants.UnixEpoch;
... 然后它工作正常,显示正确的值。让我感到困惑。
【问题讨论】:
-
@John 在 VS 2013 中的相同行为(我不得不删除 c#6 的东西),并附加一条消息:名称函数评估已禁用,因为之前的函数评估超时。您必须继续执行才能重新启用函数评估。字符串
-
欢迎使用 c# 6.0 @3-14159265358979323846264
-
也许
DebuggerDisplayAttribute会让它更加努力。 -
您可以在注册表中更改超时...stackoverflow.com/a/1212068/5040941。 (感谢@Neel)!
-
@DiomidisSpinellis:好吧,我已经在这里问过了,以便 a) 以前见过相同事物或了解 VS 内部的人可以回答; b) 以后遇到同样问题的人都可以很快得到答案。
标签: c# debugging visual-studio-2015