5

I would like to write a reusable function I can call within any method to log a snapshot of all the local variables. For example:

    void somemethod()
    {
        int a = 1;
        string s = "something";
        dumpLocalVariables("step 1", MethodInfo.GetCurrentMethod(), this);

        a++;
        string t = s + "else";
        dumpLocalVariables("step 2", MethodInfo.GetCurrentMethod(), this);
    }

I would like to get a console output like this:

step 1
    Int32 a = 1 
    String s = something
step 2
    Int32 a = 2
    String s = something
    String t = somethingelse

I want to avoid providing a specific list of local variable names.

The closest I could find was MethodInfo.GetCurrentMethod().GetMethodBody().LocalVariables, but I do not know how to access the values of the local variables using reflection.

void dumpLocalVariables(string context, MethodBase currentMethod, object obj)
{
    Console.WriteLine(context);
    MethodBody methodBody = currentMethod.GetMethodBody();
    foreach (LocalVariableInfo lvi in methodBody.LocalVariables)
    {
        string variableType = lvi.LocalType.Name;
        // how do I get this?
        string variableName = "variableNameHere";
        // how do I get this?    
        string variableValue = "variableValueHere";
        Console.WriteLine("   " + variableType  + " " + variableName + 
            " = " + variableValue);
    }
}

The reflection API seems well suited for static analysis, but not for dynamic analysis like this. For instance, the variable t is not in scope during the first call to dumpLocalVariables, but it still appears in the LocalVariables property of the MethodBody.

I suspect there is a debugging API that I am overlooking. How does Developer Studio populate the "locals" tab when at a breakpoint? Is there a way to do something similar at runtime?

EDIT:

I can see in ILSpy that my example class uses IL codes like ldloc.0 and ldloc.1 to get to the first and second local variable.

.locals init (
    [0] int32 a
    [1] string s
    [2] string t
)

and later

IL_001b: ldloc.0  // this is a
IL_001c: ldc.i4.1
IL_001d: add
IL_001e: stloc.0
IL_001f: ldloc.1  // this is s
IL_0020: ldstr "else"
IL_0025: call string string::Concat(string, string)
IL_002a: stloc.2  // this is t

Maybe I could use some sort of proxy-like mechanism that lets me do the same thing? I don't mind if the call to my reusable method is messy, I just want something I can paste into any code block without a lot of hand-editing.

hsmiths
  • 1,257
  • 13
  • 17
  • 1
    "I suspect there is a debugging API that I am overlooking" : `System.Diagnostics.Debugger` – H H May 08 '11 at 18:34
  • @Henk: can you clarify that? I didn't know `Debugger` class had anything useful other than `IsAttached` and `Break()`. – vgru May 08 '11 at 18:44
  • 1
    Possible duplicates: [Is there a way to examine the Stack variables at runtime in c#?](http://stackoverflow.com/questions/5130414/is-there-a-way-to-examine-the-stack-variables-at-runtime-in-c), [C# How to dump all variables & current values during runtime](http://stackoverflow.com/questions/1552478/c-how-to-dump-all-variables-current-values-during-runtime). – vgru May 08 '11 at 18:49
  • possible duplicate of [Is there a simple way to obtain all the local variables in the current stack frame in C# (or CIL)](http://stackoverflow.com/questions/5135939/is-there-a-simple-way-to-obtain-all-the-local-variables-in-the-current-stack-fram) – casperOne May 08 '11 at 19:02
  • @Hank Holterman & @Groo: `System.Diagnostics.Debugger` offers nothing that would help in this case. – casperOne May 08 '11 at 19:03
  • Why would someone up-vote a comment like ""I suspect there is a debugging API that I am overlooking" : System.Diagnostics.Debugger", when it provides no useful information? – Jeff May 08 '11 at 19:10
  • I up-voted it, and at the time it was the only response. It got me looking at System.Diagnostics which led to places like Environment.StackTrace. Unfortunately that didn't help much. – hsmiths May 08 '11 at 19:23

3 Answers3

8

See this related question:

Is there a simple way to obtain all the local variables in the current stack frame in C# (or CIL)

The short answer is: you can't get the values of the local variables because they're allocated directly on the stack at runtime, and thus are not available via reflection. The only way to do this is via the debugger API...and it's far from trivial. Further, this would only work if your custom debugger is actually attached to the process.

A better, more feasible option might be via assembly weaving. You said you don't want to have the method have to know the specific names of local variables to access when logging their values. I would suggest creating two methods:

static void LogVariables();

and

static void LogVariables(params string[] names, params object[] values);

Add a post build task that calls an assembly weaving routine that swaps out the first LogVariables call with the second, but explicitly providing the variable names/values to the method. You can write this routine to modify the assembly using Mono Cecil (there are other tools too that can do this).

http://www.mono-project.com/Cecil

Community
  • 1
  • 1
Jeff
  • 35,755
  • 15
  • 108
  • 220
  • This is the easiest answer so far, but is not what I was looking for. I would still like to come up with something that can be inserted and compiled in the "normal" way. – hsmiths May 08 '11 at 23:21
  • I guess there are no more ideas so I am reluctantly accepting this answer. I think it is at least feasible to write something using the cecil assembly at runtime to perform this sort of logging without the need for a post-compile tweak. – hsmiths May 09 '11 at 22:53
  • if is possible to get a list of local variables why is not possible to generate dynamic code by Reflection.Emit to dump it values? – gdbdable Sep 13 '13 at 08:11
2

It is possible by using external debugger for managed code. See "managed debugger sample" for how it is done: http://blogs.msdn.com/b/jmstall/archive/2004/09/30/236281.aspx (includes link to the sample and more information)

Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
  • Thank you for the link. The mdbg stuff is interesting reading. It will take a lot more work up front, but I suspect I can cook up something based on this to do what I want. I do not think I am alone in recognizing the pattern of needing to dump locals and having to do a one-off each and every time. – hsmiths May 08 '11 at 23:21
  • The mdbg v2.0 download that the blog links to is no longer available. I found a download for mdbg v4.0 [here](https://www.microsoft.com/en-us/download/confirmation.aspx?id=2282). (Who knows how long it'll be there—it's almost three years old by now.) – InteXX Aug 01 '19 at 08:19
0

Consider to write minidumps using a custom PostSharp aspect (with IL transformation).

A shared debuging engine library, written in C#. is available on NuGet as Microsoft.Samples.Debugging.MdbgEngine.

The code of PostSharp aspect is available on GitHub as part of the PADRE ( Pluggable Automatic Debugging and Reporting Engine)repository

Michael Freidgeim
  • 26,542
  • 16
  • 152
  • 170