Могу ли я получить имена / значения параметров процедурно из выполняемой в данный момент функции? - PullRequest
40 голосов
/ 09 марта 2010

Я хотел бы сделать что-то вроде этого:

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

Я даже не уверен, что то, что я хочу сделать, возможно, но было бы очень приятно иметь возможность автоматически выводить имена / значения параметров во время выполнения в файл без явного написания кода для их регистрации.

Возможно ли это?

Ответы [ 6 ]

22 голосов
/ 10 марта 2010

Я понимаю, что люди связывались с другими вопросами, в которых упоминался 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.
12 голосов
/ 09 марта 2010

Теоретически это возможно при отладочной сборке и отключенной оптимизации, но на практике я предлагаю вам пройти этап переписывания исходного кода.

Люди будут повторять, что отражение будет работать, когда оно не работает, поэтому здесь - функция, которая на самом деле способна получать значения аргументов . Это вряд ли будет работать надежно с включенной оптимизацией (например, может даже не быть фреймом стека, когда включено встраивание) и не будет установлен отладчик, чтобы вы могли вызвать эту функцию не так просто, как вы надеялись.

7 голосов
/ 09 марта 2010
StackTrace stackTrace = new StackTrace();
ParameterInfo[] parameters = stackTrace.GetFrame(1).GetMethod().GetParameters();

Обратите внимание, GetFrame (1) получает вызывающий метод, а не текущий метод.Это должно дать вам желаемые результаты и позволить вам выполнить приведенный ниже код в LogParameters ().

Вам нужно будет вызвать LogParameters, как показано ниже, поскольку вы не сможете получить отраженные значения integerParameter и stringParameterиз ParameterInfo.

LogParameters(integerParameter, stringParameter);
3 голосов
/ 03 ноября 2013

Это класс Utility, который создает журнал.

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")
3 голосов
/ 09 марта 2010

Если вы не используете отладчик 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
}
1 голос
/ 27 июля 2013

Я следовал инструкциям и создал этот класс:

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 для вывода красиво выглядящих отформатированных объектов 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 повсюду в моем приложении, и теперь я автоматически отлаживал приложение.

Одна вещь, которую я добавил к своей функции вывода, это вызов Error Event в сериализации.И справился с этим из Json.NET.На самом деле вы можете попасть в круговую ссылку.Который я поймал.А также, если есть больше ошибок сериализации, вы можете их перехватить, а затем вывести ошибки сериализации чуть ниже вывода объекта параметров.

...