Search code examples
c#debuggingvisual-studio-2015conditional-breakpoint

How to use Environment.StackTrace in a conditional expression for a breakpoint in VS2015


I'm trying to debug an issue and it would really help if I could have a breakpoint in the getter of a property, but I don't need to break every time it is called from a CanExecute call for some button in my UI. My thought was that I could simply condition the breakpoint on the call stack not containing that string, but that seems to not work. The best way I can think of to explain this is with an image of the settings and the output from the breakpoint being hit.

BreakpointSettings

As you can see in the image, the Conditional settings for the breakpoint is outputted directly when it is hit, and in the output you can see it being hit a few times .get(): true - correctly. However, when it took this screenshot it was being hit with the condition being false, as seen in the output. The program halted as the breakpoint was hit, incorrectly.

Am I doing something wrong - is this possible at all? Seems to me like a bug in VS2015, the output can evaluate the bool correctly, why can't the breakpoint condition do that?

Edit, to annotate the image in case it gets lost sometime. I have a breakpoint in the get method of a property that simply returns the underlying field. The Breakpoint settings show that there is a Condition on the breakpoint that defines it such that it should only be hit when the expression !Environment.StackTrace.Contains("CanExecute") returns true, i.e. only break when the Stack Trace does not contain the "CanExecute" string. The Action part of the breakpoint settings simply outputs the function name and the conditional expression using $FUNCTION: {!Environment.StackTrace.Contains("CanExecute")}. The action is set to not continue the execution.


Solution

  • I don't know exactly why the stack trace information is not available inside breakpoint conditions, but there's a crazy workaround/hack you can do as an extension to a technique outlined in another answer

    It won't work as "cleanly" if you use auto properties as it requires two breakpoints, and auto properties only have a single point to bind a breakpoint to but I'd imagine this isn't as useful for an auto property anyway.


    Place the first breakpoint on the opening curly brace of the get method (set your cursor on the curly brace and hit F9). Set an action on this breakpoint; set "Log a message to Output Window:" to

    {System.AppDomain.CurrentDomain.SetData("break", !Environment.StackTrace.Contains("CanExecute"))}
    

    and ensure Continue execution is checked.

    Place the second breakpoint on the return statement (set your cursor on the curly brace and hit F9). Set the condition on this breakpoint:

    (bool)System.AppDomain.CurrentDomain.GetData("break")
    

    Depending on your formatting, this may require the aid of the "Breakpoints" window Debug > Windows > Breakpoints (Ctrl+Alt+B) in case the curly brace and the return statement are on the same line. You can edit the action/condition by right clicking on the breakpoint in the "Breakpoints" window and selecting Settings

    The cast to bool is required as GetData() returns an object and the conditional breakpoint won't do the cast for you.


    It isn't pretty, and it doesn't work very well in a multi-threaded environment due to the global state, but it can be useful in a pinch. If you need multiple "conditional breakpoints" in this manner, make sure to use a different key in SetData()/GetData().

    However, if you can, it's usually (depending on your compile time and access to the code vs debug symbols) quicker/easier to just temporarily edit the code to put the condition you want to break on.

    e.g.

    public Foo Selected
    {
        get
        {
            if (!Environment.StackTrace.Contains("CanExecute"))
                System.Diagnostics.Debugger.Break();
    
            return _selected;
        }
    }
    

    *In the case of auto properties, you can use a pair of action breakpoints in the CanExecute() method before the property access to set "break" to true, and then after to set "break" to false, and for the condition use

    (bool?)System.AppDomain.CurrentDomain.GetData("break") != false
    

    to ensure it still breaks before and after CanExecute() but not during.