Friday, June 20, 2008

Object Deep Cloning using IL in C# - version 1.1

Howdy,

Meanwhile i've been busy with the cloning stuff in IL, and on demand of mr. Sam
I've adapted the code so that it can also clone LINQ entities (EntitySet etc)

The code is not yet in a final stage but it is becoming more and more stable as I refine the code, so, if you have comments or suggestions, please let me know...

I've added in this version:


  • Deep cloning until 1 level (more level's, i'm not yet sure)

  • Use of attributes for defining which field should be deepcloned, shallowcloned or not be cloned at all. You can change the 'CloneAttribute' by a system attribute if you like, when you for example transfer stuff through WCF or something like that. Just map you own attribute to the cloning attribute in the function -> 'GetCloneTypeForField' and it's normally OK.

  • The cloning code is put together in a CloneHelper static generic class, so that you can use it in combination with the ICloneable interface for example, or just in some framework as possible cloning mechanism.



Now let's take a look at the code:

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[0as CloneAttribute).CloneType;
        
}

        
#endregion
    
}
}


If you have any remarks, please notify me, or when you can use this code, please notify me, you may use it as you whish :)

Regards,

F.

19 comments:

Joshua said...

How would one go about cloning a property with IL? I've been working with your code with Entity Objects but I still lack some of the skill with IL to clone over a property, and a couple of properties in an Entity Object are needed to attach an object back into a Data Context.

Whizzo said...

You could change the GetFields() by GetProperties(), but normally the fields should work. Because all internal fields of the class you clone will be filled in.

Be aware to check the properties for a set method if you want to clone property-based.

Joshua said...

Well looks like that works, I have done it with reflection and am now able to pass a large amount of entities over a WCF service connection without the errors you get for your object graph being to deep and message size. I have yet to do this with IL though.

I am currently writing an article about the method I used to share entity objects from a WCF server to a client without the client having a data context and I was wondering if I could reference your article in it.

Whizzo said...

Sure, you may reference my post.

Isn't reflection only, a little slow?

Ernest said...

Hi Whizzo,

I have a limited knowledge of the IL as welll :). I used your code and it works for me however I tried to be able to change your code to clone a object with multimple other objects inside and with multiple object levels deep.

What I have done is if it reaches the isClass then I check the the field if it is a Property, if so I called your clone method inside the existing clode tread...?? is this advisable?

another thing is how would I go about cloning arrays(jagged or multi dimensinal) of a custom object?

M said...

Hi. Do you have a zip with your test program and the Clone code all in one. It seems as though as something's missing from the source posted online.

Thanks !

Anonymous said...

First of all... Thanks for the post... very potential time saver...

I Ran into a cloning of arrays issue... meaning these are shallow by default, if i try to deepclone them it fails... I think you mentioned this some place so no biggie...

But i wanted to post what i ended up with so far...

Instead of keeping all things in IL, since a simple array would be pritty basic, i figured that implementing a generic method for this and then using this would be easyer...

In the "CopyReferenceType" method, ive added the folowing part:

if ( field.FieldType.IsGenericType )
{...}
else if ( field.FieldType.IsArray )
{
Type containingType = field.FieldType.GetElementType();
MethodInfo methodInfo = typeof( Helper ).GetMethod( "CloneArray" );
MethodInfo genericMethodInfo = methodInfo.MakeGenericMethod( new[] { containingType } );

LocalBuilder lbHelper = generator.DeclareLocal( typeof( Helper ) );
ConstructorInfo ci = typeof( Helper ).GetConstructor( Type.EmptyTypes );
if ( ci != null )
{
generator.Emit( OpCodes.Newobj, ci );
generator.Emit( OpCodes.Stloc, lbHelper );
generator.Emit( OpCodes.Ldloc, builder );
generator.Emit( OpCodes.Ldloc, lbHelper );

generator.Emit( OpCodes.Ldarg_0 );
generator.Emit( OpCodes.Ldfld, field );
generator.Emit( OpCodes.Call, genericMethodInfo );
generator.Emit( OpCodes.Stfld, field );
}
}

And then ofc the helper class i create within this section...

internal class Helper
{
public T[] CloneArray<T>( T[] array )
{
int length = array.Length;
T[] newArray = new T[ length ];
for ( int i = 0; i < length; i++ )
{
Type type = array[ i ].GetType();
if ( type.IsValueType || type == typeof(string) )
newArray[ i ] = array[ i ];
else
newArray[ i ] = Cloner.DeepCopy( array[ i ] );
}
return newArray;
}
}

