Wednesday, July 11, 2012

Build fast access to objects with code emiting.

 using System;  
 using System.Collections.Generic;  
 using System.Text;  
 using System.Reflection;  
 using System.Reflection.Emit;  
 using System.Threading;  
 using System.Collections;  
 namespace Cloning  
 {  
   /// <summary>  
   /// Enumeration that defines the type of cloning of a field.  
   /// Used in combination with the CloneAttribute  
   /// </summary>  
   public enum CloneType  
   {  
     None,  
     ShallowCloning,  
     DeepCloning  
   }  
   /// <summary>  
   /// CloningAttribute for specifying the cloneproperties of a field.  
   /// </summary>  
   [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]  
   public class CloneAttribute : Attribute  
   {  
     private CloneType _clonetype;  
     public CloneAttribute()  
     {  
     }  
     public CloneType CloneType  
     {  
       get { return _clonetype; }  
       set { _clonetype = value; }  
     }  
   }  
   /// <summary>  
   /// Class that clones objects  
   /// </summary>  
   /// <remarks>  
   /// Currently can deepclone to 1 level deep.  
   /// Ex. Person.Addresses (Person.List<Address>)   
   /// -> Clones 'Person' deep  
   /// -> Clones the objects of the 'Address' list deep  
   /// -> Clones the sub-objects of the Address object shallow. (at the moment)  
   /// </remarks>  
   public static class CloneHelper<T>  
   where T : class  
   {  
     #region Declarations  
     // Dictionaries for caching the (pre)compiled generated IL code.  
     private static Dictionary<Type, Delegate> _cachedILShallow = new Dictionary<Type, Delegate>();  
     private static Dictionary<Type, Delegate> _cachedILDeep = new Dictionary<Type, Delegate>();  
     // This is used for setting the fixed cloning, of this is null, then  
     // the custom cloning should be invoked. (use Clone(T obj) for custom cloning)  
     private static CloneType? _globalCloneType = CloneType.ShallowCloning;  
     #endregion  
     #region Public Methods  
     /// <summary>  
     /// Clone an object with Deep Cloning or with a custom strategy   
     /// such as ShallowCloning and/or DeepCloning combined (use the CloneAttribute)  
     /// </summary>  
     /// <param name="obj">Object to perform cloning on.</param>  
     /// <returns>Cloned object.</returns>  
     public static T Clone(T obj)  
     {  
       _globalCloneType = null;  
       return CloneObjectWithILDeep(obj);  
     }  
     /// <summary>  
     /// Clone an object with one strategy (DeepClone or ShallowClone)  
     /// </summary>  
     /// <param name="obj">Object to perform cloning on.</param>  
     /// <param name="cloneType">Type of cloning</param>  
     /// <returns>Cloned object.</returns>  
     /// <exception cref="InvalidOperationException">When a wrong enum for cloningtype is passed.</exception>  
     public static T Clone(T obj, CloneType cloneType)  
     {  
       if (_globalCloneType != null)  
         _globalCloneType = cloneType;  
       switch (cloneType)  
       {  
         case CloneType.None:  
           throw new InvalidOperationException("No need to call this method?");  
         case CloneType.ShallowCloning:  
           return CloneObjectWithILShallow(obj);  
         case CloneType.DeepCloning:  
           return CloneObjectWithILDeep(obj);  
         default:  
           break;  
       }  
       return default(T);  
     }  
     #endregion  
     #region Private Methods  
     /// <summary>    
     /// Generic cloning method that clones an object using IL.    
     /// Only the first call of a certain type will hold back performance.    
     /// After the first call, the compiled IL is executed.    
     /// </summary>    
     /// <typeparam name="T">Type of object to clone</typeparam>    
     /// <param name="myObject">Object to clone</param>    
     /// <returns>Cloned object (shallow)</returns>    
     private static T CloneObjectWithILShallow(T myObject)  
     {  
       Delegate myExec = null;  
       if (!_cachedILShallow.TryGetValue(typeof(T), out myExec))  
       {  
         DynamicMethod dymMethod = new DynamicMethod("DoShallowClone", typeof(T), new Type[] { typeof(T) }, Assembly.GetExecutingAssembly().ManifestModule, true);  
         ConstructorInfo cInfo = myObject.GetType().GetConstructor(new Type[] { });  
         ILGenerator generator = dymMethod.GetILGenerator();  
         LocalBuilder lbf = generator.DeclareLocal(typeof(T));  
         generator.Emit(OpCodes.Newobj, cInfo);  
         generator.Emit(OpCodes.Stloc_0);  
         foreach (FieldInfo field in myObject.GetType().GetFields(System.Reflection.BindingFlags.Instance  
         | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public))  
         {  
           generator.Emit(OpCodes.Ldloc_0);  
           generator.Emit(OpCodes.Ldarg_0);  
           generator.Emit(OpCodes.Ldfld, field);  
           generator.Emit(OpCodes.Stfld, field);  
         }  
         generator.Emit(OpCodes.Ldloc_0);  
         generator.Emit(OpCodes.Ret);  
         myExec = dymMethod.CreateDelegate(typeof(Func<T, T>));  
         _cachedILShallow.Add(typeof(T), myExec);  
       }  
       return ((Func<T, T>)myExec)(myObject);  
     }  
     /// <summary>  
     /// Generic cloning method that clones an object using IL.  
     /// Only the first call of a certain type will hold back performance.  
     /// After the first call, the compiled IL is executed.   
     /// </summary>  
     /// <param name="myObject">Type of object to clone</param>  
     /// <returns>Cloned object (deeply cloned)</returns>  
     private static T CloneObjectWithILDeep(T myObject)  
     {  
       Delegate myExec = null;  
       if (!_cachedILDeep.TryGetValue(typeof(T), out myExec))  
       {  
         // Create ILGenerator        
         DynamicMethod dymMethod = new DynamicMethod("DoDeepClone", typeof(T), new Type[] { typeof(T) }, Assembly.GetExecutingAssembly().ManifestModule, true);  
         ILGenerator generator = dymMethod.GetILGenerator();  
         LocalBuilder cloneVariable = generator.DeclareLocal(myObject.GetType());  
         ConstructorInfo cInfo = myObject.GetType().GetConstructor(Type.EmptyTypes);  
         generator.Emit(OpCodes.Newobj, cInfo);  
         generator.Emit(OpCodes.Stloc, cloneVariable);  
         foreach (FieldInfo field in typeof(T).GetFields(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public))  
         {  
           if (_globalCloneType == CloneType.DeepCloning)  
           {  
             if (field.FieldType.IsValueType || field.FieldType == typeof(string))  
             {  
               generator.Emit(OpCodes.Ldloc, cloneVariable);  
               generator.Emit(OpCodes.Ldarg_0);  
               generator.Emit(OpCodes.Ldfld, field);  
               generator.Emit(OpCodes.Stfld, field);  
             }  
             else if (field.FieldType.IsClass)  
             {  
               CopyReferenceType(generator, cloneVariable, field);  
             }  
           }  
           else  
           {  
             switch (GetCloneTypeForField(field))  
             {  
               case CloneType.ShallowCloning:  
                 {  
                   generator.Emit(OpCodes.Ldloc, cloneVariable);  
                   generator.Emit(OpCodes.Ldarg_0);  
                   generator.Emit(OpCodes.Ldfld, field);  
                   generator.Emit(OpCodes.Stfld, field);  
                   break;  
                 }  
               case CloneType.DeepCloning:  
                 {  
                   if (field.FieldType.IsValueType || field.FieldType == typeof(string))  
                   {  
                     generator.Emit(OpCodes.Ldloc, cloneVariable);  
                     generator.Emit(OpCodes.Ldarg_0);  
                     generator.Emit(OpCodes.Ldfld, field);   
                     generator.Emit(OpCodes.Stfld, field);  
                   }  
                   else if (field.FieldType.IsClass)  
                     CopyReferenceType(generator, cloneVariable, field);  
                   break;  
                 }  
               case CloneType.None:  
                 {  
                   // Do nothing here, field is not cloned.  
                 }  
                 break;  
             }  
           }  
         }  
         generator.Emit(OpCodes.Ldloc_0);  
         generator.Emit(OpCodes.Ret);  
         myExec = dymMethod.CreateDelegate(typeof(Func<T, T>));  
         _cachedILDeep.Add(typeof(T), myExec);  
       }  
       return ((Func<T, T>)myExec)(myObject);  
     }  
     /// <summary>  
     /// Helper method to clone a reference type.  
     /// This method clones IList and IEnumerables and other reference types (classes)  
     /// Arrays are not yet supported (ex. string[])  
     /// </summary>  
     /// <param name="generator">IL generator to emit code to.</param>  
     /// <param name="cloneVar">Local store wheren the clone object is located. (or child of)</param>  
     /// <param name="field">Field definition of the reference type to clone.</param>  
     private static void CopyReferenceType(ILGenerator generator, LocalBuilder cloneVar, FieldInfo field)  
     {  
       if (field.FieldType.IsSubclassOf(typeof(Delegate)))  
       {  
         return;  
       }  
       LocalBuilder lbTempVar = generator.DeclareLocal(field.FieldType);  
       if (field.FieldType.GetInterface("IEnumerable") != null && field.FieldType.GetInterface("IList") != null)  
       {  
         if (field.FieldType.IsGenericType)  
         {  
           Type argumentType = field.FieldType.GetGenericArguments()[0];  
           Type genericTypeEnum = Type.GetType("System.Collections.Generic.IEnumerable`1[" + argumentType.FullName + "]");  
           ConstructorInfo ci = field.FieldType.GetConstructor(new Type[] { genericTypeEnum });  
           if (ci != null && GetCloneTypeForField(field) == CloneType.ShallowCloning)  
           {  
             generator.Emit(OpCodes.Ldarg_0);  
             generator.Emit(OpCodes.Ldfld, field);  
             generator.Emit(OpCodes.Newobj, ci);  
             generator.Emit(OpCodes.Stloc, lbTempVar);  
             generator.Emit(OpCodes.Ldloc, cloneVar);  
             generator.Emit(OpCodes.Ldloc, lbTempVar);  
             generator.Emit(OpCodes.Stfld, field);  
           }  
           else  
           {  
             ci = field.FieldType.GetConstructor(Type.EmptyTypes);  
             if (ci != null)  
             {  
               generator.Emit(OpCodes.Newobj, ci);  
               generator.Emit(OpCodes.Stloc, lbTempVar);  
               generator.Emit(OpCodes.Ldloc, cloneVar);  
               generator.Emit(OpCodes.Ldloc, lbTempVar);  
               generator.Emit(OpCodes.Stfld, field);  
               CloneList(generator, field, argumentType, lbTempVar);  
             }  
           }  
         }  
       }  
       else  
       {  
         ConstructorInfo cInfo = field.FieldType.GetConstructor(new Type[] { });  
         generator.Emit(OpCodes.Newobj, cInfo);  
         generator.Emit(OpCodes.Stloc, lbTempVar);  
         generator.Emit(OpCodes.Ldloc, cloneVar);  
         generator.Emit(OpCodes.Ldloc, lbTempVar);  
         generator.Emit(OpCodes.Stfld, field);  
         foreach (FieldInfo fi in field.FieldType.GetFields(System.Reflection.BindingFlags.Instance  
           | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public))  
         {  
           if (fi.FieldType.IsValueType || fi.FieldType == typeof(string))  
           {  
             generator.Emit(OpCodes.Ldloc_1);  
             generator.Emit(OpCodes.Ldarg_0);  
             generator.Emit(OpCodes.Ldfld, field);  
             generator.Emit(OpCodes.Ldfld, fi);  
             generator.Emit(OpCodes.Stfld, fi);  
           }  
         }  
       }  
     }  
     /// <summary>  
     /// Makes a deep copy of an IList of IEnumerable  
     /// Creating new objects of the list and containing objects. (using default constructor)  
     /// And by invoking the deepclone method defined above. (recursive)  
     /// </summary>  
     /// <param name="generator">IL generator to emit code to.</param>  
     /// <param name="listField">Field definition of the reference type of the list to clone.</param>  
     /// <param name="typeToClone">Base-type to clone (argument of List<T></param>  
     /// <param name="cloneVar">Local store wheren the clone object is located. (or child of)</param>  
     private static void CloneList(ILGenerator generator, FieldInfo listField, Type typeToClone, LocalBuilder cloneVar)  
     {  
       Type genIEnumeratorTyp = Type.GetType("System.Collections.Generic.IEnumerator`1[" + typeToClone.FullName + "]");  
       Type genIEnumeratorTypLocal = Type.GetType(listField.FieldType.Namespace + "." + listField.FieldType.Name + "+Enumerator[[" + typeToClone.FullName + "]]");  
       LocalBuilder lbEnumObject = generator.DeclareLocal(genIEnumeratorTyp);  
       LocalBuilder lbCheckStatement = generator.DeclareLocal(typeof(bool));  
       Label checkOfWhile = generator.DefineLabel();  
       Label startOfWhile = generator.DefineLabel();  
       MethodInfo miEnumerator = listField.FieldType.GetMethod("GetEnumerator");  
       generator.Emit(OpCodes.Ldarg_0);  
       generator.Emit(OpCodes.Ldfld, listField);  
       generator.Emit(OpCodes.Callvirt, miEnumerator);  
       if (genIEnumeratorTypLocal != null)  
       {  
         generator.Emit(OpCodes.Box, genIEnumeratorTypLocal);  
       }  
       generator.Emit(OpCodes.Stloc, lbEnumObject);  
       generator.Emit(OpCodes.Br_S, checkOfWhile);  
       generator.MarkLabel(startOfWhile);  
       generator.Emit(OpCodes.Nop);  
       generator.Emit(OpCodes.Ldloc, cloneVar);  
       generator.Emit(OpCodes.Ldloc, lbEnumObject);  
       MethodInfo miCurrent = genIEnumeratorTyp.GetProperty("Current").GetGetMethod();  
       generator.Emit(OpCodes.Callvirt, miCurrent);  
       Type cloneHelper = Type.GetType(typeof(CloneHelper<T>).Namespace + "." + typeof(CloneHelper<T>).Name + "[" + miCurrent.ReturnType.FullName + "]");  
       MethodInfo miDeepClone = cloneHelper.GetMethod("CloneObjectWithILDeep", BindingFlags.Static | BindingFlags.NonPublic);  
       generator.Emit(OpCodes.Call, miDeepClone);  
       MethodInfo miAdd = listField.FieldType.GetMethod("Add");  
       generator.Emit(OpCodes.Callvirt, miAdd);  
       generator.Emit(OpCodes.Nop);  
       generator.MarkLabel(checkOfWhile);  
       generator.Emit(OpCodes.Nop);  
       generator.Emit(OpCodes.Ldloc, lbEnumObject);  
       MethodInfo miMoveNext = typeof(IEnumerator).GetMethod("MoveNext");  
       generator.Emit(OpCodes.Callvirt, miMoveNext);  
       generator.Emit(OpCodes.Stloc, lbCheckStatement);  
       generator.Emit(OpCodes.Ldloc, lbCheckStatement);  
       generator.Emit(OpCodes.Brtrue_S, startOfWhile);  
     }  
     /// <summary>  
     /// Returns the type of cloning to apply on a certain field when in custom mode.  
     /// Otherwise the main cloning method is returned.  
     /// You can invoke custom mode by invoking the method Clone(T obj)  
     /// </summary>  
     /// <param name="field">Field to examine</param>  
     /// <returns>Type of cloning to use for this field.</returns>  
     private static CloneType GetCloneTypeForField(FieldInfo field)  
     {  
       object[] attributes = field.GetCustomAttributes(typeof(CloneAttribute), true);  
       if (attributes == null || attributes.Length == 0)  
       {  
         if (!_globalCloneType.HasValue)  
           return CloneType.ShallowCloning;  
         else  
           return _globalCloneType.Value;  
       }  
       return (attributes[0] as CloneAttribute).CloneType;  
     }  
     #endregion  
   }  
 }