Introduction
This is a short article that solves a particular problem. If you're not worried of having magic strings in your code, then there is nothing going on here...
Many methods in the .NET Framework use strings to identify code tokens. Reflection is the obvious area, but there are others like ObjectDataSource
. The problem with these strings is that they are opaque to the compiler and IDE. This effectively means that you cannot refactor your code, use simple obfuscation, or even do a "Find All References", and rely on the results. I think this is a major problem, so I found a different way, which I present here.
Background
This solution relies on a new feature in the C# 3.0 compiler. When a lambda expression is assigned to a variable, field, or parameter whose type is System.Linq.Expressions.Expression<TDelegate>
, the compiler emits instructions to build an expression tree instead of compiling the lambda to IL. An expression tree is a data representation of a code statement, much like Reflection provides data representations of types. As you can guess from the namespace, this was introduced to support LINQ, but it also enables this solution.
Lambdas are real tokens that the compiler can check, and expression trees are real data structures that can be examined at runtime. So, if you write a lambda that specifies a particular member (field, property, or method), you can then examine the expression tree in the code to find the member.
So, in C# 3.0, the compiler goes just far enough to enable this solution. It parses the source file, but instead of going all the way to IL, it produces output in a form that is easily accessible at runtime.
Here are some links:
Manuel Abadia has written a very good expression tree graphical debugger visualizer [^].
There is also a basic text visualizer in the VS2008 C# samples (MSDN [^]).
The Strong class
The Strong
class is quite small, so I have included it here in full. LambdaExpression
is the base class of Expression<TDelegate>
. The interesting methods are the last four.
public delegate void Action<A, B, C, D, E>
( A a, B b, C c, D d, E e );
public delegate void Action<A, B, C, D, E, F>
( A a, B b, C c, D d, E e, F f );
public delegate void Action<A, B, C, D, E, F, G>
( A a, B b, C c, D d, E e, F f, G g );
public delegate void Action<A, B, C, D, E, F, G, H>
( A a, B b, C c, D d, E e, F f, G g, H h );
public delegate void Action<A, B, C, D, E, F, G, H, I>
( A a, B b, C c, D d, E e, F f, G g, H h, I i );
public static class Strong
{
public static class Static
{
public static FieldInfo Field<T>
( Expression<Func<T>> m )
{ return GetFieldInfo( m ); }
public static PropertyInfo Property<T>
( Expression<Func<T>> m )
{ return GetPropertyInfo( m ); }
public static MethodInfo Method
( Expression<Action> m )
{ return GetMethodInfo( m ); }
public static MethodInfo Method<T1>
( Expression<Action<T1>> m )
{ return GetMethodInfo( m ); }
public static MethodInfo Method<T1, T2>
( Expression<Action<T1, T2>> m )
{ return GetMethodInfo( m ); }
public static MethodInfo Method<T1, T2, T3>
( Expression<Action<T1, T2, T3>> m )
{ return GetMethodInfo( m ); }
public static MethodInfo Method<T1, T2, T3, T4>
( Expression<Action<T1, T2, T3, T4>> m )
{ return GetMethodInfo( m ); }
public static MethodInfo Method<T1, T2, T3, T4, T5>
( Expression<Action<T1, T2, T3, T4, T5>> m )
{ return GetMethodInfo( m ); }
public static MethodInfo Method<T1, T2, T3, T4, T5, T6>
( Expression<Action<T1, T2, T3, T4, T5, T6>> m )
{ return GetMethodInfo( m ); }
public static MethodInfo Method<T1, T2, T3, T4, T5, T6, T7>
( Expression<Action<T1, T2, T3, T4, T5, T6, T7>> m )
{ return GetMethodInfo( m ); }
public static MethodInfo Method<T1, T2, T3, T4, T5, T6, T7, T8>
( Expression<Action<T1, T2, T3, T4, T5, T6, T7, T8>> m )
{ return GetMethodInfo( m ); }
}
public static class Instance<TClass>
{
public static FieldInfo Field<T>
( Expression<Func<TClass, T>> m )
{ return GetFieldInfo( m ); }
public static PropertyInfo Property<T>
( Expression<Func<TClass, T>> m )
{ return GetPropertyInfo( m ); }
public static MethodInfo Method
( Expression<Action<TClass>> m )
{ return GetMethodInfo( m ); }
public static MethodInfo Method<T1>
( Expression<Action<TClass, T1>> m )
{ return GetMethodInfo( m ); }
public static MethodInfo Method<T1, T2>
( Expression<Action<TClass, T1, T2>> m )
{ return GetMethodInfo( m ); }
public static MethodInfo Method<T1, T2, T3>
( Expression<Action<TClass, T1, T2, T3>> m )
{ return GetMethodInfo( m ); }
public static MethodInfo Method<T1, T2, T3, T4>
( Expression<Action<TClass, T1, T2, T3, T4>> m )
{ return GetMethodInfo( m ); }
public static MethodInfo Method<T1, T2, T3, T4, T5>
( Expression<Action<TClass, T1, T2, T3, T4, T5>> m )
{ return GetMethodInfo( m ); }
public static MethodInfo Method<T1, T2, T3, T4, T5, T6>
( Expression<Action<TClass, T1, T2, T3, T4, T5, T6>> m )
{ return GetMethodInfo( m ); }
public static MethodInfo Method<T1, T2, T3, T4, T5, T6, T7>
( Expression<Action<TClass, T1, T2, T3, T4, T5, T6, T7>> m )
{ return GetMethodInfo( m ); }
public static MethodInfo Method<T1, T2, T3, T4, T5, T6, T7, T8>
( Expression<Action<TClass, T1, T2, T3, T4, T5, T6, T7, T8>> m )
{ return GetMethodInfo( m ); }
}
static FieldInfo GetFieldInfo( LambdaExpression lambda )
{ return ( FieldInfo ) GetMemberInfo( lambda ); }
static PropertyInfo GetPropertyInfo( LambdaExpression lambda )
{ return ( PropertyInfo ) GetMemberInfo( lambda ); }
static MemberInfo GetMemberInfo( LambdaExpression lambda )
{ return ( ( MemberExpression ) lambda.Body ).Member; }
static MethodInfo GetMethodInfo( LambdaExpression lambda )
{ return ( ( MethodCallExpression ) lambda.Body ).Method; }
}
Using the code
The static Strong
class has no public methods. Instead, it contains two nested static classes, Static
and Instance
, that allow you to specify the type of member you want. The Instance
class takes a generic type parameter which should be the containing class.
Both classes provide public methods called Field
, Property
, and Method
which are overloaded. These return FieldInfo
, PropertyInfo
, and MethodInfo
respectively. These classes all derive from MemberInfo
which has a Name
property, amongst others.
The easiest way is to show some examples. They all operate on this class:
class Class
{
public static int StaticField = 42;
public static int StaticProperty { get; set; }
public static void StaticAction() { }
public static void StaticAction( int i ) { }
public static int StaticFunc() { return 42; }
public static int StaticFunc( int i ) { return i; }
public int Field = 42;
public int Property { get; set; }
public void Action() { }
public void Action( int i ) { }
public int Func() { return 42; }
public int Func( int i ) { return i; }
}
Finally, here are the examples:
Strong.Static.Field( () => Class.StaticField );
Strong.Static.Property( () => Class.StaticProperty );
Strong.Static.Method( () => Class.StaticAction() );
Strong.Static.Method<int>( i => Class.StaticAction( i ) );
Strong.Static.Method( () => Class.StaticFunc() );
Strong.Static.Method<int>( i => Class.StaticFunc( i ) );
Strong.Instance<Class>.Field( o => o.Field );
Strong.Instance<Class>.Property( o => o.Property );
Strong.Instance<Class>.Method( o => o.Action() );
Strong.Instance<Class>.Method<int>( ( o, i ) => o.Action( i ) );
Strong.Instance<Class>.Method( o => o.Func() );
Strong.Instance<Class>.Method<int>( ( o, i ) => o.Func( i ) );
Conclusion
This is the neatest solution to the magic string problem that I could think of. However, it relies on the C# 3.0 compiler, which might be a problem for some people. You could do something similar with delegates in C# 2.0, but you would have to extract the inner references from the compiled IL code, which is a bit more of a hack. The new expression trees in C# 3.0 make this solution a lot easier.
Thanks for reading this article; I hope you liked it.