An introduction to Static Reflection

by OSgAgA 1. November 2012 16:18

One of the many annoyances in modern day development is that there are still some situations where
you are forced by libraries to use "magic" strings. One example for that is the INotifyPropertyChanged interface.

A typical implementation of the INotifyPropertyChanged Interface.

Lets have a look at a typical implementation of this interface.

public class Test : INotifyPropertyChanged
{
    int myProperty;

    public int MyProperty 
    {
        get
        {
            return this.myProperty;
        }
            
        set
        {
            this.myProperty = value;
            this.NotifyPropertyChanged("MyProperty");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged(String info)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }
}

In this example inside the MyProperty setter the NotifyPropertyChanged method is called using the name of the property as a hardcoded string. This can easily be a problem when the name of the property changed and you forget to change the string accordingly. In this situation the code will compile fine and even an event will be raised, but the UI may not react as you would expect.

What we need is a type safe way to get the name of the property correctly at compile time. That's where static reflection comes into play.

[When using .NET 4.5 you should use the new CallerMemberName Attribute to handle this problem which presents a better solution than the one using StaticReflection. But StaticReflection can be used in other situations as well, which can be seen later in this post. ]

Say hello to StaticReflection

Let's first have a look at what we want to achieve:

set
{
    this.myProperty = value;
    this.NotifyPropertyChanged(GetMemberName<Test>(p => p.MyProperty));
}

As you can see here, we are using a method GetMemberName getting a lambda expression as a parameter. If the name of the property changes, this code will not compile anymore until you change the property name in the lambda expression as well. Even better, if you refactor the property name, the lambda expression will be refactored accordingly.

But how can we implement the GetMemberName method, so that this neat little trick will work? Well lets have a look at a simple implementation:

public string GetMemberName<T>(Expression<Func<T, object>> expr)
{
    if (expr.Body is MemberExpression)
    {
        return (expr.Body as MemberExpression).Member.Name;
    }

    return String.Empty;
}

The interesting thing to notice here ist the type of the expr argument. It is not Func as you may expect but it is an Expression of this type. As a result, the compiler will not execute the Linq-expression. It will just compile the Linq-Statement to an expression and give this expression to the method. So even if access to the property would lead to an exception, this code will still work, as the property is never really accessed.

This will get clearer when we will have deeper look at the expression object. This will look like the following (simplified):

Expression Overview

As we are not interested in things like the paramters that are given to to the function, it is sufficient to have a look at the body. In the case of the previous example (p => p.MyProperty) we are accessing a member inside the body. This is indicated via the type MemberExpression, with which we can access the name of the member.

Getting the name of a method is straight forward. If we have the following expression p => p.MyMethod(), the body of the expression is a MethodCallExpression and the name of the method can be accessed via body.Method.Name.

This approach will not work, when boxing is applied. That means if you access an integer field with this method via p => p.AnIntegerField, you will notice that the type of the body is UnaryExpression instead of MemberExpression. This is because of a boxing taking place. In this case you can access the member name via (body.Operand as UnaryExpression).Member.Name. The same applies for method access.

Here you can see the full code for accessing the data of the expression body:

private static string GetMemberName(Expression body)
{
    if (body is MemberExpression)
    {
        // Reference Type Property or Field.
        return (body as MemberExpression).Member.Name;
    }
    else if (body is UnaryExpression)
    {
        var operand = (body as UnaryExpression).Operand;
        if (operand is MemberExpression)
        {
            // Value Type Property or field.
            return (operand as MemberExpression).Member.Name;
        }
        else if (operand is MethodCallExpression)
        {
            // Method with value type return value.
            return (operand as MethodCallExpression).Method.Name;
        }
    }
    else if (body is MethodCallExpression)
    {
        // Method with reference type return value.
        return (expr as MethodCallExpression).Method.Name;
    }
    else if (body is ParameterExpression)
    {
        return (body as ParameterExpression).Type.Name;
    }

    throw new ArgumentException("expr", "Unhandled expression type. Only the following types are supported: MemberExpression, UnaryExpression, MethodCallExpression, ParameterExpression.");
}

Getting Parameter names

It is even possible to get the parameter names of a method. So lets say we have a method with the following signature: public int CallMe(int someNumber). If we want to get the name of the first parameter of the method we call GetParamName(t => t.CallMe(42), 0) using the following method:

private static string GetParamName<T>(Expression expr, int index)
{
    string methodName = GetMemberName(expr.body);
    MethodInfo method = typeof(T).GetMethod(methodName);

    if (method.GetParameters().Length > index)
    {
        return method.GetParameters()[index].Name;
    }

    throw new ArgumentOutOfRangeException("index");
}

This comes in handy, when throwing ArgumentExceptions. So you are able to do the following:

private void Test(string param)
{
    if (param == null) 
    {
        throw new ArgumentNullException(GetParamName<Test>( t => t.Test(null), 0));
    }
}

Again this solution is able to survive refactorings, so if you change the name of the first parameter it will be directly reflected in the ArgumentNullException. The interesting thing to notice in this example is, that we are calling the method with an illegal parameter (null), which would lead to an exception. This works as the method is never really called, only the expression of the method call is evaluated to get the name of the parameter.

StaticReflection versus CallerMemberName

With .NET 4.5 comes the new CallerMemberName Attribute which can be used to get the Name of a calling property or method. Details about it can be found here. This Attribute should be used wherever possible.

But that does not mean, that there is no room for StaticReflection! The CallerMemberName Attribute only gives you the Name of the caller, you cannot access other properties of a class with it. So you cannot get the Parameter names as we have used it in our ArgumentNullException, or if you want to access the Name of a property or method that is not the caller of the context StaticReflection can help you with that.

Download code

You can find the complete code together with some convenience functions here:

StaticReflection.zip (78.43 kb) [Downloads: 335]

The following table gives an overview of using the library on a class called myClass

Usage of StaticReflection library using static methods
Task Code
Get name of property StaticReflectionHelper.GetMemberName<MyClass>(c => c.MyProperty)
Get name of field StaticReflectionHelper.GetMemberName<MyClass>(c => c.MyField)
Get name of method with one parameter StaticReflectionHelper.GetMemberName<MyClass>(c => c.MyMethod(null))
Get name of first parameter of method StaticReflectionHelper.GetParamName<MyClass>(c => c.MyMethod(null), 0)
Get name of class StaticReflectionHelper.GetMemberName<MyClass>(c => c)

If you allready have an object of type MyClass (lets call it myClass) then you can use extension methods as follows:

Usage of StaticReflection library using extension methods
Task Code
Get name of property myClass.GetMemberName(c => c.MyProperty)
Get name of field myClass.GetMemberName(c => c.MyField)
Get name of method with one parameter myClass.GetMemberName(c => c.MyMethod(null))
Get name of first parameter of method myClass.GetParamName(c => c.MyMethod(null), 0)
Get name of class myClass.GetMemberName(c => c)

Tags: , , ,

Comments (1) -

OSgAgA Germany
11/6/2012 9:49:25 PM #

Edit: Added information about CallerMemberName attribute.

Reply

Add comment




biuquote
  • Comment
  • Preview
Loading


Month List