最近,我在调试我的 Noda Time 代码时注意到,当我有一个
NodaTime.Instant
类型的局部变量(Noda Time 中的中心 struct
类型之一)时,“Locals” 和 “Watch” 窗口似乎没有调用其 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}
然而,如果我将
Thread.Sleep
调用从1秒减少到900毫秒,仍然会有短暂的暂停,但然后我会看到Class: Foo
作为值。似乎DemoStruct.ToString()
中的Thread.Sleep
调用持续多长时间并不重要,它总是被正确显示——调试器在睡眠完成前显示了该值。(就像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
当我在Noda Time中应用它时:
[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}
然而,在评论中Eren的建议下,如果我将InstantWrapper
更改为结构体,则会得到:
instant {NodaTime.Instant}
wrapper {InstantWrapper}
因此,它可以评估“Instant.ToString()” - 只要这是由另一个ToString方法调用的...而且该方法在类中。基于显示的变量类型而不是需要执行的代码,类/结构部分似乎很重要。
作为另一个例子,如果我们使用:
object boxed = NodaConstants.UnixEpoch;
如果这样做,它可以正常工作,并显示正确的值。让我感到困惑。
DebuggerDisplayAttribute
会让它更加努力尝试。 - Rawling