Now obviously this is not nessesary the best aproach as it is written now... becouse it creates a new helper for every time it clones an array, obviously reuse of the helper class would be better, but in the time i had i couln't get this to work...

Note that i have removed all the switches on "CloneType" ect. becouse i basicly just needed a pure deep clone...

Since im not all into your code yet i have a hard time seeing if it causes any implications... But since it seemed like there wasn't posted a solution to this part, i wanted to post this, if for nothing else then inspiration...

Cheers...

Anonymous said...

Great thing that it's so performant.

I have a problem tho, I need to clone "foreign" objects which often do not have any parameterless constructor, and that doesn't seem to work. :-/

As the state of an instance is completely determined by all of its instance fields, all we would need is just an instance.

It would be nice if there was an overload where I can hand in a new instance or a delegate which would create one (and that for all child types ... uuuh) ... or maybe the IL generator could spit out some reflection magic to create an empty instance beforehand.

Jens Melgaaard said...

27 maart, 2009 16:52 >>>

Everywhere where objects are created based on there default constructor... These blocks looks like this:

ConstructorInfo cInfo = myObject.GetType().GetConstructor(Type.EmptyTypes);
generator.Emit(OpCodes.Newobj, cInfo);
generator.Emit(OpCodes.Stloc, cloneVariable);

Try this instead:

ConstructorInfo cInfo = myObject.GetType().GetConstructor( Type.EmptyTypes );
if(cInfo!= null)
{
generator.Emit(OpCodes.Newobj, cInfo);
}
else
{
MethodInfo getType = myObject.GetType().GetMethod("GetType");
generator.Emit( OpCodes.Ldarg_0 );
generator.Emit( OpCodes.Call, getType );
generator.Emit( OpCodes.Stloc_0 );
generator.Emit( OpCodes.Ldloc_0 );

MethodInfo methodInfo = typeof( FormatterServices ).GetMethod( "GetUninitializedObject" );
generator.Emit( OpCodes.Call, methodInfo );
generator.Emit( OpCodes.Castclass, myObject.GetType() );
}
generator.Emit( OpCodes.Stloc, cloneVariable );

It properly will reduce the performance a bit on those specific objects, but at least it should work...

/cheers

Anonymous said...

Oh, thank you very much, of course, we can emit an empty constructor in that case. I'll try that out.

Cheers, Michael

Michal said...
This comment has been removed by the author.
Michal said...
This comment has been removed by the author.
Michal said...

RegistratúrneZáznamyItem regZazn_najdeny = var_regZazn.First();
RegistratúrneZáznamyItem regZazn_kopia = DSS_DeepClone_Linq.CloneHelper.CloneObjectWithILDeep(regZazn_najdeny);

I can see, that kopia-instance object is clone with all properties as regZazn_najdeny, but when a I want to insert it to sharepoint by methods:
kontext_Aspreg.RegistratúrneZáznamy.
InsertOnSubmit(regZazn_kopia);
kontext_Aspreg.SubmitChanges();

