views:

202

answers:

8

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.

A: 

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
Did you even read the question? He wants to log all parameters coming into a function and would like to access that data.
Oded
+1  A: 

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:

http://stackoverflow.com/questions/214086/how-can-you-get-the-names-of-method-parameters-in-c

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].

Adam
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.
djch
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
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
+5  A: 

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
+1. I was going to write something about automatically re-writing the CIL output by the compiler to add logging, but just using an established product is probably going to be easier.
stakx
Also doing the above is actaully quick and easy to do with it.
aqwert
+1  A: 

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?

Si
+3  A: 

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) 
{
   //..
}
Yannick M.
(`System.Diagnostics.StackTrace` / `System.Diagnostics.StackFrame` seem to only provide source file name and line number information etc., not actual arguments.)
stakx
A: 

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
A: 

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? :)

AlexW
A: 

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.

djch