Duck Copy in C#






4.75/5 (8 votes)
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