28

Say I have a method:

 public void SomeMethod(String p1, String p2, int p3)
 {

 #if DEBUG
    object[] args = GetArguments();
    LogParamaters(args);
 #endif

     // Do Normal stuff in the method
 }

Is there a way to retrieve an array of the arguments passed into the method, so that they can be logged?

I have a large number of methods and want to avoid manually passing the arguments by name to the logger, as human error will inevitably creep in.

I'm guessing it will involve reflection in some form - which is fine, as it will only be used for debugging purposes.

Update

A little more information:

I can't change the method signature of SomeMethod, as it is exposed as a WebMethod and has to replicate the legacy system it is impersonating.

The legacy system already logs the arguments that are passed in. To start with the new implementation will wrap the legacy system, so I'm looking to log the parameters coming into the C# version, so that I can verify the right parameters are passed in in the right order.

I'm just looking to log the argument values and order, not their names.

David Henderson
  • 1,185
  • 1
  • 10
  • 16

9 Answers9

7

If you use Postsharp you can simply add an attribute to the method you want to log. Within this attribute you can write the logging code and also will provide the arguments you need. This is known as cross cutting concerns and AOP (Aspect orientated programming)

aqwert
  • 10,559
  • 2
  • 41
  • 61
5

I am unsure if the API to access the call stack provides a means to get the argument list.

However there are ways to inject IL to intercept method calls and execute custom code.

The Library I use frequently is PostSharp by Gael Fraiteur, it includes an application that runs postbuild and injects IL in your output assemblies depending on the Aspects that you are using. There are attributes with which you can decorate assemblies, types, or individual methods. For instance:

[Serializable]
public sealed class LoggingAttribute : OnMethodBoundaryAspect
{
    public override void OnEntry(MethodExecutionArgs eventArgs)
    {
        Console.WriteLine("Entering {0} {1} {2}",
                          eventArgs.Method.ReflectedType.Name,
                          eventArgs.Method,
                          string.Join(", ", eventArgs.Arguments.ToArray()));

        eventArgs.MethodExecutionTag = DateTime.Now.Ticks;
    }

    public override void OnExit(MethodExecutionArgs eventArgs)
    {
        long elapsedTicks = DateTime.Now.Ticks - (long) eventArgs.MethodExecutionTag;
        TimeSpan ts = TimeSpan.FromTicks(elapsedTicks);

        Console.WriteLine("Leaving {0} {1} after {2}ms",
                          eventArgs.Method.ReflectedType.Name,
                          eventArgs.Method,
                          ts.TotalMilliseconds);
    }
}

After this you can just decorate the method you want with this Attribute:

[Logging]
public void SomeMethod(String p1, String p2, int p3) 
{
   //..
}
Anirudha Gupta
  • 9,073
  • 9
  • 54
  • 79
Yannick Motton
  • 34,761
  • 4
  • 39
  • 55
2

Well, if you just want to pass the values, you can cheat and define an object array:

public static void LogParameters(params object[] vals)
{

}

This will incur boxing on value types and also not give you any parameter names, however.

Say I have a method:

 public void SomeMethod(String p1, String p2, int p3) 
 { 

 #if DEBUG 
    LogParamaters(p1, p2, p3); 
 #endif 

     // Do Normal stuff in the method 
 } 

Update: unfortunately reflection will not do it all automatically for you. You will need to provide the values, but you can use reflection to provide the param names/types:

How can you get the names of method parameters?

So the method sig would change to something like:

public static void LogParameters(string[] methodNames, params object[] vals)
{ }

Then you can enforce/assume that each index in each collection tallies, such that methodNames[0] has the value vals[0].

Community
  • 1
  • 1
Adam Houldsworth
  • 63,413
  • 11
  • 150
  • 187
  • If possible, I'm trying to avoid writing the param names in the LogParameters call, so aiming for something like: LogParamaters(GetMethodParams()); where GetMethodParams() would return the paramaters array. I'm not too bothered about the boxing overhead. – David Henderson Jul 20 '10 at 09:33
  • I've amended my answer, but as others have said there are frameworks available for appending the necessary code as a compiler step - meaning you don't need to write much of it manually. – Adam Houldsworth Jul 20 '10 at 09:37
  • Of course the only downside with PostSharp is it isn't free and you still need to edit the code to place the attribute. – Adam Houldsworth Jul 20 '10 at 09:42
