New LateBinder

By on 12/11/2008

A long time ago, I posted a class called LateBinder to this site (the previous blog software).  I went on to use it rather prodigiously at work, and it also turned out to be a boon to others who visited this site (for example, Victor of FlatRedBall fame).  Unfortunately, it did have a few limitations; namely that it didn't support getting/setting static fields and properties, and it also didn't work on the xbox360 and zune because the System.Reflection.Emit namespace wasn't available.

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     } }

See more in the archives