Click here to Skip to main content
Click here to Skip to main content

Duck Copy in C#

, 24 Nov 2012
Rate this:
Please Sign up or sign in to vote.
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Pascal Ganaye
Software Developer (Senior)
United Kingdom United Kingdom
I am a French programmer.
These days I spend most of my time with the .NET framework, JavaScript and html.

Comments and Discussions

| Advertise | Privacy | Mobile
Web04 | 2.8.140721.1 | Last Updated 24 Nov 2012
Article Copyright 2012 by Pascal Ganaye
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid