Programming  /  C# June 03, 2011

Getting property and method names using static reflection in C#

For several years I’ve had a little “utility” function that I’ve used in several projects that I use to convert property names into strings. One use case is for instance when mapping code to some data source or third party API that where the names are used as keys. The method uses “static reflection”, or rather it parses the expression tree from a lambda expression, to figure out the name of a property that the lambda expression returns the value of.

So far I haven’t really needed to do the same for methods or fields but today I decided I would try to make it a bit more generic anyway. In other words, I wanted to be able to do stuff like this:

//Should return "Length", value type property
StaticReflection.GetMemberName<string>(x => x.Length);

//Should return "Data", reference type property
StaticReflection.GetMemberName<Exception>(x => x.Data);

//Should return "Clone", method returning reference type
StaticReflection.GetMemberName<string>(x => x.Clone());

//Should return "GetHashCode", method returning value type
StaticReflection.GetMemberName<string>(x => x.GetHashCode());

//Should return "Reverse", void method
StaticReflection.GetMemberName<List<string>>(x => x.Reverse());

//Should return "LastIndexOf", method with parameter
StaticReflection.GetMemberName<string>(x => x.LastIndexOf(','));

Or, by having an overload that is an extension method achieve the same effect but utilizing type inference so we don’t have to explicitly specify the type if we have an instance of it, for instance like this:

//Should return Length, no type parameter required
"someString".GetMemberName(x => x.Length);

The solution

In order to fulfill the above requirements we need two methods, one accepting a Expression<Func<T, object>> and one, used for methods without return types, an Expression<Action<T>> as parameters. We also need the corresponding extension methods. Once we have those we need to look at the Body property of the expressions and figure out what types of expressions they are. In other words, if we’re dealing with a field, property or method as well as if the return type is a value type or reference type as the way to get the name from the expression tree differs depending on that.

My solution was to create a static class with a couple of private method that did the bulk of the work.

private static string GetMemberName(Expression expression)
{
    if (expression == null)
    {
        throw new ArgumentException(
            "The expression cannot be null.");
    }

    if (expression is MemberExpression)
    {
        // Reference type property or field
        var memberExpression = (MemberExpression) expression;
        return memberExpression.Member.Name;
    }

    if (expression is MethodCallExpression)
    {
        // Reference type method
        var methodCallExpression = 
            (MethodCallExpression) expression;
        return methodCallExpression.Method.Name;
    }

    if (expression is UnaryExpression)
    {
        // Property, field of method returning value type
        var unaryExpression = (UnaryExpression) expression;
        return GetMemberName(unaryExpression);
    }

    throw new ArgumentException("Invalid expression");
}

private static string GetMemberName(
        UnaryExpression unaryExpression)
{
    if (unaryExpression.Operand is MethodCallExpression)
    {
        var methodExpression = 
            (MethodCallExpression) unaryExpression.Operand;
        return methodExpression.Method.Name;
    }
    
    return ((MemberExpression) unaryExpression.Operand)
                .Member.Name;
}

The if statements could be replaced with an “action map” but this form allowed me to better illustrate what the methods, especially the first one, do. Anyhow, with the private methods in place the public methods are pretty straight forward. First the static helper methods:

public static string GetMemberName<T>(Expression<Func<T, object>> expression)
{
    if (expression == null)
    {
        throw new ArgumentException(
            "The expression cannot be null.");
    }

    return GetMemberName(expression.Body);
}

public static string GetMemberName<T>(
        Expression<Action<T>> expression)
{
    if (expression == null)
    {
        throw new ArgumentException(
            "The expression cannot be null.");
    }

    return GetMemberName(expression.Body);
}

And the corresponding extension methods:

public static string GetMemberName<T>(
    this T instance, Expression<Func<T, object>> expression)
{
    return GetMemberName(expression);
}

public static string GetMemberName<T>(
    this T instance, Expression<Action<T>> expression)
{
    return GetMemberName(expression);
}

Note how we’re not actually using the instance the extension methods are called on. They are only there to allow us to invoke the methods on instances of a type directly instead of specifying the type as the compiler can then infer the type parameter (T). In other words, we could in theory do something like this:

((string) null).GetMemberName(x => x.Length);

That seems a bit pointless though as we’re probably better off using the regular methods in to achieve the same result.

The full class looks like this:

public static class StaticReflection
{
    public static string GetMemberName<T>(
        this T instance, 
        Expression<Func<T, object>> expression)
    {
        return GetMemberName(expression);
    }

    public static string GetMemberName<T>(
        Expression<Func<T, object>> expression)
    {
        if (expression == null)
        {
            throw new ArgumentException(
                "The expression cannot be null.");
        }

        return GetMemberName(expression.Body);
    }

    public static string GetMemberName<T>(
        this T instance, 
        Expression<Action<T>> expression)
    {
        return GetMemberName(expression);
    }

    public static string GetMemberName<T>(
        Expression<Action<T>> expression)
    {
        if (expression == null)
        {
            throw new ArgumentException(
                "The expression cannot be null.");
        }

        return GetMemberName(expression.Body);
    }

    private static string GetMemberName(
        Expression expression)
    {
        if (expression == null)
        {
            throw new ArgumentException(
                "The expression cannot be null.");
        }

        if (expression is MemberExpression)
        {
            // Reference type property or field
            var memberExpression = 
                (MemberExpression) expression;
            return memberExpression.Member.Name;
        }

        if (expression is MethodCallExpression)
        {
            // Reference type method
            var methodCallExpression = 
                (MethodCallExpression) expression;
            return methodCallExpression.Method.Name;
        }

        if (expression is UnaryExpression)
        {
            // Property, field of method returning value type
            var unaryExpression = (UnaryExpression) expression;
            return GetMemberName(unaryExpression);
        }

        throw new ArgumentException("Invalid expression");
    }

    private static string GetMemberName(
        UnaryExpression unaryExpression)
    {
        if (unaryExpression.Operand is MethodCallExpression)
        {
            var methodExpression = 
                (MethodCallExpression) unaryExpression.Operand;
            return methodExpression.Method.Name;
        }

        return ((MemberExpression) unaryExpression.Operand)
            .Member.Name;
    }
}

For the really interested person, it passes a number of tests, or executable specifications verified and generated using MSpec, producing the following specification:

StaticReflection GetMemberName, given a Func returning a value type property
» should return the name of the property

StaticReflection GetMemberName, given a Func returning a reference type property
» should return the name of the property

StaticReflection GetMemberName, given a Func invoking a reference type method
» should return the name of the method

StaticReflection GetMemberName, given a Func invoking a value type method
» should return the name of the method

StaticReflection GetMemberName, given a Func invoking a void method
» should return the name of the method

StaticReflection GetMemberName, given a Func invoking a method with a parameter
» should return the name of the method

StaticReflection GetMemberName, called with null cast to Func
» should throw an exception
» should throw an ArgumentException

StaticReflection GetMemberName, called with null cast to Action
» should throw an exception
» should throw an ArgumentException

PS. For updates about new posts, sites I find useful and the occasional rant you can follow me on Twitter. You are also most welcome to subscribe to the RSS-feed.

Joel Abrahamsson

Joel Abrahamsson

I'm a passionate web developer and systems architect living in Stockholm, Sweden. I work as CTO for a large media site and enjoy developing with all technologies, especially .NET, Node.js, and ElasticSearch. Read more

Comments

comments powered by Disqus

More about C#