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, 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, 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