empty row is inserted. Why ???? When I write to instance
Then I want to insert cloned object to sharepoint but modified - regZazn_kopia.Title - "xy", it is inserted all right, but Title from cloned regZazn_kopia item without editing not. :( It concerns all the properties of cloned object.

ozasco said...

Thanks a lot, your code is being very useful in my projects. Though, i'm using your first code version due to the problems with arrays. But i would like to contribute with something very interesting. I've made the code able to DownCast Objects, imagine this scenario, in the past i had to create this DownCast method in the superclass:


public class MyClass
{
public string MyProperty { get; set; }

public T DownCast() where T : MyClass, new()
{
T result = new T();
result.MyProperty = this.MyProperty;
return result;
}
}

public class SpecializedClass : MyClass
{
public string MyProperty2 { get; set; }
}


static void Main(string[] args)
{
MyClass myClass = new MyClass() { MyProperty = "I'm the superclass" };
// New DownCast method
//var specialized = myClass.DownCastWithIL();
// Old DownCast method
var specialized = myClass.DownCast();
specialized.MyProperty2 = "I'm the specialized class";
Console.WriteLine(specialized.MyProperty);
Console.WriteLine(specialized.MyProperty2);
Console.Read();
}

ozasco said...

Now i can use the code with IL, let's see what i had to change:


public static class SwallowCloning
{
private static Dictionary _cachedIL = new Dictionary();


public static T CloneObjectWithIL(this T objectToClone) where T : class
{
return CloneObjectWithIL(objectToClone: objectToClone, methodName: "DoClone");
}

public static TOut DownCastWithIL(this TIn objectToClone) where TOut : TIn
{
return CloneObjectWithIL(objectToClone: objectToClone, methodName: "DoCast");
}

private static TOut CloneObjectWithIL(TIn objectToClone, string methodName) where TOut : TIn
{
Type outType = Type.GetType(typeof(TOut).ToString());

Delegate myExec = null;
if (!_cachedIL.TryGetValue(typeof(TIn), out myExec))
{ // Create ILGenerator
DynamicMethod dymMethod = new DynamicMethod(methodName, typeof(TOut), new Type[] { typeof(TIn) }, true);
ConstructorInfo cInfo = outType.GetConstructor(new Type[] { });
ILGenerator generator = dymMethod.GetILGenerator();
LocalBuilder lbf = generator.DeclareLocal(typeof(TOut));
//lbf.SetLocalSymInfo("_temp");
generator.Emit(OpCodes.Newobj, cInfo);
generator.Emit(OpCodes.Stloc_0);
foreach (FieldInfo field in objectToClone.GetType().GetFields(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic))
{
// Load the new object on the eval stack... (currently 1 item on eval stack)
generator.Emit(OpCodes.Ldloc_0);
// Load initial object (parameter) (currently 2 items on eval stack)
generator.Emit(OpCodes.Ldarg_0); // Replace value by field value (still currently 2 items on eval stack)
generator.Emit(OpCodes.Ldfld, field); // Store the value of the top on the eval stack into the object underneath that value on the value stack.
// (0 items on eval stack)
generator.Emit(OpCodes.Stfld, field);
}
// Load new constructed obj on eval stack -> 1 item on stack
generator.Emit(OpCodes.Ldloc_0); // Return constructed object. --> 0 items on stack
generator.Emit(OpCodes.Ret);
myExec = dymMethod.CreateDelegate(typeof(Func));
_cachedIL.Add(typeof(TIn), myExec);
}
return ((Func)myExec)(objectToClone);
}
}

selcux said...

Good job Whizzo,
I want to use this approach on objects containing custom type fields. But when I tried I got null reference exception. Is there a way to make it recursively working?

bigfoot said...

Hi.
Good job on the code. I'm running into the same problem as selcux with NullReferenceException when cloning an object with more than one level of reference types. Took me a while to figure out the problem was multiple levels though. Do you have any updates which takes care of this?

I also had to do some changes when it comes to getting the IEnumerator and Enumerators to work. I got NullReferenceException because the type was not found. I worked it out by adding an additional [] around the generic argument type of the enumerator, as well as getting the QualifiedAssemblyName.

Type genIEnumeratorTyp = Type.GetType("System.Collections.Generic.IEnumerator`1[[" + typeToClone.AssemblyQualifiedName + "]]");

as opposed to
Type genIEnumeratorTyp = Type.GetType("System.Collections.Generic.IEnumerator`1[" + typeToClone.FullName + "]");

namefree said...

i think there is a bug,try this

----------------

public class H
{
[Tools.CloneAttribute(CloneType=Tools.CloneType.DeepCloning)]
private HashSet _set = new HashSet();

public HashSet Set
{
get { return _set; }
set { _set = value; }
}
}
----------------------
HashSet set = new HashSet();
for (int i = 0; i < 10; i++)
set.Add(i);

var h = new H() { Set = set };

H newObj = null;
newObj = Tools.CloneUtil.Clone(h, Tools.CloneType.DeepCloning);//h.Map();
//newObj.Set.Clear();
//Console.WriteLine(newObj.Set.Remove(1));

Console.WriteLine(h.Set.Count);
Console.WriteLine(Tools.Json.JsonTransfer.SerializeObject(newObj.Set));
Console.WriteLine(newObj.Set.Contains(0));

ianwright83 said...

This looks like a really interesting way to carry out cloning. Have you done any more work on this at all, as I notice this article is a few years old now.

I'm interested in deep cloning an object graph (where some objects may be sub-classed in an external assembly). Would I be correct in thinking this will fail in a number of places? I've put these together from the list of comments that I've read.

- Trying to go more than 1 level deep within the object graph.
- Trying to use objects that may be contained within another assembly.
- An object within the graph, if referenced from more than 1 place will referenced different instances after being cloned.