In this post I will show how you can use Linq Expressions to access private(or public) class fields instead of using Reflection.FieldInfo.
Normally when you want to access a field by its name on a type you have to resort to reflection.
You will end up with something like this:
FieldInfo field = MyType.GetField("someField",BindingFlags.NonPublic | .... ); object res = field.GetValue (MyObj);
This suffers from allot of drawbacks, it’s ugly, it’s untyped and its horribly slow to access data via FieldInfo.
Instead of playing around with FieldInfo, you can now use Linq Expressions to build a typed delegate that does this for you.
Here is how you do it:
public static Func<T,R> GetFieldAccessor<T,R>(string fieldName) { ParameterExpression param = Expression.Parameter (typeof(T),"arg"); MemberExpression member = Expression.Field(param, fieldName); LambdaExpression lambda = Expression.Lambda(typeof(Func<T,R>), member, param); Func<T,R> compiled = (Func<T,R>)lambda.Compile(); return compiled; }
So what does this method do?
The first line in it will create a parameter expression, that is an argument that will be passed into our delegate eventually.
The next line creates a member expression, that is the code that access the field we want to use.
If you look at the code, you will see that we create an expression that access fields (Expression.Field) and that the first arg is the parameter we created before and the 2nd arg is the name of the field we want to access.
So what it does is that it will access the named field on the object that we pass as an argument to our delegate.
The 3rd line is where we create our lambda expression.
The lambda expression is the template for our delegate, we describe what we want to pass into it and what we want to get out of it.
The last part of the code will compile our lambda into a typed delegate: Func<T,R>, where T is the type of the argument we want to pass it and R is the type of the result we want to get out of it.
Here is an example on how to use this code:
Person person = new Person(); person.FirstName = "Roger"; var getFirstName = GetFieldAccessor<Person,string> ("firstName"); ... //typed access via delegate string result = getFirstName(person);
Also do note that the “firstName” arg is the name of the field we want to access, not the property that I access in the sample.
This approach is also much faster than Reflection.FieldInfo.
I made a little benchmark where I accessed the same field a million times.
The Reflection.FieldInfo approach took 6.2 seconds to complete.
The Compiled lambda approach took 0.013 seconds to complete.
That’s quit a big difference.
So with this approach you can optimize your old reflection code and get some serious performance gains..
Well, that’s all for now.
Enjoy.
//Roger
See also:
Very nice work. As far as I understand this approach is that much faster since the expression is compiled, am I right? So I’d like to see benchmarks where the GetFieldAccessor-method is called for each iteration, this would indicate how it peforms when the resulting function is called only once. Seems to me that the lambda approach should be slower in that case, but I’d be glad if I were wrong.
Yes the compilation phase is quite slow, much slower than the reflection approach.
So if you intend to read a single field once, then reflection will be better.
But for any scenario where you are likely to access the same field on different objects, then the lambda approach is much better.
In such case you will most likely have to cache the field accessor in a dictionary or some other lookup structure.
This is really cool. I’m currently using reflection to get ALL the private fields in a class and then loop over them. Do you know if there is a similar approach that I could use with Linq that would provide the same speed increase?
@Rob
Well I guess you could simply use reflection to find the field names, and then use those names to create your fieldAccessors and store the fieldaccessors in a dictionary.
that way, you would only need to fetch the field names once per type and then you can use the fieldAccessors every time you need to read/write data.
(ok I didnt provide a sample on how to write data, but thats doable in the same way, you just need to alter the lambda generator slightly)
> ok I didnt provide a sample on how to write data, but thats doable in the same way
Is it? I didn’t think it was; I’d love to see a sample of this?
Also: if the main objective is speed (rather than “private”), then another viable alternative [property based] is HyperDescriptor; fundamentally the same concept, but uses Reflection.Emit [it predates 3.5]; but has the advantage that it can “set”, and doesn’t need any special coding (in fact, the caller doesn’t even know you’ve switched to a faster implementation, as long as they are using PropertyDescriptor to start with):
http://www.codeproject.com/KB/cs/HyperPropertyDescriptor.aspx
>>Is it? I didn’t think it was; I’d love to see a sample of this?
I think I might have been a bit too fast on that conclusion.
Calling set properties is possible since you can make method calls and setters are simply a void method in IL.
But apparently its not possible to assign field values through expressions.
So my bad on that one. :-/
I did know that you cant use assignments in expressions in normal code, so I should have figured out that its not possible when building expressions either…
*equips donkey ears*
I was skeptical of your testing, so I tried it myself. I measured 0.1 seconds for Lambda and 5.3 seconds for reflection.
However, I need to assign both fields and properties inside an automated data class.
If you ever determine how to do the assignments I would like to know. My data class code is fast but if I ever have very large data sets, the reflection time could begin to show in my performance.
Hi Roger,
I would like to know how to call an Expression on a ParameterExpression. I generated a GroupBy Expression thru this code,
>>
public IQueryable GenerateGroupByExpression(string groupByName) {
ParameterExpression paramExpression = Expression.Parameter(SourceType, SourceType.Name);
MemberExpression memExp = Expression.PropertyOrField(paramExpression, groupByName);
LambdaExpression lambda = Expression.Lambda(memExp, paramExpression);
//.GroupBy()
var groupedSource = this.Source.Provider.CreateQuery(Expression.Call(
typeof(Queryable),
“GroupBy”,
new Type[] {
SourceType,
SourceType.GetProperty(groupByName).PropertyType
},
Source.Expression,
lambda
)
);
//.Select()
ParameterExpression groupparamExpression = Expression.Parameter(groupedSource.ElementType, “g”);
List bindings = new List();
bindings.Add(Expression.Bind(typeof(GroupContext).GetMember(“Key”)[0], Expression.PropertyOrField(groupparamExpression, “Key”)));
bindings.Add(Expression.Bind(typeof(GroupContext).GetMember(“Count”)[0], Expression.Call(typeof(Queryable),
“Count”,
new Type[] { groupedSource.ElementType },
new Expression[] { groupedSource.Expression })));
bindings.Add(Expression.Bind(typeof(GroupContext).GetMember(“Details”)[0], groupparamExpression));
Expression e = Expression.MemberInit(Expression.New(typeof(GroupContext)), bindings.ToArray());
//g=>prop.Propertyname
LambdaExpression selectLambda = Expression.Lambda(e, groupparamExpression);
return groupedSource.Provider.CreateQuery(Expression.Call(
typeof(Queryable),
“Select”,
new Type[] { groupedSource.ElementType, typeof(GroupContext) },
new Expression[] { groupedSource.Expression, selectLambda }
));
}
>>
I have a GroupContext class with Key, Count and Details as parameters in it. Let me know your thoughts on this Dynamic query.
-Fahad
Roger,
I’ve created an article that builds on the ideas presented in this blog entry: http://www.codeproject.com/KB/recipes/LinqExpressionAccessors.aspx
I solved the issues of not being able to *set* a private member’s value, and also cases where you need to set a value type’s (struct) private member. Since you have to pass the value-type instance as a parameter (which then becomes a copy itself), you have to create a lambda which takes the ‘this’ by-ref. Since you can’t do that out of the box with the Func delegate, I created a specialized delegate for such accessor cases.
Thanks!