Click here to Skip to main content
Email Password   helpLost your password?

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.

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralYou made the same error I made...
Paulo Morgado
6:38 20 Aug '08  
... when I first thought of this: Getting MethodInfo using LINQ[^]

You only need to provide implementation for Action, Action<T>, Func<TResult> and Func<T, TResult>.

MemberInfo(() => MyClass.Method());
MemberInfo((MyClass i) => i.Method());
MemberInfo(() => MyClass.Property);
MemberInfo((MyClass i) => i.Property);

You must be careful when using this, because C# And Visual Basic Generate Different Expression Trees[^].

Paulo Morgado Portugal - Europe's West Coast

GeneralRe: You made the same error I made...
Nick Butler
7:03 20 Aug '08  
Thanks for your comment Paulo.

I presume you mean something like this:

Strong.Static.Method( () => Class.StaticAction( 42 ) );
You can certainly do it that way.

Interesting that C# and VB differ. Does this also happen when using Type.GetProperty( string name )?

Nick

----------------------------------
Be excellent to each other Smile

GeneralRe: You made the same error I made...
Paulo Morgado
14:13 20 Aug '08  
Yes. There's no need for all those overloads.

You probably thought the same as I did. I need a type parameter for the instance and for all parameters. But not. You only need a type for the result (if a result is returned) and for the instance (if it's for an intance's member).

Type.Get<whatever> works as expected. The problem is what type the compilers are looking at when they call Get<whatever>.

Paulo Morgado Portugal - Europe's West Coast

GeneralRe: You made the same error I made...
Nick Butler
0:50 21 Aug '08  
Interesting, thanks.

I did consider Func overloads, but as you can see I decided against them. Not difficult to add if you want them though.

----------------------------------
Be excellent to each other Smile

GeneralNeed a better example
MR_SAM_PIPER
17:40 18 Aug '08  
I think I understand what you're talking about, but a better real-world example would help a lot.

You mention ObjectDataSource - can you provide an example of how to use ObjectDataSource with your system? Eg,

This one <asp:objectdatasource ...old way... />

Can become this one <asp:objectdatasource ...new way...   />

Little concerned about performance aspects but I certainly understand the pain of late-binding with ObjectDataSources, and enlisting the compiler to help with refactoring ASP.NET pages is always a good thing IMHO.
GeneralRe: Need a better example
Nick Butler
0:58 19 Aug '08  
Hi Sam

The overloaded methods return instances derived from MemberInfo. This opens up the whole Reflection API for you.

Here is a simple example of retrieving the member name for use with ObjectDataSource:

in page.aspx:

<asp:ObjectDataSource id="ods" runat="server"/>
in code-behind
ods.TypeName = typeof( Employee ).Name;
ods.SelectMethod = Strong.Static.Method( () => Employee.FetchAll() ).Name;

The compiler can now check that Employee.FetchAll() actually exists.

This technique is really a way of implementing the missing methodinfoof keyword, similar to the existing typeof keyword.

Nick

----------------------------------
Be excellent to each other Smile

GeneralVery similar to what I did with my Dynamic library.
Marc Brooks
17:17 18 Aug '08  
I like what you've done... using delegates really cleans up what I did in the Dynamic library.

http://musingmarc.blogspot.com

GeneralRe: Very similar to what I did with my Dynamic library.
Nick Butler
1:06 19 Aug '08  
Thanks Marc. I hope it's useful Smile

I have always had a problem with magic strings. I found out about compiler support for expression trees while I was looking into Linq to SQL. They seem to be a good solution until Anders adds methodinfoof, propertyinfoof and fieldinfoof keywords similar to typeof.

Nick

----------------------------------
Be excellent to each other Smile

Generalmight be a trend of doing reflection
RednaxelaFX
6:10 11 Aug '08  
Interesting stuff. Just to mention, Andre Loker had a post some time ago that used exactly the same techniques, Getting rid of strings (2): use lambda expressions But this way of doing reflection is actually slower then plain reflection. Not significant if you're doing some database queries or the like, but when I first saw this lambda way, I thought it ought to be faster than plain reflection, because: with lambdas, the compiler had all the information needed to determine what you're looking for; it does, but it also had to generate all the expression tree creation calls, which makes it slower than plain reflection. It'll just be great if those overhead could be eliminated as well, but I haven't tried to yet.
GeneralRe: might be a trend of doing reflection
Nick Butler
7:13 11 Aug '08  
Thanks for your comment and the link: yes, it's the same technique but packaged slightly differently.

I also tried putting the types in the lambda input parameters as you suggested, but it's up to the user how explicit they want to be with the generic types. Type inference works quite well, but I guess the more explicit you are, the safer it will be.

Nick

----------------------------------
Be excellent to each other Smile

GeneralRe: might be a trend of doing reflection
Roger Alsing
4:23 13 Aug '08  
There are some drawbacks in this approach.
Methods with ref / out parameters are not possible to access this way.

But I guess it will still cover 95% of the use cases.


AnswerRe: might be a trend of doing reflection
Nick Butler
7:00 13 Aug '08  
Hi Roger

It's only the lambda itself that cannot have ref or out parameters. The body of the lambda can be anything you want.

If you have this method:

partial class Class
{
public static void RefMethod( ref int i ) { ... }
}

You can write:

Strong.Static.Method( i => Class.RefMethod( ref i ) );

This should work, since the lambda will never be executed.

----------------------------------
Be excellent to each other Smile


Last Updated 11 Aug 2008 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010