Static Reflection in .NET, part 2

A few weeks ago, I talked about static reflection and its advantages. You’ll remember that the main advantages, compared to the normal reflection API’s, are the compile time checking of parameters and IntelliSense support.

How does it compare at other levels, performance for example? Before we dive into that question, let me state that performance may or may not be important to you. A program that is fast enough is, well, fast enough. It’s unlikely that a (single) reflection call will have a significant impact on, say, the response time of a graphical user interface, and so performance doesn’t matter. If you’re algorithm requires millions of reflection operations, I’m sure you can rewrite it somehow to reduce that number significantly, and then performance again probably doesn’t matter anymore. That being said, we still want to know, right?

First of all, let’s compare code.

Take this line (using the Example class from the last post):

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

This line compiles to the following IL (simplified for readability):

ldtoken Example 
call class Type Type::GetTypeFromHandle(valuetype RuntimeTypeHandle) 
ldstr "Description" 
call instance class PropertyInfo Type::GetProperty(string)

Compare that to the following line:

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

Which compiles to:

call class IStaticReflector`1<!!0> StaticReflector::Create<class Example>()
ldtoken Example
call class Type Type::GetTypeFromHandle(valuetype RuntimeTypeHandle)
ldstr "e"
call class ParameterExpression Expression::Parameter(class Type, string)
stloc.0 
ldloc.0 
ldtoken instance string Example::get_Description()
call class MethodBase MethodBase::GetMethodFromHandle(valuetype RuntimeMethodHandle)
castclass MethodInfo
call class MemberExpression Expression::Property(class Expression, class MethodInfo)
ldc.i4.1 
newarr ParameterExpression
stloc.1 
ldloc.1 
ldc.i4.0 
ldloc.0 
stelem.ref 
ldloc.1 
call class Expression`1<!!0> Expression::Lambda<class System.Func`2<class Example, string>>(class Expression, class ParameterExpression[])
call class PropertyInfo StaticReflectorExtensions::PropertyInfo<class Example, string>(class IStaticReflector`1<!!0>, class Expression`1<class System.Func`2<!!0, !!1>>)

As you can see, this code doesn’t load the “Description” string, it uses the ldtoken instruction instead. Some bloggers have suggested that this would make it more efficient. Unfortunately, even if the ldtoken instruction is efficient, it is largely offset by the construction of the lambda expression. I ran a little benchmark, in which I compare execution time (in ticks) and memory usage (in generation 0 garbage collection runs) of both approaches, executing each one a million times. This is the result (on my laptop):

Using Reflection       Time:    1089308 Collections:    45
Using StaticReflection Time:   13513777 Collections:   264

As you can see, the Static Reflection approach is about 13.5 times slower than the good old dynamic reflection, and it uses a lot more memory. That should be no surprise either: both cases allocate a PropertyInfo object, but the static case also allocates the expression, which is nothing but food for the garbage collector.

So, one approach seems good at compile time, and the other is good at run time. It seems we’re stuck between a rock and a hard place. But the situation isn’t so bad: we have two options to choose from, each with their pro’s and con’s. What the best one is depends on your requirements, and what you value the most: compile time checking (which may result in productivity and maintainability benefits), or performance.

And who knows, maybe there is a third option, giving the best of both worlds. But that’s for next time.


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.


AssemblyInfo updated

AssemblyInfo, my Reflector plug-in, has been updated.

The new version lists all native and P/Invoke imports, and native exports. Access these features from the context menu of any module (native or managed). And there are a few minor bug fixes in the decompilation of generic members.

Get it from my download page.

Technorati tags: ,

AssemblyInfo and Paint.NET Effects Updated

There's a bug fix for AssemblyInfo, my .NET Reflector language plug-in. The type name of generic types now renders correctly.

There's also a new version of the Paint.NET effects I released only two days ago. I got quite some feedback from the Paint.NET user community. I've split the framework from the demo's, and I split the demo's into 6 separate dll's. That makes it easier for Paint.NET users to choose which effects they want to install. And the effects now show up in the standard localized menu's. Last but not least, there's a small but important optimization in the Drop Shadow effect.

Both can be downloaded from my download page.


Free tool: AssemblyInfo

Have you ever come across an assembly file you wanted to know as much as possible about, without running it? Have you ever had problems with deployment, because you weren't sure what version of a DLL you copied on a machine? How do you tell the difference between a 32 bit and 64 bit assembly anyway?

I created a little command line tool, which dumps a lot of information about an assembly. For example, here is the output of the tool when run against itself:

AssemblyInfo (c) 2007 Kris Vandermotten 
File: 
 AssemblyInfo.exe 
COFF Header: 
 File is executable (EXE). 
 File is 32 bit. 
 Target machine:     I386 
PE Header: 
 File is a 32 bit PE file. 
 Linker Version:     8.0 
 Image Base Address: 0x00400000 
 Section Alignment:  8192 
 File Alignment:     4096 
 OS Version:         4.0 
 Image Version:      0.0 
 Subsystem Version:  4.0 
 Subsystem:          WINDOWS_CUI 
Version information: 
 File Version:       1.0.0.0 
 Product Version:    1.0.0.0 
 Language: Language Neutral, Codepage 1200 
  OriginalFilename:  AssemblyInfo.exe 
  FileDescription: 
  AssemblyInfo 
  FileVersion:       1.0.0.0 
  InternalName:      AssemblyInfo.exe 
  CompanyName:       Kris Vandermotten 
  LegalCopyright:    Copyright (c) 2007 Kris Vandermotten 
  LegalTrademarks: 
  ProductName:       AssemblyInfo 
  ProductVersion:    1.0.0.0 
  PrivateBuild: 
  SpecialBuild: 
  Comments: 
Assembly: 
 Name:               AssemblyInfo, Version=1.0.0.0, Culture=neutral, PublicKeyToken=e56d967875f629d3 
 Hash Algorithm:     SHA1 
 Processor:          MSIL 
 Runtime Version:    v2.0.50727 
 EntryPoint:         Void Main(System.String[]) in AssemblyInfo.Program
 Modules: 
 AssemblyInfo.exe 
  PE Kind:           ILOnly 
  Machine:           I386 
  X509 Certificate:  none 
Referenced Assemblies: 
 mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 
Assembly Attributes: 
 [System.Reflection.AssemblyProductAttribute("AssemblyInfo")] 
 [System.Runtime.CompilerServices.RuntimeCompatibilityAttribute(WrapNonExceptionThrows = True)] 
 [System.Runtime.InteropServices.GuidAttribute("5eb90c8e-1aa6-4739-917e-0d4117b78ba9")] 
 [System.CLSCompliantAttribute((Boolean)True)] 
 [System.Runtime.InteropServices.ComVisibleAttribute((Boolean)False)] 
 [System.Reflection.AssemblyTrademarkAttribute("")] 
 [System.Reflection.AssemblyCopyrightAttribute("Copyright (c) 2007 Kris Vandermotten")] 
 [System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] 
 [System.Reflection.AssemblyCompanyAttribute("Kris Vandermotten")] 
 [System.Reflection.AssemblyConfigurationAttribute("")] 
 [System.Reflection.AssemblyDescriptionAttribute("")] 
 [System.Reflection.AssemblyTitleAttribute("AssemblyInfo")] 
 [System.Diagnostics.DebuggableAttribute((System.Diagnostics.DebuggableAttribute+DebuggingModes)2)] 
 [System.Runtime.CompilerServices.CompilationRelaxationsAttribute((Int32)8)]

Download AssemblyInfo and enjoy.

Technorati Tags: , , ,