Note: phiên bản Tiếng Việt của bài này ở link dưới.

https://duongnt.com/fastmember-vie

For the most part, C# is a static language, which means its types and variable names are known at compile time. This helps reduce type errors and enables the compiler to perform more aggressive optimizations. But it also means that C# is more restrictive than other dynamic languages such as Python or JavaScript. Traditionally, we need to use the Reflection API to access an object’s members if we don’t know their types and/or names. However, the Reflection API is noticeably slower than static C# code, which makes it not suitable for critical parts of our applications.

Today, we will take a look at the FastMember library and see how it can help us speed up access to members of an object whose names are only known at runtime. Also, we will look at its limitations and then run some benchmark tests to see how it compares to the Reflection API and to static C#.

How to use FastMember

First thing first, let’s introduce our test class, which only has a public property.

public class Account
{
    public string Name { get; set; }
}
var account = new Account();

FastMember provides two different ways to access members of an object. We can choose to create an accessor for a particular Account object then use it to access Name.

var objectAccessor = ObjectAccessor.Create(account);
var name = objectAccessor["Name"] as string; // We need to manually cast return value from object type to the correct type
objectAccessor["Name"] = "New name" // Set a new value for "Name" property

If we want to access members of multiple objects of the same type then we can create just one accessor for that type and reuse it.

var typeAccessor = TypeAccessor.Create(typeof(Account));
var name = typeAccessor[account, "Name"] as string; // Don't forget the cast
objectAccessor[account, "Name"] = "New name" // Set a new value for "Name" property

Now let’s add a new property with a private setter.

public class Account
{
    private int _balance;

    public int Balance
    {
        private set => _balance = value;

        get => _balance;
    }

    public string Name { get; set; }
}

This is what happens if we try to access Balance.

var balance = typeAccessor[account, "Balance"] as int; // OK, returns 0
typeAccessor[account, "Balance"] = 2_000 // Throws an exception!

To update the value of Balance, we need to create a typeAccessor with the allowNonPublicAccessors flag set to true.

var typeAccessor = TypeAccessor.Create(typeof(Account), true);
var balance = typeAccessor[account, "Balance"] as int; // OK, returns 0
typeAccessor[account, "Balance"] = 2_000 // OK, Balance is now 2,000

We can also do the same thing with objectAccessor, just initialize it with allowNonPublicAccessors set to true.

var objectAccessor = ObjectAccessor.Create(account, true);

Limitation of FastMember

While slow, the Reflection API is very flexible and is capable of access fields, properties, methods,… of an object. Moreover, it’s possible to access non-public members of a type, as long as we set the correct binding flags. On the other hand, FastMember can only access public fields and properties of an object. What about non-public fields/properties then? One might think that by setting allowNonPublicAccessors to true, we can use FastMember to access such members. Unfortunately, a simple test reveals the opposite.

public class Account
{
    private string _password;
    protected string Address { get;set; }
    public string Name { get; set; }
}

var typeAccessor = TypeAccessor.Create(typeof(Account), true);
var password = typeAccessor[account, "_password"] as string; // Throws an exception!
var address = typeAccessor[account, "Address"] as string; // Also throws an exception!

This exception also happens when we use ObjectAccesor and the cause is the same. We will look at the source code of TypeAccessor and try to find out why. From the source code here, we can see that TypeAccessor maintains a hash table of accessors for each type it has encountered. When we create an accessor for a new type, it calls the CreateNew method and passes in the type and the allowNonPublicAccessors flag. Notice that TypeAccessor calls the static method defined here instead of this virtual method. Within CreateNew, we can find these two lines.

PropertyInfo[] props = type.GetTypeAndInterfaceProperties(BindingFlags.Public | BindingFlags.Instance);
FieldInfo[] fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance);

From these BindingFlags, we can see that TypeAccessor can only access public instance fields or properties. In other words, all non-public members are off limits, and even public static members cannot be accessed by TypeAccessor. Now let’s find out how allowNonPublicAccessors flag is used by looking at this line and this line.

else if (member is PropertyInfo prop)
{
    // bunch of code

    var accessor = (isGet | isByRef) ? prop.GetGetMethod(allowNonPublicAccessors) : prop.GetSetMethod(allowNonPublicAccessors)

    // bunch of code
};

