In C#, Is Expression API better than Reflection

14,824

Solution 1

Regarding calling one method :

  • Direct call can't be beaten speed-wise.
  • Using Expression API is globally similar to using Reflection.Emit or Delegate.CreateDelegate speed-wise (Small differences could be measured; as always optimizing for speed without measurements and goals is useless).

    They all generate IL and the framework will compile it to native code at some point. But you still pay the cost of one indirection level for calling the delegate and one method call inside your delegate.

    The expression API is more limited, but an order of magnitude simpler to use, as it doesn't require you to learn IL.

  • The Dynamic Language Runtime either used directly or via the dynamic keyword of C# 4 add a little overhead but stay near emitting code as it cache most checks related to parameter types, access and the rest.

    When used via the dynamic keyword it's also get the neatest syntax as it looks like a normal method call. But if you use dynamic you are limited to method calls while the library is able to do a lot more (See IronPython)

  • System.Reflection.MethodInfo.Invoke is slow : in addition to what other methods do it need to check access rights, check arguments count, type, ... against the MethodInfo each time you call the method.

Jon Skeet also get some good points in this answer : Delegate.CreateDelegate vs DynamicMethod vs Expression


Some samples, the same thing done different ways.

You could already see from the line count and complexity which solutions are easy to maintain and which should be avoided from a long term maintenance standpoint.

Most of the samples are pointless but they demonstrate the basic code generation classes / syntaxes of C#, for more info there is always the MSDN

PS: Dump is a LINQPad method.

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

Solution 2

Reflection does perform slower. For a good article on it see this article.

Solution 3

This guy actually measured it.

http://www.palmmedia.de/Blog/2012/2/4/reflection-vs-compiled-expressions-vs-delegates-performance-comparison

In short: compiled expression that is cached to a static var and reused - performs much faster than reflection.

Share:
14,824
Nawaz
Author by

Nawaz

Following Rust and Haskell isocpp.org/wiki/faq Contact me on LinkedIn. Religion of C Correcting Grammar for Microsoft Products and Technology

Updated on June 25, 2022

Comments

  • Nawaz
    Nawaz almost 2 years

    Nowadays, I'm exploring C# Expression APIs. So I could use some help understanding how it works, including the difference between Expression and Reflection. I also want to understand if Expressions are merely syntactic sugar, or are they indeed better than Reflection performance-wise?

    Good examples as well as links to good articles would be appreciated. :-)

    • Julien Roncaglia
      Julien Roncaglia over 13 years
      By reflection you mean calling things using the System.Reflection.MethodInfo class for example or emitting via Reflection.Emit ?
    • Nawaz
      Nawaz over 13 years
      @VirtualBlackFox: by reflection I mean things which can be done with Reflection, should they be done by Expression? That includes I think code-generation also. :-)
    • user1703401
      user1703401 over 13 years
      Best way to tackle this is by not assuming they have anything in common. If you mean Reflection.Emit, that already gets used by Expressions.
    • Paul Turner
      Paul Turner over 13 years
      This question needs to be narrowed down.
  • Julien Roncaglia
    Julien Roncaglia over 13 years
    I may add samples while in lunch break :D Reflection.Emit is especially tricky to write sample on without at least testing in Linqpad.
  • LukeH
    LukeH over 13 years
    This is misleading since you can always compile a reflected method call etc with Delegate.CreateDelegate, for example. In that situation it'll be much the same as a compiled expression.
  • Konstantin Oznobihin
    Konstantin Oznobihin over 13 years
    Expression API is not so limited starting from .NET 4.0. You can create expressions equivalent to almost any C# code with it.
  • TDaver
    TDaver over 13 years
    If I have a base class which needs to call methods of the subclass dynamically (with getting the names dynamically) then I cannot use direct or 'dynamic' since they requre compile time knowledge of the name. I use MethodInfo.Invoke (and PropertyInfo.SetValue) for simplicity sake. For every save and load operation (not realtime) one of these is called on every property and on some method of the subclass. Expression really seems like overkill, but does CreateDelegate offer me anything which MemberInfos don't?
  • Julien Roncaglia
    Julien Roncaglia over 13 years
    @TDaver : Normally it's faster. To use the returned delegate as I did in my sample you need to know the return and parameter types at compilation time but the advantage is that you skip lots of checks once casted to the correct delegate type (arguments type, count, security, ...)
  • TDaver
    TDaver over 13 years
    ah. return and parameter types at compile time. :) That I don't have. Thanks, now I understand the "when to use what"...
  • Nawaz
    Nawaz about 13 years
    @VirtualBlackFox: You said i'll add Reflection.Emit samples later.... I'm waiting for it :-)
  • Julien Roncaglia
    Julien Roncaglia about 13 years
    @Nawaz: I sent myself a mail with my current state of the code and never finished it, not its done so I add it but anyway the code is really simple once you know a bit of IL.