Static Reflection in .NET

LINQ expressions have proven to be extremely versatile, popping up in all sorts of areas. “Static Reflection” seem to be the latest hype. But what is static reflection anyway, and why is it good or why is it bad?

Reflection is used to obtain information about the code you are executing, and to use that information to interact with the code dynamically. Sometimes reflection is used to interact dynamically with code that is statically known by a program already. For example, data binding heavily relies on reflection to dynamically read and write properties. The calling program knows about those properties statically, but the data binding libraries do not. In data binding, object properties are often identified by their name, expressed as a string. That string is then used by the libraries to construct a PropertyInfo object.

Time for an example. Given this class:

public class Example
{
    public string Description { get; set; }
}


You can obtain a PropertyInfo object describing the Description property as follows:

PropertyInfo pi = typeof(Example).GetProperty("Description");


We may have an issue here. If I make a typing mistake in the GetProperty call, I don’t get a compiler error. At runtime, the call will return null, probably leading to a NullReferenceException down the road. And of course, Visual Studio Intellisense will not help me to type it right. Also, if I rename the property, for example to “Summary”, the GetProperty call will be broken, without a compile-time error. Static Reflection is one technique to avoid these issues.

Using LINQ expressions, we could create an API that allows us to do something like the following:

PropertyInfo pi = StaticReflector.GetProperty(Example e => e.Description);


The downside of this approach is that it doesn’t work with anonymous types. So I propose a different mechanism. What we need is something that statically gives us access to a type. Any generic interface will do. I propose the following:

public interface IStaticReflector<T>
{
}


Given this interface, we can define a series of extension methods, for example:

public static class StaticReflectorExtensions
{
    public static PropertyInfo PropertyInfo<T, U>(this IStaticReflector<T> obj, Expression<Func<T, U>> selector)
    {
        var body = selector.Body as MemberExpression;
        return body.Member as PropertyInfo;
    }
}


Notice how the obj parameter is not really used in the PropertyInfo method. It does serve a purpose however: it allows us to use type inference on the type T, and I get full Intellisense. For example:

IStaticReflector<Example> reflector = null;
PropertyInfo pi = reflector.PropertyInfo(e => e.Description);


Granted, initializing a variable to null and then calling a method on it is a bit weird. We need a more elegant way to create these things:

public static class StaticReflector
{
    public static IStaticReflector<T> Create<T>()
    {
        return null;
    }
}


Now we can write:

PropertyInfo pi = StaticReflector.Create<Example>().PropertyInfo(e => e.Description);


This still doesn’t work on anonymous types though. For those, we could use the following:

public static class ObjectExtensions
{
    public static IStaticReflector<T> GetReflector<T>(this T obj)
    {
        return null;
    }
}


Now we can write things such as:

var anonymous = new { Description = "Example" };

PropertyInfo pi = anonymous.GetReflector().PropertyInfo(e => e.Description);


I do prefer the StaticReflector.Create<T>() method is case the type name is known though.

Are we done? Not really. Let’s go back to dynamic reflection using string names. Lot’s of things could go wrong there, and we don’t get any warnings. The situation has not gone worse, but still lot’s of things can go wrong. So the PropertyInfo method needs some parameter validation. Also, properties certainly aren’t the only thing we can reflect upon. What about fields, methods and constructors? Here’s a full implementation:

using System;
using System.Linq.Expressions;
using System.Reflection;

public static class StaticReflectorExtensions
{
    public static PropertyInfo PropertyInfo<T, U>(this IStaticReflector<T> obj, Expression<Func<T, U>> selector)
    {
        if (selector == null)
        {
            throw new ArgumentNullException(Strings.Selector);
        }

        PropertyInfo pi = obj.MemberInfo(selector) as PropertyInfo;

        if (pi == null)
        {
            throw new ArgumentException(Strings.InvalidPropertySelector, Strings.Selector);
        }

        return pi;
    }

    public static FieldInfo FieldInfo<T, U>(this IStaticReflector<T> obj, Expression<Func<T, U>> selector)
    {
        if (selector == null)
        {
            throw new ArgumentNullException(Strings.Selector);
        }

        FieldInfo fi = obj.MemberInfo(selector) as FieldInfo;

        if (fi == null)
        {
            throw new ArgumentException(Strings.InvalidFieldSelector, Strings.Selector);
        }

        return fi;
    }

    public static MemberInfo MemberInfo<T, U>(this IStaticReflector<T> obj, Expression<Func<T, U>> selector)
    {
        if (selector == null)
        {
            throw new ArgumentNullException(Strings.Selector);
        }

        var body = selector.Body as MemberExpression;

        if (body == null)
        {
            throw new ArgumentException(Strings.InvalidMemberSelector, Strings.Selector);
        }

        if (body.Expression.NodeType != ExpressionType.Parameter)
        {
            throw new ArgumentException(Strings.InvalidMemberSelector, Strings.Selector);
        }

        return body.Member;
    }

    public static MethodInfo MethodInfo<T, U>(this IStaticReflector<T> obj, Expression<Func<T, U>> selector)
    {
        if (selector == null)
        {
            throw new ArgumentNullException(Strings.Selector);
        }

        var body = selector.Body as MethodCallExpression;

        if (body == null)
        {
            throw new ArgumentException(Strings.InvalidMethodSelector, Strings.Selector);
        }

        // instance methods must be called on the parameter
        if (body.Object != null && body.Object.NodeType != ExpressionType.Parameter)
        {
            throw new ArgumentException(Strings.InvalidMethodSelector, Strings.Selector);
        }

        // static methods must be defined in the type of the parameter or a base type
        if (body.Object == null && !body.Method.DeclaringType.IsAssignableFrom(typeof(T)))
        {
            throw new ArgumentException(Strings.InvalidMethodSelector, Strings.Selector);
        }

        return body.Method;
    }

    public static MethodInfo MethodInfo<T>(this IStaticReflector<T> obj, Expression<Action<T>> selector)
    {
        if (selector == null)
        {
            throw new ArgumentNullException(Strings.Selector);
        }

        var body = selector.Body as MethodCallExpression;

        if (body == null)
        {
            throw new ArgumentException(Strings.InvalidMethodSelector, Strings.Selector);
        }

        // instance methods must be called on the parameter
        if (body.Object != null && body.Object.NodeType != ExpressionType.Parameter)
        {
            throw new ArgumentException(Strings.InvalidMethodSelector, Strings.Selector);
        }

        // static methods must be defined in the type of the parameter or a base type
        if (body.Object == null && !body.Method.DeclaringType.IsAssignableFrom(typeof(T)))
        {
            throw new ArgumentException(Strings.InvalidMethodSelector, Strings.Selector);
        }

        return body.Method;
    }

    public static ConstructorInfo ConstructorInfo<T>(this IStaticReflector<T> obj, Expression<Func<T>> selector)
    {
        if (selector == null)
        {
            throw new ArgumentNullException(Strings.Selector);
        }

        var body = selector.Body as NewExpression;

        if (body == null)
        {
            throw new ArgumentException(Strings.InvalidConstructorSelector, Strings.Selector);
        }

        return body.Constructor;
    }

    private static class Strings
    {
        internal const string InvalidFieldSelector = "Invalid field selector";
        internal const string InvalidPropertySelector = "Invalid property selector";
        internal const string InvalidMemberSelector = "Invalid member selector";
        internal const string InvalidMethodSelector = "Invalid method selector";
        internal const string InvalidConstructorSelector = "Invalid constructor selector";
        internal const string Selector = "selector";
    }
}


Next time, we’ll talk about the disadvantages of this approach, and we’ll look at an alternative.