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:
- Roslyn generates MSIL code for expressions/local variables to get the values to be displayed in the various inspection windows.
- The debugger interprets the IL to get the result.
- If there are any “call” instructions, the debugger executes a
function evaluation as described above. - 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:
- 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. Thread.Sleepseems to take zero time when called byToStringon a
struct -> This is because the emulator is executingToString.
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.DisplayAttibute("ToString()")works. -> That is confusing. The only
difference between the implicit calling ofToStringand
DebuggerDisplayis that any time-outs of the implicitToString
evaluation will disable all implicitToStringevaluations 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. 🙂