问题 如何枚举传递的方法参数


可以枚举被调用的方法参数类型/信息,如下所示:

private void SomeMethod(int thisValue, string thatValue)
{
  StackTrace stackTrace = new StackTrace();
  foreach (ParameterInfo pInfo in stackTrace.GetFrame(0).GetMethod().GetParameters())
  {
    string name = pInfo.Name;
    string type = pInfo.GetType().ToString();
  }
}

但有没有办法获得每个参数的实际对象?

编辑: 我的目标是枚举所有参数并获取它们的值。 使用LinQ表达式,可以获得如下参数值:

private void SomeMethod(int thisValue, string thatValue)
{
  object valueOfThis = GetParameterValue(() => thisValue);
  object valueOfThat = GetParameterValue(() => thatValue);
}
private object GetParameterValue<T>(Expression<Func<T>> expr)
{
  var body = ((MemberExpression)expr.Body);
  return ((FieldInfo)body.Member).GetValue(((ConstantExpression)body.Expression).Value);
}

但我想做的是:

foreach (fooObject o in thisMethod.GetParameterObjects())
{
  object someValue = GetParameterValue(() => fooObject);
}

从而有一个收集所有参数及其值的通用方法。


9495
2018-01-14 08:08


起源

您是否尝试创建包含实际参数值的堆栈跟踪? - John Leidegren
嗯,实际的参数实例就在方法中...... - Mark Seemann
@Mark:我的想法也是,但是看到很明显他正在寻找更通用的东西,这样可以让你获得任何堆栈帧的值(我猜...) - John Leidegren
@John,我可以根据我添加的附加信息获取值。我想要的是枚举参数对象并以通用方式获取它们的值。 - Magnus Johansson
@John(“猜测”),是的,这听起来像我想要的。 - Magnus Johansson


答案:


更新:

通过尝试解释所有内容,我看起来“过于复杂”了最初的答案。这是答案的简短版本。

private static void SomeMethod(int thisValue, string thatValue)  
{ 
    IEnumerable<object> parameters = GetParameters(() => SomeMethod(thisValue, thatValue)); 
    foreach (var p in parameters) 
        Console.WriteLine(p); 
}
private static IEnumerable<object> GetParameters(Expression<Action> expr)
{
    var body = (MethodCallExpression)expr.Body;
    foreach (MemberExpression a in body.Arguments)
    {
        var test = ((FieldInfo)a.Member).GetValue(((ConstantExpression)a.Expression).Value);
        yield return test;
    }
}

这是长版本,有一些解释。

实际上,如果使用表达式树,则不需要在方法内部枚举其参数。

    static void Main(string[] args)
    {

        // First approach.
        IEnumerable<object> parameters = GetParametersFromConstants(() => SomeMethod(0, "zero"));
        foreach (var p in parameters)
            Console.WriteLine(p);

        // Second approach.
        int thisValue = 0;
        string thatValue = "zero";
        IEnumerable<object> parameters2 = GetParametersFromVariables(() => SomeMethod(thisValue, thatValue));
        foreach (var p in parameters2)
            Console.WriteLine(p);

        Console.ReadLine();
    }

    private static void SomeMethod(int thisValue, string thatValue) 
    {
        Console.WriteLine(thisValue + " " + thatValue);
    }      

    private static IEnumerable<object> GetParametersFromVariables(Expression<Action> expr)
    {
        var body = (MethodCallExpression)expr.Body;
        foreach (MemberExpression a in body.Arguments)
        {               
            var test = ((FieldInfo)a.Member).GetValue(((ConstantExpression)a.Expression).Value);
            yield return test;
        }
    }

    private static IEnumerable<object> GetParametersFromConstants(Expression<Action> expr)
    {
        var body = (MethodCallExpression)expr.Body;
        foreach (ConstantExpression a in body.Arguments)
        {
            var test = a.Value;
            yield return test;
        }
    }

}

请注意,如果使用表达式树,则代码很大程度上取决于传递给方法的表达式。我已经展示了一个使用常量,一个使用变量。但当然可以有更多场景。您可以重构此代码以针对这两种情况使用单个方法,但我认为这样可以更好地说明问题。


9
2018-01-14 18:50



感谢你的回答。但是,我正在寻找可以放在SomeMethod()中的东西,使其成为通用的。您提供的示例强制一个在SomeMethod()之前调用自定义代码。 - Magnus Johansson
不,你不需要在SomeMethod之前调用任何东西。将标记为“第二种方法”的代码(只是跳过变量声明和初始化)并将其粘贴到方法中。您将获得方法内的参数枚举。我只是想指出你可以从任何地方做到这一点,不一定是从方法体。 - Alexandra Rusina
如果你必须以某种方式明确列出源代码中的参数,为什么还要烦恼 GetParameters 在所有?这样做: object[] params = new object[] {thisValue, thatValue}; - Adrian Carneiro
@AlexandraRusina我觉得这很老了。但是,您是否遇到过MemberExpression.Member的情况 PropertyInfo 而不是 FieldInfo?我正在做这样的事情 PropertyInfos让我头疼不已。谢谢! - Bigsby


好的,所以这是交易。

你不能这样做,而不是托管语言。我没有看到有人会让你控制堆栈帧。在某种程度上,这就是你想要的。因为您需要获取值的信息。

现在运行时知道这一点,它拥有所有信息,但你无法对如何创建堆栈框架做出假设,因为你并不打算这样做。

因此,只有一种方法可以解决这个问题。分析API。

我结束了 这里。在分析API的功能内。我敢打赌有一种方法可以让你通过从托管代码调用非托管类来深入了解参数值。

现在,我不会这样做,因为那里已经有很好的分析工具,JetBrains dotTrace来命名一个和VS2010中的IntelliTrace所有这些令人头疼的事情将会消失...... IntelliTrace将让你做时间调试。

实现这一目标的另一个显而易见的方法是完全foobar,但最终可能会有趣的尝试,它总是可以这样做,但我永远不会在生活中把这些代码放在生产环境中。

// compile with unsafe
unsafe
{
    var p = stackalloc int[1];
    var baseAddr = p - sizeof(int);
}

现在,你不能写信给 baseAddr 但你应该被允许阅读它。棘手的部分是理解堆栈帧,并且必须符合调用约定,并且必须确定一定的时间。 这里的 这些东西耗尽,而且是快速通话。

有了这些信息和ParameterInfo对象,您应该能够遍历参数。

既然你将使用原始指针,你需要将它们变成托管对象,并且有一个  为了那个原因。

你去吧,疯了!

但是一个很大的警告,当你走上堆栈时你会发现什么,不会是你期望的。因为参数可以放在寄存器中,所以无法从托管代码中访问寄存器。


1
2018-01-14 08:31





您可以使用 MethodInfo.GetCurrentMethod().GetParameters() 获取方法参数列表。但是通过反思来获取他们的价值是不可能的。


1
2018-01-14 09:24



@Mehdi。是的,我的问题已经涵盖了GetParameters()方法,谢谢。 - Magnus Johansson