This project is read-only.

Generic FastPropertyGetter

May 26, 2008 at 6:14 AM
Edited May 26, 2008 at 6:15 AM
Why Is FastPropertyGetter not generic? The IL is as simple as this:

il.Emit(OpCodes.Ldarg_0);
if (methodInfo.IsFinal)
{
  il.Emit(OpCodes.Call, methodInfo);
}
else
{
  il.Emit(OpCodes.Callvirt, methodInfo);
}
if (methodInfo.ReturnType.IsValueType)
{
  il.Emit(OpCodes.Box, methodInfo.ReturnType);
}
il.Emit(OpCodes.Ret);

I run a simple test - generic version is twice as fast.

May 27, 2008 at 8:40 AM
Hi Aivanoff,

The reason we didn't go generic is that we have no idea at design time what type will be returned so we were stuck with handling the return type as an object in any case. Nonetheless I'd like to take a look at your code - can you post the entire listing?

Ta

Josh
May 27, 2008 at 4:07 PM


joshtwist wrote:
Hi Aivanoff,

The reason we didn't go generic is that we have no idea at design time what type will be returned so we were stuck with handling the return type as an object in any case. Nonetheless I'd like to take a look at your code - can you post the entire listing?

Ta

Josh


You are right. Anyways, here is the listing:

using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Globalization;

namespace FastProperty
{
 public class FastPropertyGetter2<T>
 {
  private delegate Object GetData(T data);

  private GetData _gdDelegate;

  public FastPropertyGetter2(String propertyName)
  {
   if (String.IsNullOrEmpty(propertyName))
   {
    throw new ArgumentNullException("propertyName");
   }

   Type type = typeof(T);
   PropertyInfo propertyInfo = type.GetProperty(propertyName);

   if (propertyInfo == null || !propertyInfo.CanRead)
   {
    throw new ArgumentException(
     String.Format(CultureInfo.CurrentCulture, "FastPropertyGetter: There is no readable property '{0}' on type '{1}'.", propertyName, type));
   }

   MethodInfo methodInfo = propertyInfo.GetGetMethod();
   //this.PropertyType = methodInfo.ReturnType;

   DynamicMethod dm = new DynamicMethod("FastGetProperty",
             typeof(Object),
             new Type[] { typeof(T) }, typeof(FastPropertyGetter2<T>));

   ILGenerator il = dm.GetILGenerator();

   // load the passed object
   il.Emit(OpCodes.Ldarg_0);

   if (methodInfo.IsFinal)
   {
    // Call the property get method
    il.Emit(OpCodes.Call, methodInfo);
   }
   else
   {
    // Call the property get method
    il.Emit(OpCodes.Callvirt, methodInfo);
   }

   // box if necessary
   if (methodInfo.ReturnType.IsValueType)
   {
    il.Emit(OpCodes.Box, methodInfo.ReturnType);
   }

   il.Emit(OpCodes.Ret);

   this._gdDelegate = (GetData)dm.CreateDelegate(typeof(GetData), null);
  }

  public Object GetValue(T data)
  {
   return this._gdDelegate(data);
  }
 }
}

May 27, 2008 at 4:11 PM

Event if FastPropertyGetter cannot be generic another improvement is removing type check. My tests show it is almost as fast as generic version:

using System;
using System.Reflection;
using System.Globalization;
using System.Reflection.Emit;

namespace FastProperty
{
 public class FastPropertyGetter3
 {
  private delegate Object GetData(Object data);

  private GetData _gdDelegate;

  /// <summary>
  /// Gets the Type the property returns
  /// </summary>
  public Type PropertyType
  {
   get;
   private set;
  }

  public FastPropertyGetter3(String propertyName, Type type)
  {
   if (String.IsNullOrEmpty(propertyName))
   {
    throw new ArgumentNullException("propertyName");
   }

   if (null == type)
   {
    throw new ArgumentNullException("type");
   }

   PropertyInfo propertyInfo = type.GetProperty(propertyName);

   if (propertyInfo == null || !propertyInfo.CanRead)
   {
    throw new ArgumentException(
     String.Format(CultureInfo.CurrentCulture, "FastPropertyGetter: There is no readable property '{0}' on type '{1}'.", propertyName, type));
   }

   MethodInfo methodInfo = propertyInfo.GetGetMethod();
   this.PropertyType = methodInfo.ReturnType;

   DynamicMethod dm = new DynamicMethod("FastGetProperty",
             typeof(Object),
             new Type[] { typeof(Object) }, typeof(FastPropertyGetter));

   ILGenerator il = dm.GetILGenerator();

   // load the passed object
   il.Emit(OpCodes.Ldarg_0);

   if (type.IsValueType)
   {
    il.Emit(OpCodes.Unbox_Any, type);
   }

   if (methodInfo.IsFinal)
   {
    // Call the property get method
    il.Emit(OpCodes.Call, methodInfo);
   }
   else
   {
    // Call the property get method
    il.Emit(OpCodes.Callvirt, methodInfo);
   }

   // box if necessary
   if (methodInfo.ReturnType.IsValueType)
   {
    il.Emit(OpCodes.Box, methodInfo.ReturnType);
   }

   il.Emit(OpCodes.Ret);

   this._gdDelegate = (GetData)dm.CreateDelegate(typeof(GetData), null);
  }

  public Object GetValue(Object data)
  {
   return this._gdDelegate(data);
  }
 }
}

May 28, 2008 at 8:02 PM
Hi Avianoff,

Have you seen my post detailing how you could write the FPG using Expressions?

Lambda and Expression Trees

In fact, the two converts are superfluous also - the code could be reduced to this:

var param = Expression.Parameter(typeof(object), "source");
var get
=
Expression.MakeMemberAccess(param, propertyInfo);
var lambda
= Expression.Lambda<Func<object, object>>(get, param);


You're right though - there are a number of optimisations that could be made to the .NET 2 version of the FPG - both in terms of perf and readability. I hope to get to those soon :)

Thanks for posting and keep the feedback coming.

Josh