Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Duck Copy in C#

0.00/5 (No votes)
24 Nov 2012 1  
A super simple and fast methods to copy objects

Introduction

When you want to copy an object in .NET you often have to write a function that copies all the fields or properties one by one. 
For example: 

  private static void CopyPerson(Person person1, Person person2)
  {
     person2.FirstName = person1.FirstName;
     person2.LastName = person1.LastName;
     person2.DateOfBirth = person1.DateOfBirth;
     person2.SomeOtherField = person1.SomeOtherField;
     ...
  }         

This article present a couple of functions that automatically emit this sort of functions for you at run time. 

Background 

A popular maxime says: "When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck."  

This little routine let you copy ducks and other animals hence the name 'DuckCopy'.   

Using the code 

Copying an object is a 2 phase process.
First you generate the copy dynamic method. 

Action<Person, IPerson> dynamicMethod1 = DuckCopyProperties<Person, IPerson>();      

Generating this method will take a few milliseconds, so you might want to save the result to a variable. 

Then you use the method to copy the objects. 

dynamicMethod1(person1, client1);  

DuckCopyProperties 

public static Action<TFrom, TTo> DuckCopyProperties<TFrom, TTo>()
{
    DynamicMethod meth = new DynamicMethod(
        "DuckCopyProperties",
        null,
        new Type[] { typeof(TFrom), typeof(TTo) },
        MethodInfo.GetCurrentMethod().DeclaringType,  // associate with a type
        true);
    ILGenerator il = meth.GetILGenerator();

    foreach (PropertyInfo piFrom in typeof(TFrom).GetProperties(BindingFlags.Public | BindingFlags.Instance))
    {
        if (piFrom.CanRead && piFrom.GetIndexParameters().Length == 0)
        {
            PropertyInfo piTo = typeof(TTo).GetProperty(piFrom.Name, BindingFlags.Public | BindingFlags.Instance);
            if (piTo != null
                && piTo.CanWrite
                && piTo.PropertyType.IsAssignableFrom(piFrom.PropertyType)
                && piTo.GetIndexParameters().Length == 0)
            {
                il.Emit(OpCodes.Ldarg_1);
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Callvirt, piFrom.GetGetMethod());
                il.Emit(OpCodes.Callvirt, piTo.GetSetMethod());
            }
        }
    }
    il.Emit(OpCodes.Ret);

    Delegate dlg = meth.CreateDelegate(typeof(Action<TFrom, TTo>));
    return (Action<TFrom, TTo>)dlg;
}

Copying fields 

Generally you want to copy the public Properties of the objects.
Sometime though you might want to copy the private fields. This is particularly useful for cloning. 

Action<Person, Person> dynamicMethod1 = DuckCopyFields<Person, Person>();         

DuckCopyFields 

public static Action<TFrom, TTo> DuckCopyFields<TFrom, TTo>()
{
    DynamicMethod meth = new DynamicMethod(
        "DuckCopyFields",
        null,
        new Type[] { typeof(TFrom), typeof(TTo) },
        MethodInfo.GetCurrentMethod().DeclaringType,                  // associate with a type
        true);
    ILGenerator il = meth.GetILGenerator();

    foreach (FieldInfo fiFrom in typeof(TFrom).GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
    {
        FieldInfo fiTo = typeof(TTo).GetField(fiFrom.Name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
        if (fiTo != null
            && fiTo.FieldType.IsAssignableFrom(fiFrom.FieldType))
        {
            il.Emit(OpCodes.Ldarg_1);
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Ldfld, fiFrom);
            il.Emit(OpCodes.Stfld, fiTo);
        }

    }
    il.Emit(OpCodes.Ret);

    Delegate dlg = meth.CreateDelegate(typeof(Action<TFrom, TTo>));
    return (Action<TFrom, TTo>)dlg;
}   

Point of Interest 

I was not satisfied with the speed of this until Alan N fixed it for me

The generated code is now pretty much as fast as code you would write yourself. 

IL_0000: nop        
IL_0001: ldarg.1    
IL_0002: ldarg.0    
IL_0003: callvirt   System.String get_FirstName()/DuckCopy.SpeedTests.Program+Person
IL_0008: callvirt   Void set_FirstName(System.String)/DuckCopy.SpeedTests.Program+Person
IL_000d: nop        
IL_000e: ldarg.1    
IL_000f: ldarg.0    
IL_0010: callvirt   System.String get_LastName()/DuckCopy.SpeedTests.Program+Person
IL_0015: callvirt   Void set_LastName(System.String)/DuckCopy.SpeedTests.Program+Person
IL_001a: nop        
IL_001b: ldarg.1    
IL_001c: ldarg.0    
IL_001d: callvirt   System.DateTime get_DateOfBirth()/DuckCopy.SpeedTests.Program+Person
IL_0022: callvirt   Void set_DateOfBirth(System.DateTime)/DuckCopy.SpeedTests.Program+Person
IL_0027: nop        
IL_0028: ldarg.1    
IL_0029: ldarg.0    
IL_002a: callvirt   Int32 get_SomeNumber()/DuckCopy.SpeedTests.Program+Person
IL_002f: callvirt   Void set_SomeNumber(Int32)/DuckCopy.SpeedTests.Program+Person
IL_0034: nop        
IL_0035: ret     

History

17th November 2012: First release 

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here