Obtenir les paramètres de Func <T> 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:

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

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:

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

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

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

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