Testing whether to launch debugger based on build & command line arguments I have a C# application that may be spawned by another process using Shell or its equivalent. As such, it is not trivial to simply F5 into a breakpoint nor launching an external program will work since a brand new process get spawned. Thus it is problematic especially when doing integration tests or diagnosing unusual problem that requires the same context than the one provided by automated tests.

I thought of passing in a debug flag but I don't want it to be accepted in a release build. I adapted the code from this SO thread by making it a complete function that returns a private struct. The important part is the check within the Main procedure where we inspect whether a -debug flag has been passed via the command line argument and if so, confirm this is a debug build, not a release build. This seems to work well enough; but I want to know if this is reasonably good way to enable debugging on demand without exposing too much. Thus, the review is to see if this could be improved upon:

Note: I don't currently use all the member of the struct in the code; I don't know if there may be other use yet and didn't bother eliminating the unused members.

Note #2: I belatedly realized that I could have wrapped the check and the supporting function inside a #if DEBUG block, which would also prevent the code from ever being included in a release build. However, it got me thinking if there might be legitimate scenario to launch debugger on a release build (even though the debug information will be sparse due to optimizations), so we can leave the question with the expectation that it could be used in release. Would it be an acceptable way or are there better ways? 

internal class Program
{
    [STAThread]
    private static int Main(string[] args)
    {
        if (args.Any(a =&gt; a == "--debug") &amp;&amp; GetDebugData().BuildType.ToLowerInvariant() == "debug")
        {
            Debugger.Launch();
        }

        // do something interesting....
    }

    private struct AssemblyDebugData
    {
        internal bool HasDebuggableAttribute;
        internal bool IsJITOptimized;
        internal string BuildType;
        internal string DebugOutput;
    }

    private static AssemblyDebugData GetDebugData()
    {
        AssemblyDebugData result = new AssemblyDebugData();

        object[] attribs = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(DebuggableAttribute), false);

        // If the 'DebuggableAttribute' is not found then it is definitely an OPTIMIZED build
        if (attribs.Length &gt; 0)
        {
            // Just because the 'DebuggableAttribute' is found doesn't necessarily mean
            // it's a DEBUG build; we have to check the JIT Optimization flag
            // i.e. it could have the "generate PDB" checked but have JIT Optimization enabled
            DebuggableAttribute debuggableAttribute = attribs[0] as DebuggableAttribute;
            if (debuggableAttribute != null)
            {
                result.HasDebuggableAttribute = true;
                result.IsJITOptimized = !debuggableAttribute.IsJITOptimizerDisabled;
                result.BuildType = debuggableAttribute.IsJITOptimizerDisabled ? "Debug" : "Release";

                // check for Debug Output "full" or "pdb-only"
                result.DebugOutput = (debuggableAttribute.DebuggingFlags &amp;
                               DebuggableAttribute.DebuggingModes.Default) !=
                              DebuggableAttribute.DebuggingModes.None
                    ? "Full" : "pdb-only";
            }
        }
        else
        {
            result.IsJITOptimized = true;
            result.BuildType = "Release";
        }

        return result;
    }
}
Use #if Debug. It will be a lot more expected to have to remove/disable that than to change the if and remember to remove the then-dead GetDebugData method. (Principle of Least Astonishment, right?)
Don't specify STAThread. It's just useless. First, it would be extremely surprising if someone tries to use multithreading 10 years down the road and it failed. Second, it doesn't provide any significant overhead (if any at all) to not use this. Finally, console apps run single-threaded by default. There is no excuse to disable multi-threading in the whole app. There is excuse to disable multi-threading in parts that can't be multi-threaded because of whatever (maybe it calls into code running on a STA thread), but those instances can be handled on a case-by-case basis with explanation in comments.


Now, assuming for some reason I haven't comprehended, you absolutely have to use this.


You are defining whether a build is a "debug" or "release" based on whether the code is optimized. That has nothing to do with Visual Studio's definition (and therefore the standard definition) of what a "debug" or "release" build is. That and the output paths are the only changes I can think of off-hand between the default "debug" and "release" candidates. However, you could define a "debug" build as one compiled against C# 7.2 and a "release" build as one compiled against C# 6 and leave optimization on or off in both for whatever reason. I wouldn't use the terminology "debug" vs. "release" here; I'd just check whether debug symbols were enabled when I determined whether to kick off the debugger.
If you change what I suggest above, I'd recommend renaming DebugOutput to just Output and assigning it whether the code is optimized or not. I can compile as None, Full, Pdb-Only, Portable, or Embedded with JIT-optimization enabled anyway.