Получить параметры с которыми вызван метод C# - типы, имена и значения

В методе c# получить сведения о том с какими параметрами значение переменных через .ToString() был вызван этот метод, независимо от количества параметров. Вызов функции должен быть одинаковым, без ручного перечисления параметров метода.

примерно так: (здесь не хватает ЗНАЧЕНИЙ всех параметров, есть только ТИПЫ)

using System.Reflection

    // Это вызов логера. в нем не надо каждый раз переписывать список переданных параметров. 
    ParameterInfo[] args = MethodBase.GetCurrentMethod().GetParameters();
    MY_Debug_Log(MethodBase.GetCurrentMethod().Name,args,this);
    // 
    
        void MY_Debug_Log(string message, ParameterInfo[] args, Object obj)
        {
            string s = "";
            var sb = new System.Text.StringBuilder();
            sb.Append(message);
            sb.Append("(");
            foreach (ParameterInfo p in args)
            {
                sb.Append(s);
                sb.Append(p.ParameterType.ToString());
                s = ", ";
            }
            sb.Append(")");
            Log(sb.ToString(),obj);
        }

Ответы (3 шт):

Автор решения: rotabor

Значения получить можно так:

using System.Reflection

void AnyMethod(ArgType1 arg1, ArgType2 arg2) {
    // Это вызов логера. в нем не надо каждый раз переписывать список переданных параметров. 
    ParameterInfo[] args = MethodBase.GetCurrentMethod().GetParameters();
    MY_Debug_Log(MethodBase.GetCurrentMethod().Name, args, [arg1, arg2], this);
    // 
}    
        void MY_Debug_Log(string message, ParameterInfo[] args, object[] vals, Object obj)
        {
            string s = "";
            var sb = new System.Text.StringBuilder();
            sb.Append(message);
            sb.Append("(");
            int i = 0;
            s = ", ";
            foreach (ParameterInfo p in args)
            {
                sb.Append(s);
                sb.Append(p.ParameterType.ToString());
                sb.Append(" = ");
                sb.Append(vals[i++].ToString());
            }
            sb.Append(")");
            Log(sb.ToString(),obj);
        }

P. S. Если всё так серьёзно (возможно, что ТТ сделаны для другого ЯП), то Вы можете использовать технологию прокси-объектов: "If you prefer not to reinvent the wheel, consider using libraries like Castle's DynamicProxy1. They simplify dynamic proxy creation and offer additional features."

Как пример, см. RealProxy in dotnet core?

P. P. S. А чтобы не ошибиться со списком аргументов, их нужно вставлять с помощью кодогенерации при постобработке, пример тут.

→ Ссылка
Автор решения: rotabor

Итак, решение с проксимизацией.

Сначала сделаем проксимизатор с нужными функциями:

using System.Reflection;

namespace ConsoleApp2;

internal class LoggingDecorator<T> : DispatchProxy {
    private T _decorated;
    protected override object Invoke(MethodInfo targetMethod, object[] args) {
        try {
            LogBefore(targetMethod, args);
            var result = targetMethod.Invoke(_decorated, args);
            LogAfter(targetMethod, args, result);
            return result;
        }
        catch (Exception ex) when (ex is TargetInvocationException) {
            LogException(ex.InnerException ?? ex, targetMethod);
            throw ex.InnerException ?? ex;
        }
    }
    public static T Create(T decorated) {
        object proxy = Create<T, LoggingDecorator<T>>();
        ((LoggingDecorator<T>)proxy).SetParameters(decorated);
        return (T)proxy;
    }
    private void SetParameters(T decorated) {
        if (decorated == null) { throw new ArgumentNullException(nameof(decorated)); }
        _decorated = decorated;
    }
    private void LogException(Exception exception, MethodInfo methodInfo = null) {
        Console.WriteLine($"Class {_decorated.GetType().FullName}, Method {methodInfo.Name} threw exception:\n{exception}");
    }
    private void LogAfter(MethodInfo methodInfo, object[] args, object result) {
        Console.WriteLine($"Class {_decorated.GetType().FullName}, Method {methodInfo.Name} executed, Output: {result}");
    }
    private void LogBefore(MethodInfo methodInfo, object[] args) {
        Console.WriteLine($"Class {_decorated.GetType().FullName}, Method {methodInfo.Name} is executing");
        Console.WriteLine($"Arguments types {string.Join("; ", args.Select(s => s.GetType().Name))}");
        Console.WriteLine($"Arguments values {string.Join("; ", args)}");
    }
}