1

Well params help with the log call, but won't help the existing method signatures. Logging using an AOP framework might be a more productive approach?

si618
  • 16,580
  • 12
  • 67
  • 84
1

Sure can ...check out this post, it gets the actual values of the params. how to enumerate passed method parameters

Community
  • 1
  • 1
raulucian
  • 35
  • 5
0

There's some functionality with the dynamic type system that can do it, but then your class needs to inherit from the dynamic base classes

Johann Strydom
  • 1,482
  • 14
  • 18
  • Did you even read the question? He wants to log all parameters coming into a function and would like to access that data. – Oded Jul 20 '10 at 09:28
0

might not work in some scenarios but should get you started :)

class Program
{
    static void Main(string[] args)
    {
        M1("test");
        M2("test", "test2");
        M3("test", "test2", 1);

        Console.ReadKey();
    }

    static void M1(string p1)
    {
        Log(MethodBase.GetCurrentMethod());
    }

    static void M2(string p1, string p2)
    {
        Log(MethodBase.GetCurrentMethod());
    }

    static void M3(string p1, string p2, int p3)
    {
        Log(MethodBase.GetCurrentMethod());
    }

    static void Log(MethodBase method)
    {
        Console.WriteLine("Method: {0}", method.Name);
        foreach (ParameterInfo param in method.GetParameters())
        {
            Console.WriteLine("ParameterName: {0}, ParameterType: {1}", param.Name, param.ParameterType.Name);
        }
    }
}
devnull
  • 2,790
  • 22
  • 25
0

As long as you know what types to expect you could log them in an SQL database. Write a method that does a type check, and then fills the appropriate DB column with the parameter (argument) value. If you have a custom type then you can use the type name and save that as string in it's own special column.

-Edit

Also, using the MethodBase.Name extension method, you could associate your parameters with the method that took them as arguments as mentioned in another post below. Be a handy way of keeping track of all methods used, and with which arguments, and of which type.

Is this even vaguely a good idea? :)

Alex
  • 4,844
  • 7
  • 44
  • 58
0

Here's what I came up with as a solution:

PostSharp or another AOP solution wasn't really practical in this situation, so unfortunately I had to abandon that idea.

It appears that while it is possible to parameter names and types using reflection, the only way to access the runtime values is with a debugger attached.

See here for more info:

StackOverflow

microsoft.public.dotnet.framework

So that still left me with the problem of ~50 methods that needed this logging adding by hand.

Reflection to the rescue...

public String GetMethodParameterArray()
    {
        var output = new StringBuilder();
        output.AppendLine();

        Type t = typeof(API);
        foreach (var mi in t.GetMethods())
        {
                var argsLine = new StringBuilder();
                bool isFirst = true;
                argsLine.Append("object[] args = {");
                var args = mi.GetParameters();

                foreach (var pi in args)
                {
                    if (isFirst)
                    {
                        isFirst = false;
                    }
                    else
                    {
                        argsLine.Append(", ");
                    }
                    argsLine.AppendFormat("{0}", pi.Name);
                }
                argsLine.AppendLine("};"); //close object[] initialiser

                output.AppendLine(argsLine.ToString());
                output.AppendFormat("Log(\"{0}\",args);", mi.Name);
                output.AppendLine();
                output.AppendLine();
            }
        return output.ToString();
    }

This code snippet loops through the methods on a class and outputs an object[] array initialised with the arguments passed into the method and a Log call containing the arguments and the method name.

Example output:

object[] args = {username, password, name, startDate, endDate, cost};
Log("GetAwesomeData",args);

This block can then be pasted into the top of the method to achieve the required effect.

It is more manual than I would have liked, but it is a lot better than having to type the parameters by hand and far less error prone.

Community
  • 1
  • 1
David Henderson
  • 1,185
  • 1
  • 10
  • 16