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.
One Thought on “Use FastMember to access public members at runtime”