Теперь используем его в простейшем примере:

internal interface ICalculator {
    int Add(int a, int b);
}
internal class Calculator : ICalculator {
    public int Add(int a, int b) => a + b; // никаких лишних команд вообще!
}
internal class Program {
    static void Main(string[] args) {
        ICalculator calculator = new Calculator(); // (1) обычный объект
        Console.WriteLine(calculator.Add(3, 5)); // (2) обычный вызов
        Console.WriteLine(new string('-', 80));
        calculator = LoggingDecorator<ICalculator>.Create(new Calculator()); // (3) прокси-объект, тип переменной не изменился!
        Console.WriteLine(calculator.Add(3, 5)); // (4) прокси-объект, но обычный вызов!
    }
}

-->

8
--------------------------------------------------------------------------------
Class ConsoleApp2.Calculator, Method Add is executing
Arguments types Int32; Int32
Arguments values 3; 5
Class ConsoleApp2.Calculator, Method Add executed, Output: 8
8

Как мы видим по строкам (2) и (4), разницы в работе с объектом нет, и тип переменной совпадает. Только инициализация объекта отличается - (1) и (3).

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

→ Ссылка
Автор решения: MSDN.WhiteKnight

Как правильно написали в комментариях, это задача практически нерешаемая, если нельзя модифицировать сигнатуру/вызов исследуемого метода. Это связано с тем, что оптимизирующий JIT-компилятор может генерировать разный код для передачи параметров: через регистры или через стек, может вообще ликвидировать вызов метода и вставить все его тело в конечный метод. Из-за этого всей необходимой информации, чтобы сопоставить значение параметра в памяти с соответствующим ParameterInfo, может во время выполнения не хватать. Если же перестроить вызов исследуемого метода так, чтобы эта информация всегда сохранялась, задача действительно становится элементарной.

Можно, например, создать такой вспомогательный класс для вызова метода с сохранением таблицы соответствия параметров (данная реализация поддерживает только статические методы):

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;

class ParameterMap
{
    public MethodInfo TargetMethod { get; set; }
    public object[] Args { get; set; }
}

class LoggingInvoker
{
    public static object Invoke(MethodInfo mi, params object[] args)
    {
        ParameterMap map = new ParameterMap();
        map.TargetMethod = mi;
        map.Args = args;
        List<object> finalArgs = new List<object>(args.Length + 1);
        finalArgs.Add(map);
        finalArgs.AddRange(args);

        return mi.Invoke(null, finalArgs.ToArray());
    }
}

Сам исследуемый метод нужно определить так, чтобы первым аргументом был ParameterMap. Если мы хотим создать метод для сложения чисел с логированием, определим его так:

class Program
{
    static void MY_Debug_Log(string message, ParameterMap map)
    {
        string s = "";
        var sb = new StringBuilder();
        sb.Append(message);
        sb.Append("(");
        ParameterInfo[] pars = map.TargetMethod.GetParameters();

        for(int i=0;i<pars.Length-1;i++)
        {
            ParameterInfo p = pars[i+1];
            sb.Append(s);
            sb.Append(p.ParameterType.ToString() + " ");
            sb.Append(p.Name);
            sb.Append('=');
            sb.Append(map.Args[i].ToString());
            s = ", ";
        }

        sb.Append(")");

        Console.WriteLine(sb.ToString());
    }

    public static double CalcSum(ParameterMap pmap, double x, double y)
    {
        MY_Debug_Log(MethodBase.GetCurrentMethod().Name, pmap);

        return x + y;
    }
}

Тогда вызвать его можно так:

object res = LoggingInvoker.Invoke(typeof(Program).GetMethod("CalcSum"), 1.1, 2.2);

Выведет CalcSum(System.Double x=1.1, System.Double y=2.2)

→ Ссылка