views:

327

answers:

4

I would like to do something like this:

public MyFunction(int integerParameter, string stringParameter){
    //Do this:
    LogParameters();
    //Instead of this:
    //Log.Debug("integerParameter: " + integerParameter + 
    //          ", stringParameter: " + stringParameter);

}

public LogParameters(){
    //Look up 1 level in the call stack (if possible),
    //Programmatically loop through the function's parameters/values
    //and log them to a file (with the function name as well).
    //If I can pass a MethodInfo instead of analyzing the call stack, great.
}

I'm not even sure what I want to do is possible, but it would be very nice to be able to automatically output parameter names/values at runtime to a file without explicitly writing the code to log them.

Thanks!

+5  A: 

It's theoretically possible with a debug build and optimization turned off, but practially speaking I suggest you want some source code rewriting pass.

People are going to keep telling you reflection will work when it won't, so here is the function that's actually capable of getting argument values. It's not likely to work reliably with optimization enabled (for example, there might not even BE a stack frame when inlining is on) and getting a debugger installed so you can call that function won't be nearly as simple as you were hoping.

Ben Voigt
Actually - very good point on the optimization - I was assuming this question was related to debug instrumentation....
Paul Kohler
@Ben Voigt - do you have a sample usage for `ICorDebugILFrame::GetArgument`?
Paul Kohler
@Ben Voigt - I'd like to see a sample usage as well, if you can provide it. Thanks for the answer!
Pwninstein
+2  A: 
StackTrace stackTrace = new StackTrace();
ParameterInfo[] parameters = stackTrace.GetFrame(1).GetMethod().GetParameters();

Note, GetFrame(1) gets the calling method rather than the current method. This should give you your desired results and allow you to execute the code below in LogParameters().

You would need to call LogParameters like below since you wouldn't be able to get the reflected values of integerParameter and stringParameter from ParameterInfo.

LogParameters(integerParameter, stringParameter);
Taylor Leese
Definately use a StackTrace to get the relevant StackFrame, this even allows declaring the "logging" subroutine with an optional "depth" argument. +1
M.A. Hanin
oh great, people deleted the answer where I first mentioned StackTrace.GetFrame(1) and its limitations.
Ben Voigt
+1  A: 

Unless you use the debugger api, you cannot loop through parameter values of a different method in the call stack. Though you can get the parameter names from the callstack (as others have mentioned).

The closest thing would be:

public MyFunction(int integerParameter, string stringParameter){
    LogParameters(integerParameter, stringParameter);
}

public void LogParameters(params object[] values){
    // Get the parameter names from callstack and log names/values
}
Peter Lillevold
you CAN, you just need the debugging API, not reflection
Ben Voigt
+1  A: 

I realize people linked to other questions which mentioned PostSharp, but I couldn't help posting the code that solved my problem (using PostSharp) so other people could benefit from it.

class Program {
    static void Main(string[] args) {
        Trace.Listeners.Add(new TextWriterTraceListener(Console.Out));
        new MyClass().MyMethod(44, "asdf qwer 1234", 3.14f, true);
        Console.ReadKey();
    }
}
public class MyClass {
    public MyClass() {
    }
    [Trace("Debug")]
    public int MyMethod(int x, string someString, float anotherFloat, bool theBool) {
        return x + 1;
    }
}
[Serializable]
public sealed class TraceAttribute : OnMethodBoundaryAspect {
    private readonly string category;

    public TraceAttribute(string category) {
        this.category = category;
    }

    public string Category { get { return category; } }

    public override void OnEntry(MethodExecutionArgs args) {
        Trace.WriteLine(string.Format("Entering {0}.{1}.", 
                                      args.Method.DeclaringType.Name, 
                                      args.Method.Name), category);

        for (int x = 0; x < args.Arguments.Count; x++) {
            Trace.WriteLine(args.Method.GetParameters()[x].Name + " = " + 
                            args.Arguments.GetArgument(x));
        }
    }

    public override void OnExit(MethodExecutionArgs args) {
        Trace.WriteLine("Return Value: " + args.ReturnValue);

        Trace.WriteLine(string.Format("Leaving {0}.{1}.", 
                                      args.Method.DeclaringType.Name, 
                                      args.Method.Name), category);
    }
} 

Simply adding the Trace attribute to a method will cause very nice debugging information to be output, like so:

Debug: Entering MyClass.MyMethod. 
x = 44
someString = asdf qwer 1234
anotherFloat = 3.14
theBool = True
Return Value: 45
Debug: Leaving MyClass.MyMethod.
Pwninstein