C#将任何方法作为参数传递

6

在登录时,你总是被字符串字面量所困扰。

我通过传递一个 Expression<Func<T>> expression(如此处所述)来很好地解决了属性、字段和变量的问题,因此你可以像这样做:

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

我希望对于方法Demo本身做类似的事情:
public void Demo(string someArgument)
{
    LogFramework.Log(this.Demo);
}

我尝试过这样的东西:

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

和这个:

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

但是我会遇到以下编译器错误:

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

我可以使用Func<...>Action<...>来引入一堆重载,但这听起来过于复杂。

有没有一种方法可以涵盖任何具有任意数量参数和可选结果的方法?

--jeroen

PS:我认为this question可能在这里有一些相关性,但没有答案让我感到“啊哈”:-)


1
我理解您的问题,不过您是否认为使用某些AOP框架会更容易且更具扩展性呢?您可以创建自定义属性来标记应在调用时记录的方法。 - empi
1
我以前使用过AOP,这使得编译/链接时间飞升。由于.NET已经具有更高的编译/链接时间,我宁愿避免使用AOP。 - Jeroen Wiert Pluimers
1
我所见过针对这个通用问题的所有表达式树解决方案都采用了多个重载的方法,这也有点别扭,因为你需要包含参数。如果我要编写手动跟踪日志,我通常只需将方法名称作为字符串常量传递,并依赖于Resharper提醒我保持它们同步。 - Dan Bryant
5个回答

5
您也可以通过使用System.Diagnostics.StackTrace来实现此目的,而不需要使用ExpressionTrees。
StackTrace trace = new StackTrace();

然后:
trace.GetFrame(0).GetMethod().Name

获取当前方法的MethodInfo和名称,或者:

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

获取调用方法。

+1;有趣!我会调查一下。感谢您的创新思维。这很可能也会帮助我以自动化的方式获取参数。 - Jeroen Wiert Pluimers
1
这是特别低效的,而且存在帧被优化掉的风险。 - Kirk Woll
@Kirk:众所周知,表达式的成本非常高,您能详细说明“被优化掉”的部分吗?您是指内联吗? - Jeroen Wiert Pluimers
1
Kirk所说的(这是一个合理的担忧)是,如果您构建了一个优化的发布版本而不是调试版本,您可能无法在堆栈跟踪中找到正确的帧。实际上,您可能在堆栈跟踪中找不到任何帧。 - Josh G
1
只有在“调试”版本中才有用。在“发布”版本中,我只看到主框架和记录器方法,中间什么也没有。 :( - Bitterblue
@Eve是正确的。堆栈跟踪选项只能在调试模式下工作。 - Josh G

5

这比看起来要困难得多。我认为您最好使用通用的 Func 和 Action 重载,但也可以通过表达式树来实现。以下是在 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;
}

这将输出:

DoIt
DoIt2
DoIt3

请注意,调用Log方法时,我不得不使用lambda并指定虚拟参数。

这是基于Fyodor Soikin的优秀答案


谢谢你的回答;我担心会是这样的情况,但确认一下还是很好的。 - Jeroen Wiert Pluimers

3

不要试图将方法作为参数传递给日志记录器,应该从日志记录器识别调用方法的角度来看待问题。

以下是一个(伪)示例:

日志记录器类

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 );
}

任何使用记录器的地方:

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

记录器的输出结果将为:Foo.SomeMethod: 开始

+1;感谢您详细阐述了 Josh G 提出的解决方案。我明白他的意思,但有一个更详细的例子将帮助未来的用户很多。 - Jeroen Wiert Pluimers
1
这是我几年前使用MS Enterprise Logging Library编写的日志记录库。我认为这基本上解决了您想要实现的问题,即您希望记录器知道调用记录器的方法。使用DeclaringType的好处是,您将获得一个完整的方法命名空间,并且永远不必担心将方法名称添加为日志记录消息的一部分。 - Metro Smurf
1
我接受了你的答案,因为它在调用现场创建了最干净的代码。没有复杂的表达式。很好。 - Jeroen Wiert Pluimers
同样的问题在这里。在发布版本中没有可用的有用帧。而另一个更加干净。 - Bitterblue

0
你可以定义一个委托,然后将其作为参数接受。
public delegate void DemoDelegate(string arg);

public void MyMethod(DemoDelegate delegate)
{
    // Call the delegate
    delegate("some string");
}

您可以这样调用MyMethod:
MyMethod(delegate(string arg) 
{
   // do something
});

或者

void MethodThatTakesAString(string value)
{
    // do something
}

MyMethod(MethodThatTakesAString);

请查看此链接以获取更多信息:

http://msdn.microsoft.com/en-us/library/aa288459(v=vs.71).aspx


1
我知道委托,但不明白它如何帮助我处理任意数量参数的任何方法。请详细说明,因为我可能忽略了非常明显的东西 :-) - Jeroen Wiert Pluimers

0

试试这个:

/// <summary>
/// Trace data event handler delegate.
/// </summary>
/// <returns>The data to write to the trace listeners</returns>
public delegate object TraceDataEventHandler();

public static class Tracing
{

    /// Trace a verbose message using an undefined event identifier and message.
    /// </summary>
    /// <param name="message">The delegate to call for the trace message if this event should be traced.</param>
    [Conditional("TRACE")]
    public static void TraceVerbose(TraceMessageEventHandler message)
    {
        ... your logic here
    }
}

然后你可以做...

Tracing.TraceVerbose(() => String.Format(...));

希望我已经正确理解了你的问题...这样做是否符合你的要求?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接