Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# pass any method as a parameter

When logging, you always get entangled in string literals.

I solved that nicely for properties, fields and variables by passing an Expression<Func<T>> expression (as explained here), so you can do things like this:

public void Demo(string someArgument)
{
    LogFrameWork.LogLine("Demo"); // goal is to get rid of these string literals
    LogFramework.Log(() => someArgument);
}

I want to do something similar for the method Demo itself:

public void Demo(string someArgument)
{
    LogFramework.Log(this.Demo);
}

I tried things like this:

public static void Log(Delegate method)
{
    string methodName = method.Method.Name;
    LogLine(methodName);
}

and this:

public static void Log(Action method)
{
    string methodName = method.Method.Name;
    LogLine(methodName);
}

But I get compiler errors like these:

Argument 1: cannot convert from 'method group' to 'System.Delegate' 
Argument 1: cannot convert from 'method group' to 'System.Action'   

I could introduce a bunch of overloads using Func<...> and Action<...>, but that sounds overly complex.

Is there a way to cover this for any method with any number of parameters and an optional result?

--jeroen

PS: I think this question might have some relevance here, but no answers that got me a 'aha' feeling :-)

like image 881
Jeroen Wiert Pluimers Avatar asked Mar 26 '11 16:03

Jeroen Wiert Pluimers


3 Answers

You can also achieve this without using ExpressionTrees through System.Diagnostics.StackTrace.

StackTrace trace = new StackTrace();

And then:

trace.GetFrame(0).GetMethod().Name

To get the MethodInfo and then name of the current method, or:

trace.GetFrame(1).GetMethod().Name 

To get the calling method.

like image 54
Josh G Avatar answered Oct 10 '22 10:10

Josh G


Instead of trying to pass the method in as a parameter to your logger, look at it from the perspective of having the logger identify the calling method.

Here's an (pseudo) example:

Logger Class

public void Debug( string message )
{
  message = string.Format( "{0}: {1}", GetCallingMethodInfo(), message );
  // logging stuff
}

/// <summary>
/// Gets the application name and method that called the logger.
/// </summary>
/// <returns></returns>
private static string GetCallingMethodInfo()
{
  // we should be looking at the stack 2 frames in the past:
  // 1. for the calling method in this class
  // 2. for the calling method that called the method in this class
  MethodBase method = new StackFrame( 2 ).GetMethod();
  string name = method.Name;
  string type = method.DeclaringType.Name;

  return string.Format( "{0}.{1}", type, name );
}

Anywhere that uses the logger:

// resides in class Foo
public void SomeMethod()
{
  logger.Debug("Start");
}

The output from the logger will then be: Foo.SomeMethod: Start

like image 44
Metro Smurf Avatar answered Oct 10 '22 09:10

Metro Smurf


This is much harder than it looks. I think you might be best with the generic Func and Action overloads, but there is a way to do this with expression trees. Here's an example in LINQPad:

public static void Log(Expression<Action> expr)
{
    Console.WriteLine(((MethodCallExpression)expr.Body).Method.Name);
}

void Main()
{
    Log(() => DoIt());
    Log(() => DoIt2(null));
    Log(() => DoIt3());
}

public void DoIt()
{
    Console.WriteLine ("Do It!");
}

public void DoIt2(string s)
{
    Console.WriteLine ("Do It 2!" + s);
}

public int DoIt3()
{
    Console.WriteLine ("Do It 3!");
    return 3;
}

This outputs:

DoIt
DoIt2
DoIt3

Note that I had to use lambdas and specify dummy arguments when calling the Log method.

This is based on Fyodor Soikin's excellent answer.

like image 24
TrueWill Avatar answered Oct 10 '22 10:10

TrueWill