【问题标题】:Can I get parameter names/values procedurally from the currently executing function?我可以从当前执行的函数中以程序方式获取参数名称/值吗?
【发布时间】:2010-03-08 22:19:21
【问题描述】:

我想做这样的事情:

public MyFunction(int integerParameter, string stringParameter){
    //Do this:
    LogParameters();
    //Instead of this:
    //Log.Debug("integerParameter: " + integerParameter + 
    //          ", stringParameter: " + stringParameter);

}

public LogParameters(){
    //Look up 1 level in the call stack (if possible),
    //Programmatically loop through the function's parameters/values
    //and log them to a file (with the function name as well).
    //If I can pass a MethodInfo instead of analyzing the call stack, great.
}

我什至不确定我想要做什么,但是如果能够在运行时自动将参数名称/值输出到文件而不显式编写代码来记录它们,那就太好了。

有可能吗?

【问题讨论】:

标签: c# .net reflection logging parameters


【解决方案1】:
StackTrace stackTrace = new StackTrace();
ParameterInfo[] parameters = stackTrace.GetFrame(1).GetMethod().GetParameters();

注意,GetFrame(1) 获取的是调用方法而不是当前方法。这应该会给你你想要的结果,并允许你在 LogParameters() 中执行下面的代码。

您需要像下面这样调用 LogParameters,因为您无法从 ParameterInfo 中获取 integerParameter 和 stringParameter 的反映值。

LogParameters(integerParameter, stringParameter);

【讨论】:

  • 肯定使用 StackTrace 来获取相关的 StackFrame,这甚至允许使用可选的“深度”参数声明“记录”子例程。 +1
  • 更新提供了值,但有些地方可能会出错。首先,这是一个维护负担——对参数列表的任何更改都必须在日志调用中重复。其次,如果你的原始函数只有一个 object[] 类型的参数,你必须跳过一些额外的环节。
【解决方案2】:

我意识到有人与提到 PostSharp 的其他问题相关联,但我忍不住发布了解决我的问题的代码(使用 PostSharp),以便其他人可以从中受益。

class Program {
    static void Main(string[] args) {
        Trace.Listeners.Add(new TextWriterTraceListener(Console.Out));
        new MyClass().MyMethod(44, "asdf qwer 1234", 3.14f, true);
        Console.ReadKey();
    }
}
public class MyClass {
    public MyClass() {
    }
    [Trace("Debug")]
    public int MyMethod(int x, string someString, float anotherFloat, bool theBool) {
        return x + 1;
    }
}
[Serializable]
public sealed class TraceAttribute : OnMethodBoundaryAspect {
    private readonly string category;

    public TraceAttribute(string category) {
        this.category = category;
    }

    public string Category { get { return category; } }

    public override void OnEntry(MethodExecutionArgs args) {
        Trace.WriteLine(string.Format("Entering {0}.{1}.", 
                                      args.Method.DeclaringType.Name, 
                                      args.Method.Name), category);

        for (int x = 0; x < args.Arguments.Count; x++) {
            Trace.WriteLine(args.Method.GetParameters()[x].Name + " = " + 
                            args.Arguments.GetArgument(x));
        }
    }

    public override void OnExit(MethodExecutionArgs args) {
        Trace.WriteLine("Return Value: " + args.ReturnValue);

        Trace.WriteLine(string.Format("Leaving {0}.{1}.", 
                                      args.Method.DeclaringType.Name, 
                                      args.Method.Name), category);
    }
} 

只需将Trace 属性添加到方法中,就会输出非常漂亮的调试信息,如下所示:

Debug: Entering MyClass.MyMethod. 
x = 44
someString = asdf qwer 1234
anotherFloat = 3.14
theBool = True
Return Value: 45
Debug: Leaving MyClass.MyMethod.

【讨论】:

  • postsharp 的 express 版本(免费)可以做到这一点吗?
【解决方案3】:

理论上可以关闭调试构建和优化,但实际上,我建议你需要一些源代码重写。

