0

This question is related to my previous question How to get a IDictionary<string, object> of the parameters previous method called in C#?. I wrote the code, but there is still a missing piece. How do I get the values from the parameters?

If the following code is executed, the output only shows the parameter's names, but not the values.

using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace Question {
    internal class Program {
        public static void Main(string[] args) {
            var impl = new Implementation();
            var otherClass = new OtherClass { Name = "John", Age = 100 };
            impl.MethodA(1, "two", otherClass);
        }
    }

    internal class Implementation {
        public void MethodA(int param1, string param2, OtherClass param3) {
            Logger.LogParameters();
        }
    }

    internal class OtherClass {
        public string Name { get; set; }
        public int Age { get; set; }
    }

    internal class Logger {
        public static void LogParameters() {
            var parameters = GetParametersFromPreviousMethodCall();
            foreach (var keyValuePair in parameters)
                Console.WriteLine(keyValuePair.Key + "=" + keyValuePair.Value);
        }

        private static IDictionary<string, object> GetParametersFromPreviousMethodCall() {
            var stackTrace = new StackTrace();
            var frame = stackTrace.GetFrame(2);
            var method = frame.GetMethod();

            var dictionary = new Dictionary<string, object>();
            foreach (var parameterInfo in method.GetParameters())
                dictionary.Add(parameterInfo.Name, parameterInfo.DefaultValue);
            return dictionary;
        }
    }
}
Community
  • 1
  • 1
rcarrillopadron
  • 459
  • 2
  • 6
  • 19

2 Answers2

3

Your code is probably a dead-end. There is nothing in the stack frame that lets you get parameter values.

However, it is completely possible to do this task. What you want is very similar to writing a profiler. You would want to inject code into any method you want to log to vector off its parameters. Let's say that you started with a class like this:

public class ParameterBlob {
    public ParameterInfo Info { get; set; }
    public object Value { get; set; }
}

And let's say you have a method somewhere like this:

public static void LogMethodCall(MethodInfo method, param ParameterBlob[] parameterBlobs) { /* ... */ }

Here is more or less what you want to inject:

MethodInfo methodInfo = MyLogging.GetMyMethodInfo();
ParameterBlob[] blobs = new ParameterBlobs[MyLogging.GetMyParameterCount(methodInfo);
ParameterBlob blob = new ParameterBlob();
blob.Info = MyLogging.GetParameterInfo(methodInfo, 0);
blob.Value = param0; // More on this
blobs[0] = blob;
blob = new ParameterBlob();
blob.Info = MyLogging.GetParameterInfo(methodInfo, 1);
blob.Value = param1; // More on this
blobs[1] = blob;
// ...
blob = new ParameterBlob();
blob.Info = MyLogging.GetParameterInfo(methodInfo, n);
blob.Value = paramn; // more on this
blobs[n] = blob;
MyLogging.LogMethodCall(methodInfo, blobs);

So those lines that say "More on this"? You can't actually write them. But you could write a sample routine that refers to its own parameters to do it. What you would have is an ldarg instruction and a stloc instruction (and a few others in between). The point is, write it in C# then use the compiler and ILDASM to show you the correct code that you would need for one parameter, then you could write a routine to generate that CIL for you, which you would then plug into the .NET profiling API to attach to any routine you want.

See the article Rewrite MSIL Code on the Fly with the .NET Framework Profiling API for more information on that.

You probably also want to use an attribute to mark methods as loggable or not.

The only issue is that you have to have runtime access to do this, which you might not. Are you totally out of luck? No.

By using Cecil, you can have access to the entire assembly before it is run and preprocess it to inject the logging calls for you. Cecil is incredibly straightforward and it should be a couple of days work to rewrite any assembly to include logging calls. Less if you can know a priori that you're doing this in the target assembly and have the appropriate references already set up. Essentially, you will visit every method in the assembly and if it is loggable, you will inject the CIL to log all its parameters, just like the sample above.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
plinth
  • 48,267
  • 11
  • 78
  • 120
2

According to a prior question (How can I get the values of the parameters of a calling method?), it is not possible through the StackTrace.

AOP is your friend.

Community
  • 1
  • 1
philipproplesch
  • 2,127
  • 17
  • 20