Obtenir les paramètres de Func variable

c# dynamic expression-trees func lambda

Question

J'ai un problème plutôt compliqué. J'essaie d'obtenir une clé unique à partir d'une méthode et de ses paramètres formels et réels. Le but de la méthode est de prendre un appel de méthode et de renvoyer une clé unique basée sur 1) le nom de la classe et de la méthode et 2) le nom et les valeurs des paramètres avec lesquels elle est appelée.

La méthode ressemble à ceci (désolé pour tous les détails, mais je ne trouve pas de moyen sensé de rendre l'exemple plus petit, tout en expliquant mon problème)

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

Ensuite, j'ai le test suivant, qui fonctionne bien:

        [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));
        }

Cependant, si je mets l'appel () => dummy.SayHello("Jens") dans une variable, l'appel échoue. Parce que je ne reçois plus alors une MethodCallExpression dans ma méthode FieldExpression , mais une FieldExpression (sous-classe de MemberExpression . Le test est le suivant:

        [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));
        }

Les Dummy classe SayHello définitions de méthode sont négligeables:

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

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

J'ai deux questions:

  1. Est-il possible d'envoyer la variable indirection à MethodKey.GetKey et de l'obtenir en tant que type MethodCallExpression ?
  2. Si non, comment puis-je obtenir le nom et la valeur de la méthode fournie si MemberExpression plutôt MemberExpression ? J'ai essayé quelques bits dans la partie "else" du code, mais je n'y suis pas parvenu.

Toute aide est appréciée.

Merci d'avance et désolé pour le long post.

Réponse acceptée

Le problème est que vous le mettez dans le mauvais type de variable. Votre méthode attend Expression<Func<T>> et vous utilisez une variable de type Func<string> pour la stocker. Ce qui suit devrait résoudre votre problème:

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

convertir un .net Func <T> en une expression .net <Func <T >> traite des différences entre un Func et un Expression<Func> et effectue la conversion entre les deux et d'un coup d'œil, il ne dit pas. Le compilateur en fait des choses totalement différentes. Faites-en donc la bonne chose au moment de la compilation et cela devrait fonctionner correctement.

Si ce n'est pas une option, une surcharge prenant un Func au lieu d'une expression peut fonctionner pour vous.

Notez que dans les deux cas, je transmettrais directement la variable plutôt que d'essayer de la transformer en une nouvelle expression dans votre appel.




Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi