Click here to Skip to main content
15,881,172 members
Articles / Programming Languages / MSIL

Fasterflect - a fast and simple API for Reflection invocation

Rate me:
Please Sign up or sign in to vote.
4.82/5 (14 votes)
9 Aug 2009Apache8 min read 70.4K   937   50   15
Examine the implementation, API, and performance for Fasterflect, an alternative to the .NET Reflection API.

The binary and source code for Fasterflect 1.0 (beta) can be found in the above links. You can also check out the Fasterflect CodePlex page for latest development code.

Background

If you think the built-in Reflection API in the .NET framework is too verbose for many circumstances and has poor performance, you are not alone. I think that too. And yet, being able to write reflective code is an inevitable (technical) requirement in most of the applications I develop. This is why I have built Fasterflect (read either "Faster-flect" or "Fast-reflect") as an alternative API to the .NET Reflection functionality.

The goal of this library is to make Reflection calls as straightforward as possible while offering better performance than normal Reflection calls. In this article, I'll describe the approach chosen to build Fasterflect, its APIs, and measure its performance via some benchmarks.

Implementation

Fasterflect is built based on .NET 2.0's Dynamic Methods. For those who are not familiar with Dynamic Methods, it is basically a feature which allows methods to be built at runtime using the Common Intermediate Language (CIL). After being built, dynamic methods can be casted into delegates and invoked just like normal CLR delegates.

Let's discuss a bit of the background to see how such a feature can help with the development of Fasterflect. Let's say we want to invoke a method on an object whose type we cannot (or do not want to) compile with at compile-time. We can do that using the built-in .NET Reflection API, something like this:

C#
string someTypeName = …; // load the type name from a config file
Type type = Assembly.GetExecutingAssembly().GetType(someTypeName); 
string methodToBeInvoked = …; // load method name from a config file
MethodInfo info = type.GetMethod(methodToBeInvoked, BindingFlags.Instance);
object result = info.Invoke(Activator.CreateInstance(type));

This works. But the performance of such Reflection calls is very poor, especially when we start executing the methods many times - a common scenario in most applications using Reflection. Not only that, the API is not very fun to play with although the above is probably one of the least verbose pieces of code based on .NET Reflection (i.e., we have not put visibility and method overloading etc. into the picture).

The latter issue is easy to address, we can just simply provide some wrapper classes offering a simpler and more fluent interface to reflection methods. The Fasterflect API simplifies things by abstracting away the concepts of MethodInfo, PropertyInfo etc., which most applications don't need to deal with, and make assumptions about the most frequently used scenarios (e.g., when we want to set the value of a field reflectively, we usually do not care to know whether that field is private or not). Following this approach will make the API very simple and lightweight. The downside is that it cannot be used as a complete replacement for the built-in .NET Reflection API in all applications out there. For example, if you only need to query the metadata for types and methods for other purposes than performing invocation, Fasterflect doesn't help. But as most applications only need to perform reflective invocations, at least in my experience, I think it's worth the while to optimize the API for such scenarios.

The former issue can be addressed by generating code at runtime. Get back to our example, the idea is that by the time someTypeName and methodToBeInvoked are already initialized with values from a config file at runtime (let's say the type name is Person and the method name is GetName), we already have enough information to construct a non-reflective piece of code like below. (Assume GenericInvocator is an interface with just one method, object GenericInvoke(object), that we already coded.)

C#
public class ConcreteInvocator : GenericInvocator 
{
    public object Invoke(object target) 
    {
        var person = (Person)target;
        return target.GetName();
    }
}

After generating the code at runtime, we can compile it on the fly using either the csc.exe compiler or a CodeDomProvider. Assume we compile the code into an assembly, we can load and perform the invocation like below:

C#
object obj = Assembly.LoadFrom(generatedDll).CreateInstance("ConcreteInvocator")
GenericInvocator personInvocator = obj as GenericInvocator;
string name = (string)personInvocator.Invoke(personObject);

Notice that we only need to generate the code once, and then can reuse it for all subsequent invocations, which will be very fast because it's nothing but the direct call to person.GetName().

That is the idea. However, generating and compiling high-level code (or building a CodeDOM tree) is not the only option. Another option is generating CIL directly and skipping all the compilation overhead. We can do that either by using classes in the Reflection Emit namespace to build a .NET assembly, module, and type at runtime, or by using Dynamic Methods. Among all these options, I think Dynamic Methods is the most suitable solution. I'm not going to provide a comprehensive comparison among these options in this article, but instead highlight three key benefits of Dynamic Methods over its alternatives:

  • Dynamic Methods can be declared to skip visibility. If a member is declared as private, the body of a dynamic method can still access it directly instead of via Reflection. This is not the case with the alternatives.
  • Dynamic Methods can be garbage-collected while methods built with the alternatives cannot be unloaded once loaded into the app-domain.
  • We don't need to write code to build modules, types etc.; instead, we can just write code for Dynamic Methods, and .NET takes care of the rest.

The code to generate a dynamic method invoking a no-argument instance method of a specific type would look something like below. (Assume the delegate MethodInvoker is already declared as public delegate object MethodInvoker(object target);.)

C#
public MethodInvoker CreateDelegate(Type targetType, string methodName) 
{
    var method= new DynamicMethod("invoke", MethodAttributes.Static |   
                                  MethodAttributes.Public, CallingConventions.Standard, 
                                  typeof(object), new Type[0], targetType, true);
    ILGenerator generator = method.GetILGenerator();
    MethodInfo methodInfo =  targetType.GetMethod(methodName, BindingFlags.Instance);
    generator.Emit(OpCodes.Ldarg_0);
    generator.Emit(OpCodes.Castclass, targetType);
    generator.Emit(OpCodes.Callvirt, methodInfo);
    if (methodInfo.ReturnType == typeof(void))
    {
        generator.Emit(OpCodes.Ldnull);
    }
    else
    {
     if (methodInfo.ReturnType.IsValueType)
     {
         generator.Emit(OpCodes.Box, methodInfo.ReturnType);
     }
    }
    generator.Emit(OpCodes.Ret);
    return method.CreateDelegate(typeof(MethodInvoker));
}

Now, you can invoke the dynamic method simply via the returned delegate:

C#
MethodInvoker  invoker = CreateDelegate(personType, "GetName");
string name = (string)invoker.Invoke(personObject);

If you are already familiar with CIL, the code for the dynamic method should be instantly straightforward. If not, you can consult the abundance of documentation about CIL on the Internet. You only need to do this if you want to understand the fine-grain implementation details of Fasterflect. If you only need to use the library effectively, or simply want to have enough information to make the decision to use it or not, the highlights so far on the implementation details should be more than sufficient. That said, let's explore the APIs provided by Fasterflect and observe the improved performance it brings about.

The APIs

Before exploring the APIs provided by Fasterflect, let's first look at the class we will use to perform reflective invocations on the Person class. There is nothing fancy about this class; it is just a POCO with a bunch of static and/or instance constructors, fields, properties, methods, and indexers.

C#
class Person
{
    private int id;
    private int milesTraveled;
    public int Id
    {
        get { return id; }
        set { id = value; }
    }
    public string Name { get; private set; }
    private static int InstanceCount;

    public Person() : this(0) {}
    public Person(int id) : this(id, string.Empty) { }
    public Person(int id, string name)
    {
        Id = id;
        Name = name;
        InstanceCount++;
    }

    public char this[int index]
    {
        get { return Name[index]; }
    }

    private void Walk(int miles) 
    {
        milesTraveled += miles;
    }

    private static void IncreaseInstanceCount()
    {
        InstanceCount++;
    }

    private static int GetInstanceCount()
    {
        return InstanceCount;
    }
}

Fasterflect provides two flavors of API, each with pros and cons. (In fact, there are three APIs, but the third one is not a commonly used API - check out the library code documentation for details on this instead.)

The first API is the Default API. This API comprises of a bunch of extension methods for System.Type and System.Object. The benefit of this API is that it is very easy to use. The downsize is that while this API is just the wrapper over the dynamic method generation approach described in the previous section, it is just only 6-10 times faster than normal .NET Reflection invocations. The reason is that there are a number of indirections going on between the API and the invocation on the dynamic method. This is an inevitable cost of having a wrapper API, but it is obviously an area subject to further optimization in upcoming versions of Fasterflect.

Let's explore this API via some code demonstration. I have included a lot of comments to replace the otherwise verbose text narrative.

C#
// Load a type reflectively, just to look like real-life scenario
Type type = Assembly.GetExecutingAssembly().GetType("FasterflectSample.Person");

// Person.InstanceCount should be 0 since no instance is created yet
AssertTrue(type.GetField<int>("InstanceCount") == 0);

// Invokes the no-arg constructor
object obj = type.Construct();

// Double-check if the constructor is invoked successfully or not
AssertTrue(null != obj);

// Now, Person.InstanceCount should be 1
AssertTrue(1 == type.GetField<int>("InstanceCount"));

// What if we don't know the type of InstanceCount?  
// Just specify object as the type parameter
Console.WriteLine(type.GetField<object>("InstanceCount"));

// We can bypass the constructor to change the value of Person.InstanceCount
type.SetField("InstanceCount", 2);
AssertTrue(2 == type.GetField<int>("InstanceCount"));

// Let's invoke Person.IncreaseCounter() static method to increase the counter
// In fact, let's chain the calls to increase 2 times
type.Invoke("IncreaseInstanceCount")
    .Invoke("IncreaseInstanceCount");
AssertTrue(4 == type.GetField<int>("InstanceCount"));

// Now, let's retrieve Person.InstanceCount via the static method GetInstanceCount
AssertTrue(4 == type.Invoke<int>("GetInstanceCount"));

// If we're not interested in the return (e.g. only in the side effect), 
// we don't have to specify the type parameter (and can chain the result).
AssertTrue(4 == type.Invoke("GetInstanceCount")
                    .Invoke("GetInstanceCount")
                    .Invoke<int>("GetInstanceCount"));

// Now, invoke the 2-arg constructor
obj = type.Construct(new[] {typeof (int), typeof (string)}, 
                     new object[] {1, "Doe"});

// The id field should be 1, so is Id property
AssertTrue(1 == obj.GetField<int>("id"));
AssertTrue(1 == obj.GetProperty<int>("Id"));

// Now, modify the id
obj.SetField("id", 2);
AssertTrue(2 == obj.GetField<int>("id"));
AssertTrue(2 == obj.GetProperty<int>("Id"));

// Let's use the indexer to retrieve the character at index 1st
AssertTrue('o' == obj.GetIndexer<char>(new[] {typeof (int)}, new object[] {1}));

// We can chain calls
obj.SetField("id", 3).SetProperty("Name", "Buu");
AssertTrue(3 == obj.GetProperty<int>("Id"));
AssertTrue("Buu" == obj.GetProperty<string>("Name"));
 
// How about modifying both properties at the same time using an anonymous sample
obj.SetProperties(new {Id = 4, Name = "Nguyen"});
AssertTrue(4 == obj.GetProperty<int>("Id"));
AssertTrue("Nguyen" == obj.GetProperty<string>("Name"));

// Let's have the folk walk 6 miles (and try chaining again)
obj.Invoke("Walk", new[] { typeof(int) }, new object[] { 1 })
   .Invoke("Walk", new[] { typeof(int) }, new object[] { 2 })
   .Invoke("Walk", new[] { typeof(int) }, new object[] { 3 });

// Double-check the current value of the milesTravelled field
AssertTrue(6 == obj.GetField<int>("milesTraveled"));

The second API is called the Cache API and is a little bit more verbose to use, but offers huge gain in performance (usually, a few hundred times faster than normal Reflection calls). The idea is that you can get a direct handle to the returned delegate generated by a dynamic method and keep reusing it instead of going through many layers of indirection (as in the default API). Below is how you can use the Cache API.

C#
// Load a type reflectively, just to look like real-life scenario
Type type = Assembly.GetExecutingAssembly().GetType("FasterflectSample.Person");

var range = Enumerable.Range(0, 10).ToList();

// Let's cache the getter for InstanceCount
StaticAttributeGetter count = type.DelegateForGetStaticField("InstanceCount");

// Now cache the 2-arg constructor of Person and playaround with the delegate returned
int currentInstanceCount = (int)count();
ConstructorInvoker ctor = type.DelegateForConstruct(new[] { typeof(int), typeof(string) });
range.ForEach(i =>
{
    var obj = ctor(i, "_" + i);
    AssertTrue(++currentInstanceCount == (int)count());
    AssertTrue(i == obj.GetField<int>("id"));
    AssertTrue("_" + i == obj.GetProperty<string>("Name"));
});

// Whatever thing we can do with the normal API, we can do with the cache API.
// For example:
AttributeSetter nameSetter = type.DelegateForSetProperty("Name");
AttributeGetter nameGetter = type.DelegateForGetProperty("Name");
object person = ctor(1, "Buu");
AssertTrue("Buu" == nameGetter(person));
nameSetter(person, "Doe");
AssertTrue("Doe" == nameGetter(person));

// Another example
person = type.Construct();
MethodInvoker walk = type.DelegateForInvoke("Walk", new[] { typeof(int) });
range.ForEach(i => walk(person, i));
AssertTrue(range.Sum() == person.GetField<int>("milesTraveled"));

Performance

I built an application to show the benchmark of invocations (including constructors, fields, properties, methods, and indexers) for each of the following invocation types: direct, built-in .NET Reflection, Fasterflect Default API, and Fasterflect Cache API. You can check out the full code for the benchmark application in the source code download. In this article, I only list the code for constructor invocation benchmark and method invocation benchmark.

C#
private static readonly int[] Iterations = new[] { 20000, 2000000 };
private static readonly object[] NoArgArray = new object[0];
private static readonly object[] ArgArray = new object[]{10};
private static readonly Type TargetType = typeof (Person);
private static readonly Person TargetPerson = new Person();
private static readonly Stopwatch Watch = new Stopwatch();

private static void RunConstructorBenchmark()
{
    ConstructorInfo ctorInfo = null;
    ConstructorInvoker invoker = null;;
    var initMap = new Dictionary<string, Action>
      {
            {"Init info", () => 
              { ctorInfo = typeof (Person).GetConstructor(BindingFlags.Instance | 
                           BindingFlags.NonPublic, null, new Type[0], null); }},
            {"Init ctorInvoker", () => 
              {invoker = typeof(Person).DelegateForConstruct();}}
      };
    var actionMap = new Dictionary<string, Action>
      {
            {"Direct ctor", () => new Person() },
            {"Reflection ctor", () => ctorInfo.Invoke(NoArgArray)},
            {"Fasterflect ctor", () => typeof(Person).Construct() },
            {"Fasterflect cached ctor", () => invoker(NoArgArray) },
      };
    Execute("Construction Benchmark", initMap, actionMap);
}

private static void RunMethodInvocationBenchmark()
{
    MethodInfo noArgMethodInfo = null;
    MethodInfo argMethodInfo = null;

    MethodInvoker noArgInvoker = null;
    MethodInvoker argInvoker = null;

    var initMap = new Dictionary<string, Action>
      {
            {"Init no-arg info", () => 
               { noArgMethodInfo = TargetType.GetMethod("Walk", 
                 BindingFlags.NonPublic | BindingFlags.Instance, 
                 null, new Type[0], null); }},
            {"Init arg info", () => 
               { argMethodInfo = TargetType.GetMethod("Walk", 
                 BindingFlags.NonPublic | BindingFlags.Instance, 
                 null, new Type[]{typeof(int)}, null); }},
            {"Init no-arg invoker", () => 
               { noArgInvoker = TargetType.DelegateForInvoke("Walk"); }},
            {"Init arg invoker", () => 
               { argInvoker = TargetType.DelegateForInvoke("Walk", 
                              new[] { typeof(int) }); }}
      };

    var actionMap = new Dictionary<string, Action>
      {
            {"Direct invoke", () => TargetPerson.Walk()},
            {"Direct invoke (arg)", () => TargetPerson.Walk(10)},
            {"Reflection invoke", () => 
                        noArgMethodInfo.Invoke(TargetPerson, NoArgArray)},
            {"Reflection invoke (arg)", () => 
                        argMethodInfo.Invoke(TargetPerson, ArgArray)},
            {"Fasterflect invoke", () => 
                        TargetPerson.Invoke("Walk")},
            {"Fasterflect invoke (arg)", () => 
                        TargetPerson.Invoke("Walk", 
                        new[]{typeof(int)}, ArgArray)},
            {"Fasterflect cached invoke", () => 
                        noArgInvoker(TargetPerson, NoArgArray)},
            {"Fasterflect cached invoke (arg)", () => 
                        argInvoker(TargetPerson, ArgArray)}
      };
    Execute("Method Benchmark", initMap, actionMap);
}

private static void Execute(string name, Dictionary<string, Action> initMap, 
                            Dictionary<string, Action> actionMap)
{
    Console.WriteLine("------------- {0} ------------- ", name);

    Console.WriteLine("*** Initialization");
    Measure(Watch, initMap, 1);
    Console.WriteLine();

    foreach (var iterationCount in Iterations)
    {
        Console.WriteLine("*** Executing for {0} iterations", iterationCount);
        Measure(Watch, actionMap, iterationCount);
        Console.WriteLine();
    }
    Console.WriteLine();
}

private static void Measure(Stopwatch watch, Dictionary<string, Action> actionMap, 
    int iterationCount)
{
    foreach (var entry in actionMap)
    {
        watch.Start();
        for (int i = 0; i < iterationCount; i++)
            entry.Value();
        watch.Stop();
        Console.WriteLine("{0,-35} {1,6} ms", entry.Key + ":", 
                          watch.ElapsedMilliseconds);
        watch.Reset();
    }
}

The result for the above benchmark is as follows. The key highlights of the result are:

  • It only takes a few milliseconds for Fasterflect to build a dynamic method. And, this needs doing only once per application for each field, method, constructor, property, or indexer. The dynamic method is reused by Fasterflect in subsequent calls.
  • When repeating the same invocation for a considerable number of times (2 million times), the performance of the Default API and Cache API is about 6-10 times and 200-400 times faster than the performance of the built-in .NET Reflection API, respectively.

Image 1

Image 2

Conclusion

Fasterflect allows you to perform reflective invocations on constructors, indexers, instance and static fields, methods, and properties. You should select among its two APIs, one optimized for simplicity and one for performance, depending on your particular needs. With this article, I hope I have provided you with enough information to decide whether this library can be of some use to you and enough examples for you to start using this library.

Happy writing reflecting code!

License

This article, along with any associated source code and files, is licensed under The Apache License, Version 2.0


Written By
Chief Technology Officer KMS Technology
Vietnam Vietnam
You can visit Buu's blog at http://www.buunguyen.net/blog to read about his thoughts on software development.

Comments and Discussions

 
GeneralExcellent Excellent. Pin
Dean Oliver27-Jan-12 2:32
Dean Oliver27-Jan-12 2:32 
Generallate bounding to COM Pin
xusan19-Jan-11 16:50
xusan19-Jan-11 16:50 
GeneralAdditional Requests... Pin
Fuehner24-Sep-09 9:06
Fuehner24-Sep-09 9:06 
GeneralRe: Additional Requests... Pin
Buu Nguyen2-Mar-10 17:21
Buu Nguyen2-Mar-10 17:21 
GeneralIdeas for improvement Pin
Chris Stefano9-Aug-09 22:54
Chris Stefano9-Aug-09 22:54 
GeneralRe: Ideas for improvement Pin
Buu Nguyen10-Aug-09 2:54
Buu Nguyen10-Aug-09 2:54 
Generalsuggestion Pin
Luc Pattyn9-Aug-09 7:05
sitebuilderLuc Pattyn9-Aug-09 7:05 
GeneralRe: suggestion Pin
Buu Nguyen9-Aug-09 20:07
Buu Nguyen9-Aug-09 20:07 
GeneralRe: suggestion Pin
Luc Pattyn9-Aug-09 22:37
sitebuilderLuc Pattyn9-Aug-09 22:37 
GeneralRe: suggestion Pin
Buu Nguyen10-Aug-09 3:08
Buu Nguyen10-Aug-09 3:08 
GeneralRe: suggestion Pin
Luc Pattyn10-Aug-09 3:26
sitebuilderLuc Pattyn10-Aug-09 3:26 
you're welcome.

Smile | :)

Luc Pattyn [Forum Guidelines] [My Articles]

The quality and detail of your question reflects on the effectiveness of the help you are likely to get.
Show formatted code inside PRE tags, and give clear symptoms when describing a problem.

GeneralLooks nice Pin
Kenan E.K.9-Aug-09 6:01
professionalKenan E.K.9-Aug-09 6:01 
GeneralRe: Looks nice Pin
Buu Nguyen9-Aug-09 20:08
Buu Nguyen9-Aug-09 20:08 
GeneralRe: Looks nice Pin
Buu Nguyen18-Aug-09 3:57
Buu Nguyen18-Aug-09 3:57 
GeneralRe: Looks nice Pin
Buu Nguyen2-Mar-10 17:26
Buu Nguyen2-Mar-10 17:26 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.