What does a debugger author need to do to support func-eval?

I've mentioned func-eval (aka property eval) is evil for end-users; but it's also evil if you want to write a debugger that uses func-eval.

For example, let's say you're writing your own managed debugger and you have a watch window, and you want to eval property-getters and ToString() calls on items.  Since VS does this, you can rest assured that it's indeed possible, but it does have some challenges:

  1. Func-eval is asynchronous. See here for how to do a func-eval. Quick summary is that you must setup the eval (ICorDebugEval::CallFunction), Continue the process, and then wait for an EvalComplete callback with your result.  In whidbey, ICDEval's result generally gives you back an ICorDebugHandleValue, which is like a strong-reference version of ICorDebugReferenceValue. This means you can hold onto that value and dereference it across multiple Continues. MDbg maps that value to the pseudo-value $result. Pre-whidbey, you had to play some very very evil games to make this work.
  2. You need to make some policy decisions.
    a) Other debug events may come in the meantime and you need to decide how to handle those.  For eg, if you hit a breakpoint, do you want to silently ignore it finish the eval, or do you want to enter a nested break state? It's your debugger and you get to decide what policy you want.
    b) Also, do you suspend other threads? If no, they will move on you when you eval and thus your eval becomes even more invasive. If yes, you may get deadlocks.
  3. You need to beware of neutering and refresh ICDValues. Many ICorDebug*Value objects become invalid once you continue the process. This means that you need to refresh all the outstanding values by getting a new ICDValue instance.
    Your debugger knows how it found everything it's displaying. So for each value, you need to reget its corresponding ICDValue. This is ugly, but there are some decent OOP techniques to do this somewhat cleanly. For example, you could wrap ICDValues in your own class, which remembers how the val was obtained and has some virtual Refresh() method to reget a new underlying ICDValue. This lets you do other things too, such as track when the value has changed and then display it differently (such as in red).
  4. You need to decide how you want to deal with side-effects. For example, suppose you have:
        class Foo {
            int m_x = 4; 
            int Prop1 { get { return m_x++; } }
            int Prop2 { get { return m_x++; } }
        }
    If you eval all the members, you change the members. Evaling the properties first won't help either.

I also think that all of these problems are innate challenges of func-eval. Regardless of how we carved the APIs, we would have these sort of problems. In some cases, we could shift the APIs around to solve a problem, but it would create another problem.

The bottom line is automatically doing func-evals for getters / ToString() as an integrated part of inspection is actually a very complicated thing. The VS debugger folks deserver a lot of credit for making this work so seamlessly. If you are thinking of doing this, play around with VS's debugger to get a feel for what it could look like and what sort of issues you may run into.