When the member we are trying to access is a property, TypeAccessor will call GetGetMethod or GetSetMethod and pass the allowNonPublicAccessors flag to retrieve the corresponding getter/setter. Based on the value of allowNonPublicAccessors, non-public getter/setter might or might not be retrieved. This explains why in the How to use FastMember section, we need to call TypeAccessor.Create with the allowNonPublicAccessors flag set to true before accessing the Balance property.

Having said all that, I honestly don’t mind FastMember‘s limitation too much. After all, a field or property is non-public for a reason, and trying to get around that restriction often leads to more troubles later. The exception to that rule is in test code; sometimes accessing a non-public member is the cleanest way to verify our code. However, in that situation the Reflection API might be a more suitable choice due to its flexibility, while its slower speed matters less.

Benchmark result

As I mentioned earlier, FastMember is faster than the Reflection API. At the same time, we can’t expect it to match the speed of static C#. So exactly how fast is FastMember? I wrote a small benchmark project to find the answer, you can find it at the following link. This project used the BenchmarkDotNet library, which is a very powerful profiling tool.

https://github.com/duongntbk/FastMemberBenchmark

Read a public property

Method Mean Error StdDev Median Gen 0 Gen 1 Gen 2 Allocated
FastMember_TypeAccessor_PublicGet 41.2149 ns 0.9083 ns 1.5669 ns 40.9240 ns
FastMember_ObjectAccessor_PublicGet 44.3860 ns 0.9716 ns 0.9088 ns 44.3685 ns
Static_PublicGet 0.0045 ns 0.0163 ns 0.0249 ns 0.0000 ns
Reflection_PublicGet 116.7330 ns 1.6150 ns 1.4317 ns 116.8871 ns

Update a public property

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
FastMember_TypeAccessor_PublicSet 42.953 ns 0.9328 ns 1.8193 ns
FastMember_ObjectAccessor_PublicSet 46.171 ns 0.9169 ns 1.3440 ns
Static_PublicSet 1.939 ns 0.1290 ns 0.3640 ns
Reflection_PublicSet 202.760 ns 4.0677 ns 6.9073 ns 0.0100 64 B

Read a non-public property

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
FastMember_TypeAccessor_PrivateGet 42.61 ns 1.014 ns 0.996 ns
FastMember_ObjectAccessor_PrivateGet 43.73 ns 1.032 ns 1.724 ns
Reflection_PrivateGet 120.02 ns 2.462 ns 2.418 ns

Update a non-public property

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
FastMember_TypeAccessor_PrivateSet 46.66 ns 0.924 ns 1.802 ns 0.0038 24 B
FastMember_ObjectAccessor_PrivateSet 48.02 ns 0.991 ns 1.289 ns 0.0038 24 B
Reflection_PrivateSet 237.63 ns 4.705 ns 4.401 ns 0.0138 88 B

From all these tests, we can see that FastMember is 3 ~ 5 times faster than the Reflection API. And when updating a property, FastMember also uses less memory. Additionally, TypeAccessor is slightly more efficient than ObjectAccessor, but the difference is quite small. Let’s compare the initialization of those two accessors as well.

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
TypeAccessor_Create_DisallowNonPublic 23.47 ns 0.514 ns 0.859 ns
TypeAccessor_Create_AllowNonPublic 23.51 ns 0.463 ns 0.387 ns
ObjectAccessor_Create_DisallowNonPublic 47.87 ns 0.938 ns 1.488 ns 0.0051 32 B
ObjectAccessor_Create_AllowNonPublic 43.05 ns 0.905 ns 0.802 ns 0.0051 32 B

Again, creating a TypeAccessor consumes less resources and is faster than creating an ObjectAccessor, but I doubt we will ever create enough accessors in actual code to notice the difference.

Conclusion

While I prefer to write static code when possible, sometimes accessing a member using just its name can significantly simplify the logic. In such cases, I’ve always found FastMember to be helpful, and I’ve used it in both production and test code.

A software developer from Vietnam and is currently living in Japan.

One Thought on “Use FastMember to access public members at runtime”

Leave a Reply