Obteniendo parámetros de Func variable

c# dynamic expression-trees func lambda

Pregunta

Tengo un problema bastante complicado. Estoy tratando de obtener una clave única de un método y sus parámetros formales y reales. El objetivo del método es tomar una llamada al método y devolver una clave única basada en 1) El nombre de la clase y el método y 2) El nombre y los valores de los parámetros con los que se llama.

El método se ve así (lo siento por todos los detalles, pero no puedo encontrar una manera sensata de hacer que el ejemplo sea más pequeño pero aún así explico mi problema)

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

Entonces, tengo la siguiente prueba, que funciona 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));
        }

Sin embargo, si pongo la llamada () => dummy.SayHello("Jens") en una variable, la llamada falla. Porque entonces ya no obtengo una MethodCallExpression en mi método GetKey, sino una FieldExpression (subclase de MemberExpression . La prueba es:

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

Las definiciones del método SayHello clase Dummy son triviales:

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

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

Tengo dos preguntas:

  1. ¿Hay alguna forma de enviar la variable MethodKey.GetKey indirection a MethodKey.GetKey y obtenerla como un tipo MethodCallExpression ?
  2. De lo contrario, ¿cómo puedo obtener el nombre y el valor del método suministrado si obtengo una MemberExpression en MemberExpression lugar? He intentado algunos bits en la parte "else" del código, pero no he tenido éxito.

Cualquier ayuda es apreciada.

Gracias de antemano, y lo siento por el largo post.

Respuesta aceptada

El problema es que lo estás poniendo en el tipo incorrecto de variable. Su método espera la Expression<Func<T>> y está utilizando una variable de tipo Func<string> para almacenarla. Lo siguiente debería solucionar su problema:

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

convertir una .net Func <T> en una .net Expression <Func <T>> discute las diferencias entre una Func y una Expression<Func> y la conversión entre las dos y de un vistazo dice que no. El compilador los convierte en cosas totalmente diferentes. Así que conviértalo en tiempo de compilación y debería funcionar bien.

Si esta no es una opción, posiblemente una sobrecarga que tome un Func en lugar de una Expresión podría funcionar para usted.

Tenga en cuenta que en ambos casos pasaría la variable directamente en lugar de intentar convertirla en una nueva expresión en su llamada.



Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow