.net performance reflection methodinfo func

.net - ¿Puedes obtener un Func<T>(o similar) de un objeto MethodInfo?



performance reflection (6)

Me doy cuenta de que, en términos generales, hay implicaciones de rendimiento del uso de la reflexión. (Yo mismo no soy fanático de la reflexión en realidad, esta es una pregunta puramente académica).

Supongamos que existe alguna clase que se parece a esto:

public class MyClass { public string GetName() { return "My Name"; } }

Ten paciencia conmigo aquí. Sé que si tengo una instancia de MyClass llamada x , puedo llamar a x.GetName() . Además, podría establecer una variable Func<string> para x.GetName .

Ahora esta es mi pregunta. Digamos que no sé que la clase anterior se llama MyClass ; Tengo un objeto, x , pero no tengo idea de qué es. Podría verificar si ese objeto tiene un método GetName al hacer esto:

MethodInfo getName = x.GetType().GetMethod("GetName");

Supongamos que getName no es nulo. Entonces, ¿podría además comprobar si getName.ReturnType == typeof(string) y getName.GetParameters().Length == 0 , y en este punto, no estaría bastante seguro de que el método representado por mi objeto getName podría Definitivamente ser lanzado a un Func<string> , de alguna manera?

Me doy cuenta de que hay un MethodInfo.Invoke , y también me doy cuenta de que siempre podría crear un Func<string> como:

Func<string> getNameFunc = () => getName.Invoke(x, null);

Supongo que lo que estoy preguntando es si hay alguna manera de ir desde un objeto MethodInfo al método real que representa, incurriendo en el costo de rendimiento de la reflexión en el proceso , pero después de ese punto es posible llamar el método directamente (a través, por ejemplo, , una Func<string> o algo similar) sin una penalización de rendimiento.

Lo que estoy visualizando podría verse más o menos así:

// obviously this would throw an exception if GetActualInstanceMethod returned // something that couldn''t be cast to a Func<string> Func<string> getNameFunc = (Func<string>)getName.GetActualInstanceMethod(x);

(Me doy cuenta de que no existe, me pregunto si hay algo así ).


Aquí está mi respuesta, construyendo un árbol de expresiones. A diferencia de las otras respuestas, el resultado ( getNameFunc ) es una función que está vinculada a la instancia original, sin tener que pasarla como un parámetro.

class Program { static void Main(string[] args) { var p = new Program(); var getNameFunc = GetStringReturningFunc(p, "GetName"); var name = getNameFunc(); Debug.Assert(name == p.GetName()); } public string GetName() { return "Bob"; } static Func<string> GetStringReturningFunc(object x, string methodName) { var methodInfo = x.GetType().GetMethod(methodName); if (methodInfo == null || methodInfo.ReturnType != typeof(string) || methodInfo.GetParameters().Length != 0) { throw new ArgumentException(); } var xRef = Expression.Constant(x); var callRef = Expression.Call(xRef, methodInfo); var lambda = (Expression<Func<string>>)Expression.Lambda(callRef); return lambda.Compile(); } }


Este tipo reemplaza mi respuesta anterior porque esto, aunque es una ruta un poco más larga, le da una llamada rápida al método y, a diferencia de algunas de las otras respuestas, le permite pasar por diferentes instancias (en caso de que se encuentre con varias instancias) del mismo tipo). SI no quieres eso, mira mi actualización en la parte inferior (o mira la respuesta de Ben M).

Aquí hay un método de prueba que hace lo que quiere:

