He asked me the other day the feasibility of supporting the static properties, and after a few days of hacking about, I've added that, and then some :-)
The version (below) not only supports static fields and properties, but it also supports the xbox360 and zune platforms. Be warned though, I had to fall back on using simple reflection on the non-windows platforms ... so the perf will obviously be slightly slower.
I am fairly proud of how I was able to refactor the code to encapsulate the cross-platform differences. If you look at the LateBindFactory abstract class, it uses a factory method to instantiate the platform-specific version. This way, I was able to keep the pre-processor directives to a minimum, using them only where I needed ... and also keeping the same public API for both platforms.
Let me know if this is useful for you :-)
#region Using Statements
#if !XBOX360 && !ZUNE #define USE_EMIT #endif
using System; using System.Collections.Generic; using System.Reflection; #if USE_EMIT using System.Reflection.Emit; #endif
#endregion
namespace Scurvy { /// <summary> /// Provides a simple interface to late bind a class. /// </summary> /// <remarks>The first time you attempt to get or set a property, it will dynamically generate the get and/or set /// methods and cache them internally. Subsequent gets uses the dynamic methods without having to query the type's /// meta data.</remarks> public sealed class LateBinder<T> { #region Fields
private static LateBindFactory factory = LateBindFactory.Create(); private BindingFlags propertyFlags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static; private Type mType; private Dictionary<string, GetHandler> mPropertyGet; private Dictionary<string, SetHandler> mPropertySet;
private Dictionary<Type, List<string>> mFields;
private T mTarget = default(T);
private static LateBinder<T> _instance;
#endregion
#region Properties
public static LateBinder<T> Instance { get { return _instance; } }
/// <summary> /// The instance that this binder operates on by default /// </summary> /// <remarks>This can be overridden by the caller explicitly passing a target to the indexer</remarks> public T Target { get { return mTarget; } set { mTarget = value; } }
/// <summary> /// Gets or Sets the supplied property on the contained <seealso cref="Instance"/> /// </summary> /// <exception cref="InvalidOperationException">Throws if the contained Instance is null.</exception> public object this[string propertyName] { get { //ValidateInstance(); return this[mTarget, propertyName]; } set { //ValidateInstance(); this[mTarget, propertyName] = value; } }
/// <summary> /// Gets or Sets the supplied property on the supplied target /// </summary> public object this[T target, string propertyName] { get { ValidateGetter(ref propertyName); return mPropertyGet[propertyName](target); } set { ValidateSetter(ref propertyName); mPropertySet[propertyName](target, value); } }
#endregion
#region Methods
#region Constructors
static LateBinder() { _instance = new LateBinder<T>(); }
public LateBinder(T instance) : this() { mTarget = instance; }
public LateBinder() { mType = typeof(T); mPropertyGet = new Dictionary<string, GetHandler>(); mPropertySet = new Dictionary<string, SetHandler>();
mFields = new Dictionary<Type, List<string>>(); } #endregion
#endregion
#region Public Accessors
/// <summary> /// Sets the supplied property on the supplied target /// </summary> /// <typeparam name="K">the type of the value</typeparam> public void SetProperty<K>(T target, string propertyName, K value) { #if !USE_EMIT
// find out if this is a property or field Type type = typeof(T);
PropertyInfo propertyInfo = type.GetProperty(propertyName);
if (propertyInfo != null) { propertyInfo.SetValue(target, value, null); }
else { FieldInfo fieldInfo = type.GetField(propertyName);
if (fieldInfo != null) { fieldInfo.SetValue(target, value); } else { throw new ArgumentException("Cannot find property or field with the name " + propertyName); }
}
#else ValidateSetter(ref propertyName);
if (mPropertySet.ContainsKey(propertyName)) { mPropertySet[propertyName](target, value); } else { // This is probably not a property so see if it is a field.
FieldInfo fieldInfo = mType.GetField(propertyName);
if (fieldInfo == null) { string errorMessage = "LateBinder could not find a field or property by the name of " + propertyName + "Check the name of the property to verify if it is correct."; throw new System.MemberAccessException(errorMessage); } else { object[] args = { value }; mType.InvokeMember(propertyName, BindingFlags.SetField, null, target, args); } } #endif }
public object GetField(T target, ref string propertyName) { Binder binder = null; object[] args = null;
return mType.InvokeMember( propertyName, BindingFlags.GetField, binder, target, args ); }
/// <summary> /// Gets the supplied property on the supplied target /// </summary> /// <typeparam name="K">The type of the property being returned</typeparam> public K GetProperty<K>(T target, ref string propertyName) { ValidateGetter(ref propertyName); return (K)mPropertyGet[propertyName](target); }
public object GetProperty(T target, ref string propertyName) { ValidateGetter(ref propertyName); return mPropertyGet[propertyName](target); }
#endregion
#region Private Helpers
private void ValidateInstance() { if (mTarget == null) { throw new InvalidOperationException("Instance property must not be null"); } }
private void ValidateSetter(ref string propertyName) { if (!mPropertySet.ContainsKey(propertyName)) { PropertyInfo propertyInfo = mType.GetProperty(propertyName, this.propertyFlags);
if (propertyInfo != null) { mPropertySet.Add(propertyName, factory.CreateSetHandler(mType, propertyInfo)); } } }
private void ValidateGetter(ref string propertyName) { if (!mPropertyGet.ContainsKey(propertyName)) { mPropertyGet.Add(propertyName, factory.CreateGetHandler(mType, mType.GetProperty(propertyName))); } }
#endregion
#region Contained Classes
internal delegate object GetHandler(object source); internal delegate void SetHandler(object source, object value); internal delegate object InstantiateObjectHandler();
internal abstract class LateBindFactory { public LateBindFactory() { }
public static LateBindFactory Create() { #if USE_EMIT return new DynamicMethodFactory(); #else return new ReflectionFactory(); #endif }
public abstract InstantiateObjectHandler CreateInstantiateObjectHandler(Type type); public abstract GetHandler CreateGetHandler(Type type, PropertyInfo propertyInfo); public abstract GetHandler CreateGetHandler(Type type, FieldInfo fieldInfo); public abstract SetHandler CreateSetHandler(Type type, PropertyInfo propertyInfo); public abstract SetHandler CreateSetHandler(Type type, FieldInfo fieldInfo); }
#if USE_EMIT internal sealed class DynamicMethodFactory : LateBindFactory { public DynamicMethodFactory() { }
#region Public Methods
public override InstantiateObjectHandler CreateInstantiateObjectHandler(Type type) { ConstructorInfo constructorInfo = type.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[0], null); if (constructorInfo == null) { throw new ApplicationException(string.Format("The type {0} must declare an empty constructor (the constructor may be private, internal, protected, protected internal, or public).", type)); }
DynamicMethod dynamicMethod = new DynamicMethod("InstantiateObject", MethodAttributes.Static | MethodAttributes.Public, CallingConventions.Standard, typeof(object), null, type, true); ILGenerator generator = dynamicMethod.GetILGenerator(); generator.Emit(OpCodes.Newobj, constructorInfo); generator.Emit(OpCodes.Ret); return (InstantiateObjectHandler)dynamicMethod.CreateDelegate(typeof(InstantiateObjectHandler)); }
public override GetHandler CreateGetHandler(Type type, PropertyInfo propertyInfo) { MethodInfo getMethodInfo = propertyInfo.GetGetMethod(true);
DynamicMethod dynamicGet = CreateGetDynamicMethod(type); ILGenerator getGenerator = dynamicGet.GetILGenerator();
if (!getMethodInfo.IsStatic) { getGenerator.Emit(OpCodes.Ldarg_0); } getGenerator.Emit(OpCodes.Call, getMethodInfo); BoxIfNeeded(getMethodInfo.ReturnType, getGenerator); getGenerator.Emit(OpCodes.Ret);
return (GetHandler)dynamicGet.CreateDelegate(typeof(GetHandler)); }
public override GetHandler CreateGetHandler(Type type, FieldInfo fieldInfo) {
DynamicMethod dynamicGet = CreateGetDynamicMethod(type); ILGenerator getGenerator = dynamicGet.GetILGenerator();
if (!fieldInfo.IsStatic) { getGenerator.Emit(OpCodes.Ldarg_0); } getGenerator.Emit(OpCodes.Ldfld, fieldInfo); BoxIfNeeded(fieldInfo.FieldType, getGenerator); getGenerator.Emit(OpCodes.Ret);
return (GetHandler)dynamicGet.CreateDelegate(typeof(GetHandler)); }
public override SetHandler CreateSetHandler(Type type, PropertyInfo propertyInfo) { MethodInfo setMethodInfo = propertyInfo.GetSetMethod(true);
DynamicMethod dynamicSet = CreateSetDynamicMethod(type); ILGenerator setGenerator = dynamicSet.GetILGenerator();
if (!setMethodInfo.IsStatic) { setGenerator.Emit(OpCodes.Ldarg_0); } setGenerator.Emit(OpCodes.Ldarg_1); UnboxIfNeeded(setMethodInfo.GetParameters()[0].ParameterType, setGenerator); setGenerator.Emit(OpCodes.Call, setMethodInfo); setGenerator.Emit(OpCodes.Ret);
return (SetHandler)dynamicSet.CreateDelegate(typeof(SetHandler)); }
public override SetHandler CreateSetHandler(Type type, FieldInfo fieldInfo) { DynamicMethod dynamicSet = CreateSetDynamicMethod(type); ILGenerator setGenerator = dynamicSet.GetILGenerator();
if (!fieldInfo.IsStatic) { setGenerator.Emit(OpCodes.Ldarg_0); } setGenerator.Emit(OpCodes.Ldarg_1); UnboxIfNeeded(fieldInfo.FieldType, setGenerator);
if (!fieldInfo.IsStatic) { setGenerator.Emit(OpCodes.Stfld, fieldInfo); } else { setGenerator.Emit(OpCodes.Stsfld, fieldInfo); }
setGenerator.Emit(OpCodes.Ret);
return (SetHandler)dynamicSet.CreateDelegate(typeof(SetHandler)); }
#endregion
#region Private Methods
private static DynamicMethod CreateGetDynamicMethod(Type type) { return new DynamicMethod("DynamicGet", typeof(object), new Type[] { typeof(object) }, type, true); }
// CreateSetDynamicMethod private static DynamicMethod CreateSetDynamicMethod(Type type) { return new DynamicMethod("DynamicSet", typeof(void), new Type[] { typeof(object), typeof(object) }, type, true); }
// BoxIfNeeded private static void BoxIfNeeded(Type type, ILGenerator generator) { if (type.IsValueType) { generator.Emit(OpCodes.Box, type); } }
// UnboxIfNeeded private static void UnboxIfNeeded(Type type, ILGenerator generator) { if (type.IsValueType) { generator.Emit(OpCodes.Unbox_Any, type); } }
#endregion } #endif
#if !USE_EMIT internal sealed class ReflectionFactory : LateBindFactory { public ReflectionFactory() { }
public override InstantiateObjectHandler CreateInstantiateObjectHandler(Type type) { return delegate() { return Activator.CreateInstance(type); }; }
public override GetHandler CreateGetHandler(Type type, PropertyInfo propertyInfo) { MethodInfo method = propertyInfo.GetGetMethod(true);
return delegate(object source) { return method.Invoke(method.IsStatic ? null : source, null); }; }
public override GetHandler CreateGetHandler(Type type, FieldInfo fieldInfo) { return delegate(object source) { return fieldInfo.GetValue(fieldInfo.IsStatic ? null : source); }; }
public override SetHandler CreateSetHandler(Type type, PropertyInfo propertyInfo) { MethodInfo method = propertyInfo.GetSetMethod();
return delegate(object source, object value) { method.Invoke(method.IsStatic ? null : source, new object[] { value }); }; }
public override SetHandler CreateSetHandler(Type type, FieldInfo fieldInfo) { return delegate(object source, object value) { fieldInfo.SetValue(fieldInfo.IsStatic ? null : source, value); }; } } #endif #endregion } }