Click here to Skip to main content
15,867,771 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.7K   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 
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.