人们会不断告诉你反射会起作用,而它不会起作用,所以here is the function that's actually capable of getting argument values。它不太可能在启用优化的情况下可靠地工作(例如,当内联打开时甚至可能没有堆栈帧)并且安装了调试器,因此您可以调用它函数不会像您希望的那么简单。

【讨论】:

  • 实际上-关于优化的好点-我假设这个问题与调试工具有关....
  • @Ben Voigt - 你有ICorDebugILFrame::GetArgument 的示例用法吗?
  • @Ben Voigt - 如果你能提供的话,我也想看看一个示例用法。感谢您的回答!
【解决方案4】:

除非您使用调试器API,否则您不能循环调用堆栈上不同方法的参数。虽然您可以从调用堆栈中获取参数names(正如其他人提到的那样)。

最接近的应该是:

public MyFunction(int integerParameter, string stringParameter){
    LogParameters(integerParameter, stringParameter);
}

public void LogParameters(params object[] values){
    // Get the parameter names from callstack and log names/values
}

【讨论】:

  • 你可以,你只需要调试API,而不是反射
  • 这种方法是个好主意,但也有一些地方会出错。首先,这是一个维护负担——对参数列表的任何更改都必须在日志调用中重复。其次,如果你的原始函数有一个 object[] 类型的参数,你必须跳过一些额外的环节。
  • @BenVoigt - 是的,这就是为什么这些天我会使用 AOP 来做这样的事情。
【解决方案5】:

这是创建日志的实用程序类。

internal class ParamaterLogModifiedUtility
{
    private  String _methodName;
    private String _paramaterLog;

    private readonly JavaScriptSerializer _serializer;
    private readonly Dictionary<String, Type> _methodParamaters;
    private readonly List<Tuple<String, Type, object>>_providedParametars;

    public ParamaterLogModifiedUtility(params Expression<Func<object>>[] providedParameters)
    {
        try
        {
            _serializer = new JavaScriptSerializer();
            var currentMethod = new StackTrace().GetFrame(1).GetMethod();

            /*Set class and current method info*/
            _methodName = String.Format("Class = {0}, Method = {1}", currentMethod.DeclaringType.FullName, currentMethod.Name);

            /*Get current methods paramaters*/
            _methodParamaters = new Dictionary<string, Type>();
            (from aParamater in currentMethod.GetParameters()
             select new { Name = aParamater.Name, DataType = aParamater.ParameterType })
             .ToList()
             .ForEach(obj => _methodParamaters.Add(obj.Name, obj.DataType));

            /*Get provided methods paramaters*/
            _providedParametars = new List<Tuple<string, Type, object>>();
            foreach (var aExpression in providedParameters)
            {
                Expression bodyType = aExpression.Body;

                if (bodyType is MemberExpression)
                {
                    AddProvidedParamaterDetail((MemberExpression)aExpression.Body);
                }
                else if (bodyType is UnaryExpression)
                {
                    UnaryExpression unaryExpression = (UnaryExpression)aExpression.Body;
                    AddProvidedParamaterDetail((MemberExpression)unaryExpression.Operand);
                }
                else
                {
                    throw new Exception("Expression type unknown.");
                }
            }

            /*Process log for all method parameters*/
            ProcessLog();

        }
        catch (Exception exception)
        {
            throw new Exception("Error in paramater log processing.", exception);
        }
    }

    private void ProcessLog()
    {
        try
        {
            foreach (var aMethodParamater in _methodParamaters)
            {
                var aParameter =
                    _providedParametars.Where(
                        obj => obj.Item1.Equals(aMethodParamater.Key) && obj.Item2 == aMethodParamater.Value).Single();
                _paramaterLog += String.Format(@" ""{0}"":{1},", aParameter.Item1, _serializer.Serialize(aParameter.Item3));
            }
            _paramaterLog = (_paramaterLog != null) ? _paramaterLog.Trim(' ', ',') : string.Empty;
        }
        catch (Exception exception)
        {
            throw new Exception("MathodParamater is not found in providedParameters.");
        }
    }

