是什么让Visual Studio调试器停止评估的ToString覆盖? [英] What makes the Visual Studio debugger stop evaluating a ToString override?

查看:203
本文介绍了是什么让Visual Studio调试器停止评估的ToString覆盖?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



最近,我一直在调试我的一些的野田时间代码,而且我发现,当我有型的 NodaTime.Instant 局部变量(中央之一结构类型的野田时间)时,当地人和观察窗口不显示调用它的的ToString()覆盖。如果我称之为的ToString()明确在监视窗口,我看到了适当名额的代表,但除此之外,我刚才看到:

  VARIABLENAME {} NodaTime.Instant 

这是不非常有用的。



如果我改变倍率返回一个常量字符串,字符串的的在调试器中显示的,因此它显然能够挑选了它的存在 - 它只是不希望在其正常状态下使用它



我决定在一个小的演示应用程序在本地重现此,这里的我想出的。 (请注意,在这个岗位的早期版本, DemoStruct 是一类和 DemoClass 根本不存在 - 我的错,但它说明了一些意见这看起来很奇怪,现在...)

 使用系统;使用System.Diagnostics程序
;
使用的System.Threading;

公共结构DemoStruct
{
公共字符串名称{; }

公共DemoStruct(字符串名称)
{
名称=名称;
}

公共重写字符串的ToString()
{
Thread.sleep代码(1000); //改变该看到不同的结果
$回报的结构:{}名称;
}
}

公共类DemoClass
{
公共字符串名称{; }

公共DemoClass(字符串名称)
{
名称=名称;
}

公共重写字符串的ToString()
{
Thread.sleep代码(1000); //改变该看到不同的结果
$回报类:{名};
}
}

公共类节目
{
静态无效的主要()
{
变种demoClass =新DemoClass( 富);
变种demoStruct =新DemoStruct(酒吧);
Debugger.Break();
}
}

在调试器,我现在看到的:

  demoClass {demoClass} 
demoStruct {结构:酒吧}

不过,如果我减少 Thread.sleep代码从1秒到900毫秒叫下来,还是有一个短暂的停顿,但后来我看到类:富作为值。这似乎并不重要的 Thread.sleep代码通话中 DemoStruct.ToString()有多长,它总是正确显示 - 和调试器显示的数值睡眠会完成之前。 (这是因为如果 Thread.sleep代码已禁用。)



现在 Instant.ToString( )在Noda时间不工作了相当多的,但它肯定不会采取整秒 - 所以想必有更多的条件导致调试放弃评估的ToString()电话。当然,这是一个结构呢。



我试着递归,看看它是否是一个堆栈限制,但这似乎不是如此。



所以,我怎么能找出什么是从充分评估停止VS Instant.ToString()?如下文所述, DebuggerDisplayAttribute 出现帮助,但不知道的为什么的,我永远不会在我需要的时候,当完全有信心我不知道。



更新



如果我使用的 DebuggerDisplayAttribute ,事情的变化:

  //对于这个问题... 
示例代码[DebuggerDisplay({的ToString()})]
公共类DemoClass

给我:

  demoClass评价超时

而我申请的时候它在Noda时间:

  [DebuggerDisplay({的ToString()})] 
公共结构即时

一个简单的测试程序显示我正确的结果:

 即时1970-01-01T00:00:00Z

所以大概在Noda时间问题是一些条件 DebuggerDisplayAttribute 确实的力通过 - 即使它不会通过强制超时。 (这是符合我的期望 Instant.ToString 很容易足够快,以避免超时。)



此的可能的是一个足够好的解决办法 - 但我还是想知道这是怎么回事,我是否可以更改代码只是为了避免把属性上都在不同的值类型野田佳彦时间。



奇妙而又奇妙



无论是混淆了调试器只迷有时。让我们创建一个类,它的持有的一个即时,并用它来满足自己的的ToString()方法:

 使用NodaTime;使用System.Diagnostics程序
;

公共类InstantWrapper
{
私人只读即时瞬间;

公共InstantWrapper(即时速溶)
{
this.instant =瞬间;
}

公共重写字符串的ToString()=> instant.ToString();
}

公共类节目
{
静态无效的主要()
{
无功瞬间= NodaConstants.UnixEpoch;
VAR包装=新InstantWrapper(即时);

Debugger.Break();
}
}

现在我最终看到:

 即时{} NodaTime.Instant 
{包装1970-01-01T00:00:00Z}

然而,在二连的意见建议,如果我改变 InstantWrapper 是一个结构,我得到:

 即时{} NodaTime.Instant 
{包装} InstantWrapper

因此,它的可以的评估 Instant.ToString() - 只要这是由另一个的ToString 方法......这是一个类中调用。的类/结构部分似乎基于所述变量的类型被显示是重要的,而不是代码需要
,以获得的结果来执行。



