在C#中,Expression API比Reflection更好

Naw*_*waz 43 .net c# reflection expression

如今,我正在探索C#Expression API.所以我可以使用一些帮助来理解它是如何工作的,包括Expression和Reflection之间的区别.我还想了解表达式是否仅仅是语法糖,还是它们确实比反射性能更好?

很好的例子以及与好文章的链接将不胜感激.:-)

Jul*_*lia 48

关于调用一种方法:

  • 直接打电话不能快速打败.
  • 使用Expression API在全局上类似于使用Reflection.EmitDelegate.CreateDelegate速度方式(可以测量小的差异;总是在没有测量和目标的情况下优化速度是无用的).

    它们都生成IL,框架会在某些时候将其编译为本机代码.但是,您仍然需要支付一个间接级别的成本来调用委托和委托中的一个方法调用.

    表达式API更受限制,但使用起来要简单一个数量级,因为它不需要您学习IL.

  • 直接使用或通过dynamicC#4关键字使用的动态语言运行时增加了一点开销但保持接近发射代码,因为它缓存了与参数类型,访问和其余部分相关的大多数检查.

    当通过dynamic关键字使用时,它也会获得最新的语法,因为它看起来像普通的方法调用.但是如果你使用动态,你只能使用方法调用,而库可以做更多的事情(参见IronPython)

  • System.Reflection.MethodInfo.Invoke很慢:除了需要检查访问权限的其他方法之外,还要检查MethodInfo每次调用方法时的参数count,type,....

Jon Skeet在这个答案中也得到了一些好处:Delegate.CreateDelegate vs DynamicMethod vs Expression


一些样品,同样的事情做了不同的方式.

您可以从线数和复杂性中看出哪些解决方案易于维护,哪些应该从长期维护的角度来避免.

大多数示例都毫无意义,但它们演示了C#的基本代码生成类/语法,更多信息总是有MSDN

PS:转储是一种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();
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 从.NET 4.0开始,Expression API并不局限于此.您可以使用它创建几乎与任何C#代码相当的表达式. (3认同)
  • @TDaver:通常它更快.要像我在我的示例中那样使用返回的委托,您需要在编译时知道返回和参数类型,但优点是一旦转换为正确的委托类型(参数类型,计数,安全性,...),就会跳过大量检查. .) (2认同)