    private void AddProvidedParamaterDetail(MemberExpression memberExpression)
    {
        ConstantExpression constantExpression = (ConstantExpression) memberExpression.Expression;
        var name = memberExpression.Member.Name;
        var value = ((FieldInfo) memberExpression.Member).GetValue(constantExpression.Value);
        var type = value.GetType();
        _providedParametars.Add(new Tuple<string, Type, object>(name, type, value));
    }


    public String GetLog()
    {
        return String.Format("{0}({1})", _methodName, _paramaterLog);
    }

}

使用实用程序

class PersonLogic
{
    public bool Add(PersonEntity aPersonEntity, ushort age = 12, String id = "1", String name = "Roy")
    {
        string log =  new ParamaterLogModifiedUtility(() => aPersonEntity, () => age, () => id, () => name).GetLog();
        return true;
    }
}

现在调用用法

class Program
{
    static void Main(string[] args)
    {
        try
        {
            PersonLogic personLogic = new PersonLogic();
            personLogic.Add(id: "1", age: 24, name: "Dipon", aPersonEntity: new PersonEntity() { Id = "1", Name = "Dipon", Age = 24 });
        }
        catch (Exception exception)
        {
            Console.WriteLine("Error.");
        }
        Console.ReadKey();
    }
}

结果日志:

        Class = MethodParamatersLog.Logic.PersonLogic, Method = Add("aPersonEntity":{"CreatedDateTime":"\/Date(1383422468353)\/","Id":"1","Name":"Dipon","Age":24}, "age":24, "id":"1", "name":"Dipon")

