【问题标题】:Getting parameters of Func<T> variable获取 Func<T> 变量的参数
【发布时间】:2014-05-14 21:12:35
【问题描述】:

我有一个相当复杂的问题。我正在尝试从方法及其形式参数和实际参数中获取唯一键。该方法的目标是进行方法调用,并根据 1) 类和方法的名称以及 2) 调用它的参数的名称和值返回一个唯一键。

该方法看起来像这样(抱歉所有细节,但我找不到合理的方法来缩小示例但仍能解释我的问题)

 public class MethodKey
    {
        public static string GetKey<T>(Expression<Func<T>> method, params string[] paramMembers)
        {
            var keys = new Dictionary<string, string>();
            string scope = null;
            string prefix = null;
            ParameterInfo[] formalParams = null;
            object[] actual = null;

            var methodCall = method.Body as MethodCallExpression;
            if (methodCall != null)
            {
                scope = methodCall.Method.DeclaringType.FullName;
                prefix = methodCall.Method.Name;

                IEnumerable<Expression> actualParams = methodCall.Arguments;
                actual = actualParams.Select(GetValueOfParameter<T>).ToArray();
                formalParams = methodCall.Method.GetParameters();
            }
            else
            {
                // TODO: Check if the supplied expression is something that makes sense to evaluate as a method, e.g. MemberExpression (method.Body as MemberExpression)

                var objectMember = Expression.Convert(method.Body, typeof (object));
                var getterLambda = Expression.Lambda<Func<object>>(objectMember);
                var getter = getterLambda.Compile();
                var m = getter();


                var m2 = ((System.Delegate) m);

                var delegateDeclaringType = m2.Method.DeclaringType;
                var actualMethodDeclaringType = delegateDeclaringType.DeclaringType;
                scope = actualMethodDeclaringType.FullName;
                var ar = m2.Target;
                formalParams = m2.Method.GetParameters();
                //var m = (System.MulticastDelegate)((Expression.Lambda<Func<object>>(Expression.Convert(method.Body, typeof(object)))).Compile()())

                //throw new ArgumentException("Caller is not a method", "method");
            }


            // null list of paramMembers should disregard all parameters when creating key.
            if (paramMembers != null)
            {
                for (var i = 0; i < formalParams.Length; i++)
                {
                    var par = formalParams[i];
                    // empty list of paramMembers should be treated as using all parameters 
                    if (paramMembers.Length == 0 || paramMembers.Contains(par.Name))
                    {
                        var value = actual[i];
                        keys.Add(par.Name, value.ToString());
                    }
                }

                if (paramMembers.Length != 0 && keys.Count != paramMembers.Length)
                {
                    var notFound = paramMembers.Where(x => !keys.ContainsKey(x));
                    var notFoundString = string.Join(", ", notFound);

                    throw new ArgumentException("Unable to find the following parameters in supplied method: " + notFoundString, "paramMembers");
                }
            }

            return scope + "¤" + prefix +  "¤" + Flatten(keys);

        }


        private static object GetValueOfParameter<T>(Expression parameter)
        {
            LambdaExpression lambda = Expression.Lambda(parameter);
            var compiledExpression = lambda.Compile();
            var value = compiledExpression.DynamicInvoke();
            return value;
        }
}

然后,我有以下测试,可以正常工作:

        [Test]
        public void GetKey_From_Expression_Returns_Expected_Scope()
        {
            const string expectedScope = "MethodNameTests.DummyObject";
            var expected = expectedScope + "¤" + "SayHello" + "¤" + MethodKey.Flatten(new Dictionary<string, string>() { { "name", "Jens" } });

            var dummy = new DummyObject();

            var actual = MethodKey.GetKey(() => dummy.SayHello("Jens"), "name");

            Assert.That(actual, Is.Not.Null);
            Assert.That(actual, Is.EqualTo(expected));
        }

但是,如果我将 () =&gt; dummy.SayHello("Jens") 调用放入变量中,调用将失败。因为我在 GetKey 方法中不再得到MethodCallExpression,而是得到FieldExpressionMemberExpression 的子类。测试是:

        [Test]
        public void GetKey_Works_With_func_variable()
        {
            const string expectedScope = "MethodNameTests.DummyObject";
            var expected = expectedScope + "¤" + "SayHello" + "¤" + MethodKey.Flatten(new Dictionary<string, string>() { { "name", "Jens" } });

            var dummy = new DummyObject();

            Func<string> indirection = (() => dummy.SayHello("Jens"));

            // This fails. I would like to do the following, but the compiler
            // doesn't agree :)
            // var actual = MethodKey.GetKey(indirection, "name");
            var actual = MethodKey.GetKey(() => indirection, "name");

            Assert.That(actual, Is.Not.Null);
            Assert.That(actual, Is.EqualTo(expected));
        }

DummySayHello 方法定义很简单:

 public class DummyObject
    {
        public string SayHello(string name)
        {
            return "Hello " + name;
        }

        public string Meet(string person1, string person2 )
        {
            return person1 + " met " + person2;
        }
    }

我有两个问题:

  1. 有没有办法将变量indirection 发送到MethodKey.GetKey,并将其作为MethodCallExpression 类型获取?
  2. 如果没有,如果我得到MemberExpression,如何获取提供的方法的名称和值?我在代码的“else”部分尝试了一些位,但没有成功。

感谢任何帮助。

先谢谢了,很抱歉发了这么长的帖子。

【问题讨论】:

  • Func&lt;T&gt; 表示一个输入参数为零的函数。如果您将其更改为 Func&lt;R,I&gt;Func&lt;R,I1,I2&gt; 或类似名称,您是否看到参数类型发生了变化?

标签: c# dynamic lambda expression-trees func


【解决方案1】:

问题是您将其放入错误类型的变量中。您的方法需要 Expression&lt;Func&lt;T&gt;&gt; 并且您正在使用 Func&lt;string&gt; 类型的变量来存储它。以下应该可以解决您的问题:

Expression<Func<string>> foo = () => dummy.SayHello("Jens");
var actual = MethodKey.GetKey<string>(foo, "name");

converting a .net Func<T> to a .net Expression<Func<T>> 讨论了FuncExpression&lt;Func&gt; 之间的区别以及两者之间的转换,乍一看它说不要。编译器将它们变成完全不同的东西。所以在编译时让它成为正确的东西,它应该可以正常工作。

如果这不是一个选项,那么采用 Func 而不是 Expression 的重载可能对您有用。

请注意,在这两种情况下,我都会直接传递变量,而不是尝试在您的调用中将其变成新的表达式。

【讨论】:

  • 非常感谢。我不知道 Expressions 和 Funcs 实际上被编译成不同的东西。很高兴知道。因此,如果我想要同时获取方法名称和参数的包装器方法,并使用参数调用它,我需要将其分成两部分,一个带有 Func 参数,一个带有 Expression 参数。认为我需要再次选择“ANSI Common Lisp”...
猜你喜欢
  • 1970-01-01
  • 2023-03-24
  • 2014-05-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多