作为另一个例子,如果我们使用:

 对象盒装= NodaConstants.UnixEpoch; 



...那么它工作正常,显示正确的价值。 。颜色我困惑


解决方案

更新:



这个错误已经被。固定在Visual Studio 2015年更新2让我知道,如果你还在运行到评估使用更新2或更高版本结构值的ToString问题



原来的答复:



您正在运行到一个已知的bug /设计限制与Visual Studio 2015年,并呼吁结构类型的ToString。与 System.DateTimeSpan 时,这也可以被观察到。 System.DateTimeSpan.ToString()工程与Visual Studio 2013的评价窗口,但并不总是在2015年的工作。



如果您有兴趣在低级别的细节,这里发生了什么:



要评估的ToString ,调试器做什么的被称为功能评价。在大大简化计算,调试器暂停进程中的所有线程除了当前线程,更改当前线程到的ToString 功能方面,设置了一个隐藏的后卫断点,然后允许过程继续。当后卫命中断点,调试过程中恢复到以前的状态和函数的返回值被用来填充窗口。



要支持lambda表达式,我们不得不完全重写在Visual Studio 2015年CLR表达式求值在较高的水平,实现如下:




  1. 罗斯林产生MSIL代码表现形式/局部变量获取要显示在各种检查窗口中的值。

  2. 调试器解释的IL得到的结果。

  3. 如果有什么呼的指示,调试器执行
    功能评价如上所述。

  4. 调试器/罗斯林接受这个结果和格式入
    树该的显示给用户的样图。



  5. 由于IL的执行,调试器总是处理的复杂混合真正的和假的价值观。真正的价值其实在这个过程中被调试存在。假值只存在在调试过程中。要实现正确的语义结构,调试器总是需要一个推动结构值的IL堆栈时,使值的副本。复制的价值不再是一个真正的价值,现在只能在调试过程中存在。这意味着,如果我们以后需要执行的ToString 的功能评价,我们不能因为值不会在过程中存在。尝试并获得我们需要模拟的ToString 方法的执行价值。虽然我们可以效仿一些东西,也有很多局限性。例如,我们不能效仿本地代码,我们不能执行调用真正代表的值或反射值调用。



    对于所有的这一点这里是什么导致你所看到的各种行为:




    1. 调试器未评估 NodaTime.Instant。的ToString - >这是
      ,因为它是结构类型和toString的实现不能
      。通过上述的调试器来模拟

    2. Thread.sleep代码似乎采取当在
      结构被称为的ToString 零时间 - >这是因为仿真器执行的ToString
      Thread.sleep代码是一个本地方法,但模拟器是它意识到
      ,只是忽略呼叫。我们这样做是为了尝试,并得到一个价值
      展现给用户。延迟不会在这种情况下,有帮助的。

    3. DisplayAttibute(的ToString())的作品。 - >这是混乱的。 的ToString 的隐式调用和
      DebuggerDisplay 之间唯一的
      不同的是,任何时间隐含的的ToString出局
      评价将禁用为
      类所有隐的ToString 评估,直到接下来的调试会话。你可能会观察到
      行为。



    在设计问题/错误方面,这是我们计划地址在将来的Visual Studio版本。



    希望这将清除的东西了。让我知道如果您有更多问题。 : - )


    Environment: Visual Studio 2015 RTM. (I haven't tried older versions.)

    Recently, I've been debugging some of my Noda Time code, and I've noticed that when I've got a local variable of type NodaTime.Instant (one of the central struct types in Noda Time), the "Locals" and "Watch" windows don't appear to call its ToString() override. If I call ToString() explicitly in the watch window, I see the appropriate representation, but otherwise I just see:

    variableName       {NodaTime.Instant}
    

    which isn't very useful.

    If I change the override to return a constant string, the string is displayed in the debugger, so it's clearly able to pick up that it's there - it just doesn't want to use it in its "normal" state.

    I decided to reproduce this locally in a little demo app, and here's what I've come up with. (Note that in an early version of this post, DemoStruct was a class and DemoClass didn't exist at all - my fault, but it explains some comments which look odd now...)

    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();
        }
    }
    

    In the debugger, I now see:

    demoClass    {DemoClass}
    demoStruct   {Struct: Bar}
    

    However, if I reduce the Thread.Sleep call down from 1 second to 900ms, there's still a short pause, but then I see Class: Foo as the value. It doesn't seem to matter how long the Thread.Sleep call is in DemoStruct.ToString(), it's always displayed properly - and the debugger displays the value before the sleep would have completed. (It's as if Thread.Sleep is disabled.)

    Now Instant.ToString() in Noda Time does a fair amount of work, but it certainly doesn't take a whole second - so presumably there are more conditions that cause the debugger to give up evaluating a ToString() call. And of course it's a struct anyway.

    I've tried recursing to see whether it's a stack limit, but that appears not to be the case.

    So, how can I work out what's stopping VS from fully evaluating Instant.ToString()? As noted below, DebuggerDisplayAttribute appears to help, but without knowing why, I'm never going to be entirely confident in when I need it and when I don't.

    Update

    If I use DebuggerDisplayAttribute, things change:

    // For the sample code in the question...
    [DebuggerDisplay("{ToString()}")]
    public class DemoClass
    

    gives me:

    demoClass      Evaluation timed out
    

    Whereas when I apply it in Noda Time:

    [DebuggerDisplay("{ToString()}")]
    public struct Instant
    

    a simple test app shows me the right result:

    instant    "1970-01-01T00:00:00Z"
    

    So presumably the problem in Noda Time is some condition that DebuggerDisplayAttribute does force through - even though it doesn't force through timeouts. (This would be in line with my expectation that Instant.ToString is easily fast enough to avoid a timeout.)

    This may be a good enough solution - but I'd still like to know what's going on, and whether I can change the code simply to avoid having to put the attribute on all the various value types in Noda Time.

    Curiouser and curiouser

    Whatever is confusing the debugger only confuses it sometimes. Let's create a class which holds an Instant and uses it for its own ToString() method:

    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();
        }
    }
    

    Now I end up seeing:

    instant    {NodaTime.Instant}
    wrapper    {1970-01-01T00:00:00Z}
    

    However, at the suggestion of Eren in comments, if I change InstantWrapper to be a struct, I get:

    instant    {NodaTime.Instant}
    wrapper    {InstantWrapper}
    

    So it can evaluate Instant.ToString() - so long as that's invoked by another ToString method... which is within a class. The class/struct part seems to be important based on the type of the variable being displayed, not what code needs to be executed in order to get the result.

    As another example of this, if we use:

    object boxed = NodaConstants.UnixEpoch;
    

    ... then it works fine, displaying the right value. Colour me confused.

    解决方案

    Update:

    This bug has been fixed in Visual Studio 2015 Update 2. Let me know if you are still running into problems evaluating ToString on struct values using Update 2 or later.

    Original Answer:

    You are running into a known bug/design limitation with Visual Studio 2015 and calling ToString on struct types. This can also be observed when dealing with System.DateTimeSpan. System.DateTimeSpan.ToString() works in the evaluation windows with Visual Studio 2013, but does not always work in 2015.

    If you are interested in the low level details, here's what's going on:

    To evaluate ToString, the debugger does what's known as "function evaluation". In greatly simplified terms, the debugger suspends all threads in the process except the current thread, changes the context of the current thread to the ToString function, sets a hidden guard breakpoint, then allows the process to continue. When the guard breakpoint is hit, the debugger restores the process to its previous state and the return value of the function is used to populate the window.

    To support lambda expressions, we had to completely rewrite the CLR Expression Evaluator in Visual Studio 2015. At a high level, the implementation is:

    1. Roslyn generates MSIL code for expressions/local variables to get the values to be displayed in the various inspection windows.
    2. The debugger interprets the IL to get the result.
    3. If there are any "call" instructions, the debugger executes a function evaluation as described above.
    4. The debugger/roslyn takes this result and formats it into the tree-like view that's shown to the user.

    Because of the execution of IL, the debugger is always dealing with a complicated mix of "real" and "fake" values. Real values actually exist in the process being debugged. Fake values only exist in the debugger process. To implement proper struct semantics, the debugger always needs to make a copy of the value when pushing a struct value to the IL stack. The copied value is no longer a "real" value and now only exists in the debugger process. That means if we later need to perform function evaluation of ToString, we can't because the value doesn't exist in the process. To try and get the value we need to emulate execution of the ToString method. While we can emulate some things, there are many limitations. For example, we can't emulate native code and we can't execute calls to "real" delegate values or calls on reflection values.

    With all of that in mind, here is what's causing the various behaviors you are seeing:

    1. The debugger isn't evaluating NodaTime.Instant.ToString -> This is because it is struct type and the implementation of ToString can't be emulated by the debugger as described above.
    2. Thread.Sleep seems to take zero time when called by ToString on a struct -> This is because the emulator is executing ToString. Thread.Sleep is a native method, but the emulator is aware of it and just ignores the call. We do this to try and get a value to show to the user. A delay wouldn't be helpful in this case.
    3. DisplayAttibute("ToString()") works. -> That is confusing. The only difference between the implicit calling of ToString and DebuggerDisplay is that any time-outs of the implicit ToString evaluation will disable all implicit ToString evaluations for that type until the next debug session. You may be observing that behavior.

    In terms of the design problem/bug, this is something we are planning to address in a future release of Visual Studio.

    Hopefully that clears things up. Let me know if you have more questions. :-)

    这篇关于是什么让Visual Studio调试器停止评估的ToString覆盖?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