【讨论】:

    【解决方案6】:

    我按照说明创建了这个类:

    public static class Tracer
    {
        public static void Parameters(params object[] parameters)
        {
            #if DEBUG
                var jss = new JavaScriptSerializer();
    
                var stackTrace = new StackTrace();
    
                var paramInfos = stackTrace.GetFrame(1).GetMethod().GetParameters();
    
                var callingMethod = stackTrace.GetFrame(1).GetMethod();
                Debug.WriteLine(string.Format("[Func: {0}", callingMethod.DeclaringType.FullName + "." + callingMethod.Name + "]"));
    
                for (int i = 0; i < paramInfos.Count(); i++)
                {
                    var currentParameterInfo = paramInfos[i];
    
                    var currentParameter = parameters[i];
    
                    Debug.WriteLine(string.Format("    Parameter: {0}", currentParameterInfo.Name));
    
                    Debug.WriteLine(string.Format("    Value: {0}", jss.Serialize(currentParameter)));
                }
                Debug.WriteLine("[End Func]");
            #endif
        }
    }
    

    这样称呼它:

    public void Send<T>(T command) where T : Command
    {
        Tracer.Parameters(command);
    }
    

    输出看起来像这样

    [Func: SimpleCQRS.FakeBus.Send]
        Parameter: command
        Value: {"InventoryItemId":"f7005197-bd20-42a6-b35a-15a6dcc23c33","Name":"test record"}
    [End Func]
    

    编辑

    …………

    我扩展了我的跟踪器功能,为我做了很好的工作。要跟踪每个函数及其调用函数等,您可以使用 StrackTrace.GetFrame(2) 来使用附加功能。现在我的输出更丰富了。我还使用 Json.NET's 库来输出漂亮的格式化 JSON 对象。也可以将输出粘贴到一个空的 JavaScript 文件中并查看彩色输出。

    现在我的输出如下所示:

    //Func: HomeController(Constructor): CQRSGui.Controllers.HomeController(Constructor)
    //From: RuntimeTypeHandle.CreateInstance: System.RuntimeTypeHandle.CreateInstance
    var parameters = {}
    
    //Func: HomeController.Add: CQRSGui.Controllers.HomeController.Add
    //From: System.Object lambda_method(System.Runtime.CompilerServices.Closure, System.Web.Mvc.ControllerBase, System.Object[])
    var parameters = {
        "name": "car"
    }
    
    //Func: Command(Constructor): SimpleCQRS.Command(Constructor)
    //From: CreateInventoryItem(Constructor): SimpleCQRS.CreateInventoryItem(Constructor)
    var parameters = {}
    
    //Func: CreateInventoryItem(Constructor): SimpleCQRS.CreateInventoryItem(Constructor)
    //From: HomeController.Add: CQRSGui.Controllers.HomeController.Add
    var parameters = {
        "inventoryItemId": "d974cd27-430d-4b22-ad9d-22ea0e6a2559",
        "name": "car"
    }
    
    //Func: FakeBus.Send: SimpleCQRS.FakeBus.Send
    //From: HomeController.Add: CQRSGui.Controllers.HomeController.Add
    var parameters = {
        "command": {
            "InventoryItemId": "d974cd27-430d-4b22-ad9d-22ea0e6a2559",
            "Name": "car"
        }
    }
    
    //Func: InventoryCommandHandlers.Handle: SimpleCQRS.InventoryCommandHandlers.Handle
    //From: FakeBus.Send: SimpleCQRS.FakeBus.Send
    var parameters = {
        "message": {
            "InventoryItemId": "d974cd27-430d-4b22-ad9d-22ea0e6a2559",
            "Name": "car"
        }
    }
    
    //Func: AggregateRoot(Constructor): SimpleCQRS.AggregateRoot(Constructor)
    //From: InventoryItem(Constructor): SimpleCQRS.InventoryItem(Constructor)
    var parameters = {}
    
    //Func: InventoryItem(Constructor): SimpleCQRS.InventoryItem(Constructor)
    //From: InventoryCommandHandlers.Handle: SimpleCQRS.InventoryCommandHandlers.Handle
    var parameters = {
        "id": "d974cd27-430d-4b22-ad9d-22ea0e6a2559",
        "name": "car"
    }
    
    //Func: Event(Constructor): SimpleCQRS.Event(Constructor)
    //From: InventoryItemCreated(Constructor): SimpleCQRS.InventoryItemCreated(Constructor)
    var parameters = {}
    
    //Func: InventoryItemCreated(Constructor): SimpleCQRS.InventoryItemCreated(Constructor)
    //From: InventoryItem(Constructor): SimpleCQRS.InventoryItem(Constructor)
    var parameters = {
        "id": "d974cd27-430d-4b22-ad9d-22ea0e6a2559",
        "name": "car"
    }
    
    //Func: AggregateRoot.ApplyChange: SimpleCQRS.AggregateRoot.ApplyChange
    //From: InventoryItem(Constructor): SimpleCQRS.InventoryItem(Constructor)
    var parameters = {
        "event": {
            "Id": "d974cd27-430d-4b22-ad9d-22ea0e6a2559",
            "Name": "car",
            "Version": 0
        }
    }
    

    强大,不是吗?我只需要查看输出,不需要一次又一次地破坏应用程序,也不需要检查监视和本地窗口。我喜欢这种方式。我已经将tracker.Parameters 函数放在我的应用程序的任何地方,现在我已经自动调试了应用程序。

    我添加到我的输出函数中的一件事是在序列化中调用错误事件。并从 Json.NET 处理。实际上,您可能会陷入循环引用错误。我抓住了。此外,如果有更多的序列化错误,您可以捕获它们,然后您可以在参数对象输出下方显示序列化错误。

    【讨论】:

    • 这种方法是个好主意,但也有一些地方会出错。首先,这是一个维护负担——对参数列表的任何更改都必须在日志调用中重复。其次,如果你的原始函数有一个 object[] 类型的参数,你必须跳过一些额外的环节。
    猜你喜欢
    • 2010-11-04
    • 2013-01-13
    • 1970-01-01
    • 2011-01-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-07
    相关资源
    最近更新 更多