public class TestType { public string GetName() { return "hello world!"; } } [TestMethod] public void TestMethod2() { object o = new TestType(); var input = Expression.Parameter(typeof(object), "input"); var method = o.GetType().GetMethod("GetName", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public); //you should check for null *and* make sure the return type is string here. Assert.IsFalse(method == null && !method.ReturnType.Equals(typeof(string))); //now build a dynamic bit of code that does this: //(object o) => ((TestType)o).GetName(); Func<object, string> result = Expression.Lambda<Func<object, string>>( Expression.Call(Expression.Convert(input, o.GetType()), method), input).Compile(); string str = result(o); Assert.AreEqual("hello world!", str); }

Una vez que compile el delegado una vez, puede almacenarlo en un diccionario:

Dictionary<Type, Func<object, string>> _methods;

Todo lo que hace entonces es agregarlo al diccionario, usando el tipo del objeto entrante (de GetType ()) como la clave. En el futuro, primero verifique si tiene un delegado listo para hornear en el diccionario (e inícielo si es así), de lo contrario lo compilará primero, lo agregará y luego lo invocará.

Por cierto, esta es una versión muy simplificada del tipo de cosas que hace el DLR para su mecanismo de despacho dinámico (en términos de C #, es cuando se utiliza la palabra clave ''dinámica'').

Y finalmente

Si, como algunas personas han mencionado, simplemente quieres hornear un Func vinculado directamente al objeto que recibes, entonces haces esto:

[TestMethod] public void TestMethod3() { object o = new TestType(); var method = o.GetType().GetMethod("GetName", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public); Assert.IsFalse(method == null && !method.ReturnType.Equals(typeof(string))); //this time, we bake Expression.Constant(o) in. Func<string> result = Expression.Lambda<Func<string>>( Expression.Call(Expression.Constant(o), method)).Compile(); string str = result(); //no parameter this time. Assert.AreEqual("hello world!", str); }

Sin embargo, tenga en cuenta que una vez que el árbol de expresiones se descarta, debe asegurarse de que el o permanezca en el alcance, de lo contrario, podría obtener algunos resultados desagradables. La manera más fácil sería aferrarse a una referencia local (en una instancia de clase, tal vez) durante la vida de su delegado. (Eliminado como resultado de los comentarios de Ben M)


La forma más sencilla de hacerlo es a través de Delegate.CreateDelegate :

Func<string> getNameFunc = (Func<string>) Delegate.CreateDelegate( typeof(Func<string>), x, getName);

Tenga en cuenta que esto vincula getNameFunc a x , por lo que para cada x necesitaría crear una nueva instancia de delegado. Esta opción es mucho menos complicada que los ejemplos basados ​​en Expression . Sin embargo, con los ejemplos basados ​​en expresiones, es posible crear un Func<MyClass, string> getNameFuncForAny una vez, que puede reutilizar para cada instancia de un MyClass .

Para crear un getNameFuncForAny, necesitarías un método como

public Func<MyClass, string> GetInstanceMethod(MethodInfo method) { ParameterExpression x = Expression.Parameter(typeof(MyClass), "it"); return Expression.Lambda<Func<MyClass, string>>( Expression.Call(x, method), x).Compile(); }

que puedes usar así:

Func<MyClass, string> getNameFuncForAny = GetInstanceMethod(getName); MyClass x1 = new MyClass(); MyClass x2 = new MyClass(); string result1 = getNameFuncForAny(x1); string result2 = getNameFuncForAny(x2);

Si no quiere estar vinculado a Func<MyClass, string> , puede definir

public TDelegate GetParameterlessInstanceMethod<TDelegate>(MethodInfo method) { ParameterExpression x = Expression.Parameter(method.ReflectedType, "it"); return Expression.Lambda<TDelegate>( Expression.Call(x, method), x).Compile(); }


Puede construir un Árbol de Expresión que represente una lambda que llame a este método y luego Compile() para que las llamadas adicionales sean tan rápidas como las llamadas compiladas estándar.

Alternativamente, escribí este método hace un buen tiempo basado en un gran artículo de MSDN, que genera un contenedor usando IL para llamar a cualquier MethodInfo mucho más rápido que con MethodInfo.DynamicInvoke ya que una vez que se genera el código, casi no hay sobrecarga en una llamada normal .


Sí, eso es posible:

Func<string> func = (Func<string>) Delegate.CreateDelegate(typeof(Func<string>), getName);


Un enfoque fuera de mi cabeza sería usar dinámico. Entonces podrías decir algo como esto:

if( /* This method can be a Func<string> */) { dynamic methodCall = myObject; string response = methodCall.GetName(); }