The basics behind static reflection

Sunday, June 5, 2011

Without reflection the world would be nowhere, we would know nothing about ourselves and the people living next to us. The same is true for software development. Of course this is exaggerated but it’s a little bit true. A lot of things would be needlessly complex, take to long or just not possible. I use it a lot when I have to implement dynamic behavior in my code. For example when you have to call a generic method but the type parameter is only known at runtime, reflection is the man for the job.

Of course there are downsides. When you are obtaining meta data on properties, fields and methods you will have to use strings to refer to them. This is not ideal because you will have to keep them up-to-date whenever you rename a property, field or method. Even worse, these possible errors only manifest themselves at runtime by executing the specific code.

 1: //dynamic reflection
 2: PropertyInfo property = typeof (Beer).GetProperty("Description");
 3: MethodInfo method = typeof (Beer).GetMethod("Drink");

More ideally would be to statically retrieve this meta data. This way we have a compile time check. Possible errors are directly known and refactoring is much easier. This static reflection is done by using lambda expressions. These where introduced in C# 3.0 and are essential to resolving this meta data statically.

A lambda expression is an anonymous function that can contain expressions and statements, and can be used to create delegates or expression tree types. ~ MSDN

I will not get into detail of lambda expressions so if you aren’t familiar with them, check it out now, it’s really powerful. The following snippet shows a simple lambda.

 1: Func<int, int> simpleLambda = x => x + x;
 2:  
 3: var result = simpleLambda(10);

x => x + x is the lambda, where => is the lambda operator. The left side holds the input parameter and the right side the statement block. The same lambda expression can be converted into a expression tree type: Expression<>.

 1: Expression<Func<int, int>> expressionTreeLambda = x => x + x;
 2:  
 3: var result = expressionTreeLambda.Compile()(10);

Normally if you would compile and invoke this expression you will get the value of 20 returned. But we are not interested in the value, we want the meta data. So where are not going to execute it at all. The cool thing is that the compiler can infer meta data based on the body of the lambda expression. So knowing this we have almost everything we need to start doing static reflection. We start by defining the following expression tree type.

 1: Expression<Func<T1, object>>

T1 will be the type holding the property or field of which we want the meta information. In our example this would be the Beer class. The lambda will be returning value of type System.Object. Methods will need a slightly different type definition, more on this one later. For now let us focus on retrieving the meta data for properties and fields.

 1: Expression<Func<Beer, object>> expression = b => b.Description;

For the given expression we are only interested in a so called MemberExpression. This represents a field or property. To get this member expression (if there is one) we first need to check the node type. If this is of value ExpressionType.MemberAccess, we have one and can cast the body of the expression into a MemberExpression type. If the value of the node type is ExpressionType.Convert, we have to take some additional steps. We first have to cast the body to a UnaryExpression type. This is a operation with only one parameter, the operand. We can then cast the operand of the unary expression to a MemberExpression type. All other values means that we are not dealing with a member expression. All this information results in the following code:

 1: Expression<Func<Beer, object>> expression = b => b.Description;
 2:  
 3: MemberExpression memberExpression = null;
 4: if (expression.Body.NodeType == ExpressionType.Convert)
 5: {
 6:     var body = (UnaryExpression)expression.Body;
 7:     memberExpression = body.Operand as MemberExpression;
 8: }
 9: else if (expression.Body.NodeType == ExpressionType.MemberAccess)
 10: {
 11:     memberExpression = expression.Body as MemberExpression;
 12: }
 13:  
 14: //runtime exception if not a member
 15: if (memberExpression == null)
 16:     throw new ArgumentException("Not a property or field", "expression");

The last step is the most simple one. The MemberExpression type holds a property Member which is of type MemberInfo, which is a abstract base class for providing access to member meta information. So cast this type to a PropertyInfo type and we're done. We have access to all the meta information of the property Description. We have done static reflection.

 1: var property = memberExpression.Member as PropertyInfo;

In case of a field we cast to a FieldInfo type.

 1: var field = memberExpression.Member as FieldInfo;

To get the method meta data we have to take a slightly different approach. First off al we can’t use the expression tree type definition used before. We have to define a more generic one for this. We can do this by using the Action<T1> delegate, this defines a method without parameters and without a return value. Again T1 will be the type holding the method from which we want the meta data.

 1: Expression<Action<T1>>
 2:  
 3: Expression<Action<Beer>> expression = b => b.Drink();
 4: Expression<Action<Beer>> expression2 = b => b.AddIngredient(new object());

In this case we are interested in a MethodCallExpression. This represents a call to either a static or an instance method. Again we need to check the node type of the body from the expression. If this is of value ExpressionType.Call, we can safely cast the body of the expression to a MethodCallExpression type. This type holds a property Method, which contains the MethodInfo type. Again we have done static reflection.

 1: Expression<Action<Beer>> expression = b => b.Drink();
 2:  
 3: if (expression.Body.NodeType == ExpressionType.Call)
 4: {
 5:     var methodCallExpression =  (MethodCallExpression) expression.Body;
 6:  
 7:     var method = methodCallExpression.Method;
 8: }

To bring this all together you can create some generic helper class. Because of the fact that all meta data derives from MemberInfo it becomes possible to combine the handling of the meta data (fields, properties and methods) together.

 1: public static MemberInfo GetMemberInfo(LambdaExpression lambda)
 2: {
 3:     if (lambda == null)
 4:         throw new ArgumentNullException("lambda");
 5:                         
 6:     MemberExpression memberExpression = null;
 7:     if (lambda.Body.NodeType == ExpressionType.Convert)
 8:     {
 9:         memberExpression = ((UnaryExpression)lambda.Body).Operand as MemberExpression;
 10:     }
 11:     else if (lambda.Body.NodeType == ExpressionType.MemberAccess)
 12:     {
 13:         memberExpression = lambda.Body as MemberExpression;
 14:     }
 15:     else if (lambda.Body.NodeType == ExpressionType.Call)
 16:     {
 17:         //immediately return the MethodInfo
 18:         return ((MethodCallExpression)lambda.Body).Method;
 19:     }
 20:  
 21:     if (memberExpression == null)
 22:         throw new ArgumentException("Not a member", "lambda");
 23:             
 24:     return memberExpression.Member;
 25: }

You will only need a different method for each type of meta data that will cast the MemberInfo to the right meta data type.

 1: public static PropertyInfo GetProperty<T1>(Expression<Func<T1, object>> expression)
 2: {
 3:    return GetMemberInfo(expression) as PropertyInfo;            
 4: }
 5:  
 6: public static FieldInfo GetField<T1>(Expression<Func<T1, object>> expression)
 7: {
 8:     return GetMemberInfo(expression) as FieldInfo;
 9: }
 10:  
 11: public static MethodInfo GetMethod<T1>(Expression<Action<T1>> expression)
 12: {
 13:     return GetMemberInfo(expression) as MethodInfo;          
 14: }

The example above is probably not the best implementation but does the job for explaining it all. There are a lot of implementations found on the net. I personally like the one from William E. Kempf (http://www.digitaltapestry.net/blog/reflecting-on-code).

Tags: ,
Filed Under: Programming, Technology
Comments are closed