En C#, Is Expression API es mejor que Reflection
.net (3)
Actualmente, estoy explorando las API de expresión de C #. Así que podría usar algo de ayuda para entender cómo funciona, incluida la diferencia entre Expresión y Reflexión. También quiero entender si las expresiones son meramente azúcar sintáctica, o son de hecho mejores que la reflexión en cuanto al rendimiento .
Se apreciarán buenos ejemplos y enlaces a buenos artículos. :-)
En cuanto a llamar a un método:
- La llamada directa no puede superarse en velocidad.
El uso de Expression API es globalmente similar al uso de
Reflection.Emit
oDelegate.CreateDelegate
velocidad (Se pueden medir pequeñas diferencias, ya que siempre es inútil optimizar la velocidad sin mediciones ni objetivos).Todos ellos generan IL y el marco compilará el código nativo en algún momento. Pero todavía paga el costo de un nivel indirecto para llamar al delegado y una llamada al método dentro de su delegado.
La expresión API es más limitada, pero es de un orden de magnitud más simple de usar, ya que no requiere que aprendas IL.
Dynamic Language Runtime, ya sea directamente o mediante la palabra clave
dynamic
de C # 4, agrega un poco de sobrecarga, pero permanece cerca de la emisión de código ya que almacena en la mayoría de los controles relacionados con los tipos de parámetros, el acceso y el resto.Cuando se usa a través de la palabra clave
dynamic
, también obtiene la sintaxis más precisa, ya que se parece a una llamada a método normal. Pero si usas la dinámica, estás limitado a las llamadas a métodos, mientras que la biblioteca puede hacer mucho más (ver IronPython )-
System.Reflection.MethodInfo.Invoke
es lento: además de lo que otros métodos necesitan para verificar los derechos de acceso, compruebe el recuento de argumentos, tipo, ... contraMethodInfo
cada vez que llame al método.
Jon Skeet también obtiene algunos puntos buenos en esta respuesta: Delegate.CreateDelegate vs DynamicMethod vs Expression
Algunas muestras, lo mismo hecho de diferentes maneras.
Ya se podía ver en el recuento de líneas y la complejidad qué soluciones son fáciles de mantener y cuáles se deben evitar desde el punto de vista del mantenimiento a largo plazo.
La mayoría de las muestras son inútiles pero demuestran las clases / sintaxis básicas de generación de código de C #, para obtener más información siempre hay MSDN.
PS: Dump es un método LINQPad .
public class Foo
{
public string Bar(int value) { return value.ToString(); }
}
void Main()
{
object foo = new Foo();
// We have an instance of something and want to call a method with this signature on it :
// public string Bar(int value);
Console.WriteLine("Cast and Direct method call");
{
var result = ((Foo)foo).Bar(42);
result.Dump();
}
Console.WriteLine("Create a lambda closing on the local scope.");
{
// Useless but i''ll do it at the end by manual il generation
Func<int, string> func = i => ((Foo)foo).Bar(i);
var result = func(42);
result.Dump();
}
Console.WriteLine("Using MethodInfo.Invoke");
{
var method = foo.GetType().GetMethod("Bar");
var result = (string)method.Invoke(foo, new object[] { 42 });
result.Dump();
}
Console.WriteLine("Using the dynamic keyword");
{
var dynamicFoo = (dynamic)foo;
var result = (string)dynamicFoo.Bar(42);
result.Dump();
}
Console.WriteLine("Using CreateDelegate");
{
var method = foo.GetType().GetMethod("Bar");
var func = (Func<int, string>)Delegate.CreateDelegate(typeof(Func<int, string>), foo, method);
var result = func(42);
result.Dump();
}
Console.WriteLine("Create an expression and compile it to call the delegate on one instance.");
{
var method = foo.GetType().GetMethod("Bar");
var thisParam = Expression.Constant(foo);
var valueParam = Expression.Parameter(typeof(int), "value");
var call = Expression.Call(thisParam, method, valueParam);
var lambda = Expression.Lambda<Func<int, string>>(call, valueParam);
var func = lambda.Compile();
var result = func(42);
result.Dump();
}
Console.WriteLine("Create an expression and compile it to a delegate that could be called on any instance.");
{
// Note that in this case "Foo" must be known at compile time, obviously in this case you want
// to do more than call a method, otherwise just call it !
var type = foo.GetType();
var method = type.GetMethod("Bar");
var thisParam = Expression.Parameter(type, "this");
var valueParam = Expression.Parameter(typeof(int), "value");
var call = Expression.Call(thisParam, method, valueParam);
var lambda = Expression.Lambda<Func<Foo, int, string>>(call, thisParam, valueParam);
var func = lambda.Compile();
var result = func((Foo)foo, 42);
result.Dump();
}
Console.WriteLine("Create a DynamicMethod and compile it to a delegate that could be called on any instance.");
{
// Same thing as the previous expression sample. Foo need to be known at compile time and need
// to be provided to the delegate.
var type = foo.GetType();
var method = type.GetMethod("Bar");
var dynamicMethod = new DynamicMethod("Bar_", typeof(string), new [] { typeof(Foo), typeof(int) }, true);
var il = dynamicMethod.GetILGenerator();
il.DeclareLocal(typeof(string));
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Call, method);
il.Emit(OpCodes.Ret);
var func = (Func<Foo, int, string>)dynamicMethod.CreateDelegate(typeof(Func<Foo, int, string>));
var result = func((Foo)foo, 42);
result.Dump();
}
Console.WriteLine("Simulate closure without closures and in a lot more lines...");
{
var type = foo.GetType();
var method = type.GetMethod("Bar");
// The Foo class must be public for this to work, the "skipVisibility" argument of
// DynamicMethod.CreateDelegate can''t be emulated without breaking the .Net security model.
var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(
new AssemblyName("MyAssembly"), AssemblyBuilderAccess.Run);
var module = assembly.DefineDynamicModule("MyModule");
var tb = module.DefineType("MyType", TypeAttributes.Class | TypeAttributes.Public);
var fooField = tb.DefineField("FooInstance", type, FieldAttributes.Public);
var barMethod = tb.DefineMethod("Bar_", MethodAttributes.Public, typeof(string), new [] { typeof(int) });
var il = barMethod.GetILGenerator();
il.DeclareLocal(typeof(string));
il.Emit(OpCodes.Ldarg_0); // this
il.Emit(OpCodes.Ldfld, fooField);
il.Emit(OpCodes.Ldarg_1); // arg
il.Emit(OpCodes.Call, method);
il.Emit(OpCodes.Ret);
var closureType = tb.CreateType();
var instance = closureType.GetConstructors().Single().Invoke(new object[0]);
closureType.GetField(fooField.Name).SetValue(instance, foo);
var methodOnClosureType = closureType.GetMethod("Bar_");
var func = (Func<int, string>)Delegate.CreateDelegate(typeof(Func<int, string>), instance,
closureType.GetMethod("Bar_"));
var result = func(42);
result.Dump();
}
}
Este tipo realmente lo midió.
En resumen: expresión compilada que se almacena en caché en una var estática y se reutiliza: realiza 20 veces más rápido que la reflexión.
La reflexión funciona más lento. Para un buen artículo sobre este, vea this artículo.