Click here to Skip to main content
15,878,852 members
Articles / Programming Languages / C#
Tip/Trick

Duck Copy in C#

Rate me:
Please Sign up or sign in to vote.
4.75/5 (8 votes)
24 Nov 2012CPOL1 min read 18.8K   29   3
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: 

C#
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. 

C++
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. 

C++
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. 

C++
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. 

MSIL
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
France France
I am a French programmer.
These days I spend most of my time with the .NET framework, JavaScript and html.

Comments and Discussions

 
SuggestionConstrain to class types Pin
Daniele Rota Nodari27-Nov-12 1:07
Daniele Rota Nodari27-Nov-12 1:07 
QuestionOne small tweak Pin
Alan N17-Nov-12 8:12
Alan N17-Nov-12 8:12 
Thanks for the credit in the article and on StackOverflow. This must be my 5 minutes of fame!

I've noticed that your code uses typeof(Program) in the DynamicMethod constructor and of course it should be typeof(whatever class this method is declared within).

To pick up the actual type at run time for both static and instance classes you could use System.Reflection.MethodInfo.GetCurrentMethod().DeclaringType.

Alan.
AnswerRe: One small tweak Pin
Pascal Ganaye18-Nov-12 12:57
Pascal Ganaye18-Nov-12 12:57 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.