|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionQuite often, I run across the need to dynamically retrieve the value of an object's property (or field), where the actual member might not be known until run-time. Some examples of where one might use this are:
BackgroundIn the past, I have made use of run-time reflection to: find a property's PropertyInfo pi = typeof(Widget).GetProperty("ID");
Int32 id = (Int32) pi.GetValue(widget, null);
This works fine if you only have to retrieve the member's value for a single object; but once you have to get the value in a loop, it performs very slow. The New Method (.NET 2.0)I got to thinking, why not generate (at run-time) the same IL code that the compiler generates at compile time, and use that to call the property's In previous versions of .NET (1.0 and 1.1), there is a pretty big overhead to generating IL code. This is because using the Thankfully, .NET 2.0 provides us with a new Using this new UsageTypeUtility<Widget>.MemberGetDelegate<Int32>
GetID = TypeUtility<Widget>.GetMemberGetDelegate<Int32>("ID");
Int32 id = GetID(widget);
The Codepublic class TypeUtility<ObjectType>
{
public delegate MemberType
MemberGetDelegate<MemberType>(ObjectType obj);
public static MemberGetDelegate<MemberType>
GetMemberGetDelegate<MemberType>(string memberName)
{
Type objectType = typeof(ObjectType);
PropertyInfo pi = objectType.GetProperty(memberName);
FieldInfo fi = objectType.GetField(memberName);
if (pi != null)
{
// Member is a Property...
MethodInfo mi = pi.GetGetMethod();
if (mi != null)
{
// NOTE: As reader J. Dunlap pointed out...
// Calling a property's get accessor is faster/cleaner using
// Delegate.CreateDelegate rather than Reflection.Emit
return (MemberGetDelegate<MemberType>)
Delegate.CreateDelegate(typeof(
MemberGetDelegate<MemberType>), mi);
}
else
throw new Exception( String.Format(
"Property: '{0}' of Type: '{1}' does" +
" not have a Public Get accessor",
memberName, objectType.Name ) );
}
else if (fi != null)
{
// Member is a Field...
DynamicMethod dm = new DynamicMethod("Get" + memberName,
typeof(MemberType), new Type[] { objectType }, objectType);
ILGenerator il = dm.GetILGenerator();
// Load the instance of the object (argument 0) onto the stack
il.Emit(OpCodes.Ldarg_0);
// Load the value of the object's field (fi) onto the stack
il.Emit(OpCodes.Ldfld, fi);
// return the value on the top of the stack
il.Emit(OpCodes.Ret);
return (MemberGetDelegate<MemberType>)
dm.CreateDelegate(typeof(MemberGetDelegate<MemberType>));
}
else
throw new Exception( String.Format(
"Member: '{0}' is not a Public Property or Field of Type: '{1}'",
memberName, objectType.Name ));
}
}
Tweaking Performance - Caching the Generated CodeThere is a small amount of build up time that occurs during each call to public class TypeUtility<ObjectType>
{
private static Dictionary<string,Delegate>
_memberGetDelegates = new Dictionary<string,Delegate>();
public static MemberGetDelegate<MemberType>
GetCachedMemberGetDelegate<MemberType>(string memberName)
{
if ( _memberGetDelegates.ContainsKey(memberName) )
return (MemberGetDelegate<MemberType>)
_memberGetDelegates[memberName];
MemberGetDelegate<MemberType> returnValue =
GetMemberGetDelegate<MemberType>( memberName );
lock ( _memberGetDelegates )
{
_memberGetDelegates[memberName] = returnValue;
}
return returnValue;
}
}
Performance Test Results
Based on my performance tests, using the dynamically generated code is 30x-50x faster than using normal reflection. It is still only about half as fast as using compiled code, which I imagine is due to the number of method calls. With compiled code, there is only one method call: the property's An Example Using This ConceptI mentioned earlier that one example of where this concept could come in handy is in the case of a generic comparer used to sort a collection or a list. Consider the following: List<Widget> widgets = GetWidgetsListFromSomewhere();
// Sort widgets by ID
widgets.Sort( new MemberComparer<Widget>("ID") );
// Now re-sort widgets by Name
widgets.Sort( new MemberComparer<Widget>("Name") );
You can see how easy this makes it to sort your collection based on a dynamic property or field. For example, you could easily wire this up to a UI to allow for fast sorting based on the column a user clicked on. Here is the code for a simple Note: This could easily be extended to support chaining of fields and sort directions together. public class MemberComparer<ObjectType> : IComparer<ObjectType>
{
private delegate int CompareDelegate(ObjectType x, ObjectType y);
private CompareDelegate _compare;
public MemberComparer(string memberName)
{
_compare = GetCompareDelegate(memberName);
}
public int Compare(ObjectType x, ObjectType y)
{
return _compare(x, y);
}
private CompareDelegate GetCompareDelegate(string memberName)
{
Type objectType = typeof(ObjectType);
PropertyInfo pi = objectType.GetProperty(memberName);
FieldInfo fi = objectType.GetField(memberName);
Type memberType = null;
bool isProperty = false;
if (pi != null)
{
if (pi.GetGetMethod() != null)
{
memberType = pi.PropertyType;
isProperty = true;
}
else
throw new Exception(String.Format(
"Property: '{0}' of Type: '{1}' " +
"does not have a Public Get accessor",
memberName, objectType.Name));
}
else if (fi != null)
memberType = fi.FieldType;
else
throw new Exception(String.Format(
"'{0}' is not a Public Field or Property" +
" with a Get accessor for Type: '{1}' ",
memberName, objectType.Name));
Type comparerType =
typeof(Comparer<>).MakeGenericType(
new Type[] { memberType });
MethodInfo getDefaultMethod =
comparerType.GetProperty("Default").GetGetMethod();
MethodInfo compareMethod =
getDefaultMethod.ReturnType.GetMethod("Compare");
DynamicMethod dm =
new DynamicMethod("Compare_" + memberName, typeof(int),
new Type[] { objectType, objectType }, comparerType);
ILGenerator il = dm.GetILGenerator();
// Load Comparer<memberType>.Default onto the stack
il.EmitCall(OpCodes.Call, getDefaultMethod, null);
// Load the member from arg 0 onto the stack
il.Emit(OpCodes.Ldarg_0);
if (isProperty)
il.EmitCall(OpCodes.Callvirt, pi.GetGetMethod(), null);
else
il.Emit(OpCodes.Ldfld);
// Load the member from arg 1 onto the stack
il.Emit(OpCodes.Ldarg_1);
if (isProperty)
il.EmitCall(OpCodes.Callvirt, pi.GetGetMethod(), null);
else
il.Emit(OpCodes.Ldfld);
// Call the Compare method
il.EmitCall(OpCodes.Callvirt, compareMethod, null);
il.Emit(OpCodes.Ret);
return (CompareDelegate)dm.CreateDelegate(
typeof(CompareDelegate));
}
}
Assuming you want to sort the // To Sort by Name property
public int Compare(Widget x, Widget y)
{
return System.Collections.Generic.Comparer<string>.Default.Compare(
x.Name, y.Name);
}
// To Sort by ID property
public int Compare(Widget x, Widget y)
{
return System.Collections.Generic.Comparer<int>.Default.Compare(x.ID, y.ID);
}
ConclusionThere are many other places that this concept can be used in place of the old slow method of reflection. With this article, I have only scratched the surface of what can be done. Hopefully, I have at least given some insight into how powerful History
| ||||||||||||||||||||