Introduction
In my last project, I had to perform deep cloning to backup/restore my business entities (in the IEditableObject interface implementation). I thought this code might be useful to others. Its current version can clone only the following types: primitive types, enums, value types, strings, classes, arrays (System.Array
descendants), lists (IList
and List<T>
descendants). It's easy to add support for other types as well (and you'll see it below).
Background
Before starting with this project, I've read the following two articles: "C# Object Clone Wars" and "Deep Clone of a business object: the quick and dirty way". After that, I made my choice: reflection. Serialization approach was tempting, of course, but I didn't want to have any limitations (particularly in Silverlight).
Using the Code
The main job is done by the static DeepCloner
class. It has two methods - static void DeepCopyTo<T>
and static T CreateDeepClone<T>
, each with several overloads. That is how I am using them from my BaseEntity
class:
#region IEditableObject Members
public virtual void BeginEdit()
{
_cachedEntity = DeepCloner.CreateDeepClone<BaseEntity>(this) as BaseEntity;
}
public virtual void CancelEdit()
{
DeepCloner.DeepCopyTo<BaseEntity>(_cachedEntity, this);
}
public virtual void EndEdit()
{
_cachedEntity = null;
}
#endregion
In the above sample code, I used the shortest overloads for both methods. The longest overloads accept 4 parameters more:
propertiesOnly
: If true
, only properties will participate in cloning (and so fields will not) publicOnly
: If true
, only public
members (fields/properties) will participate in cloning allowCopyConstructors
: If true
, copy-constructor will be used [instead of reflection] if it's found at the type being cloned nonWritableNullListsCanBeSkipped
: If true
, and if non-writable field/property of list/array type is null
at the target object, such field/property will be skipped (not cloned). Otherwise, a NullReferenceException
will be thrown ("Cannot clone items of a non-writable array/list field/property '...' because this field/property is null
at the target object")
Please note that a non-writable field/property is always skipped - if it's not an array/list (the latter being cloned item-by-item). To find out whether any fields/properties were skipped, you may examine the static SkippedMembers
property of the DeepCloner
class:
DeepCloner.SkippedMembers.Clear();
TestClass clone = DeepCloner.CreateDeepClone<TestClass>
(tc, false, true, true, true) as TestClass;
if (DeepCloner.SkippedMembers.Count > 0)
{
Console.WriteLine("Skipped members:");
foreach (DeepCloner.FieldOrProperty fop in DeepCloner.SkippedMembers)
{
Console.WriteLine("{0}, {1}, {2}", fop.ObjectType.FullName,
fop.MemberType, fop.Name);
}
}
(Please note that the SkippedMembers
property should be cleared manually before you perform cloning.)
Depending on your situation, you may or may not raise an error if DeepCloner.SkippedMembers.Count
is greater than zero.
Well, I think it may be useful to place here the "Summary" and "Remarks" sections of my XML comments to the DeepCloner
class:
Summary
Provides several overloaded methods for deep cloning of an arbitrary object.
Currently the following object types are supported:
- primitive types
- enum types
- value types
- classes (having default constructors!)
- arrays and lists [of the above mentioned types] (more exactly: Array descendants,
IList
descendants, List<T>
descendants).
See the 'Remarks' section for other limitations.
Cloning options:
- You can choose whether both fields and properties participate in cloning or just properties.
- You can also choose whether to clone
public
fields/properties only, or also private
and protected
ones (the latter option not compatible with Silverlight). - You can choose whether a copy-constructor should be used [instead of reflection] if it's found at the type being cloned; but you should be aware that there is no any guarantee that copy-constructor of an arbitrary type performs deep cloning!
All these options can be set when calling the CreateDeepClone<T>
/DeepCopyTo<T>
methods - either directly as method parameters or indirectly as members of the DeepClonerSettings
class instance passed to those methods via the 'settings' parameter.
Remarks
Limitations
- As already said in the Summary section, only the following
IEnumerable
descendants are currently supported:
Array
descendantsIList
descendantsList<T>
descendants
- Properties with missing getters not supported
- DPs not supported
- Fields/properties of nullable types not supported
- Indexed properties not handled
- Under Silverlight, you should set the '
publicOnly
' parameter (or the DeepClonerSettings.PublicOnly
property) to 'true
' - otherwise you will get the MethodAccessException
.
Peculiarities
Private
property setter is treated the same way as the missing setter - i.e. property is considered non-writable and so is not cloned; to change this logic, you need to change the DeepCloner.FieldOrProperty.IsWritable
getter implementation- Writable fields/properties of list/array types are cloned via creation of a new list/array with cloned items
- Non-writable fields/properties of list/array types are cloned via copying cloned items into the target field/property the latter being cleared of its items first; if such [non-writable] field/property [of list/array type] is
null
at the target object, one of the two things may happen:
- if the '
nonWritableNullListsCanBeSkipped
' parameter (or the DeepClonerSettings.NonWritableNullListsCanBeSkipped
property) is set to 'true
', the field/property will be simply skipped - Otherwise (i.e. when set to '
false
') a NullReferenceException
will be thrown ("Cannot clone items of a non-writable array/list field/property '...' because this field/property is null
at the target object")
- Any skipped (not cloned) field/property is added to the
DeepCloner.SkippedMembers
list; note that this list should be cleared manually before each cloning; to see why each field/property was skipped, you should examine the Output window contents.
Points of Interest
As already mentioned above, you can easily add support for new types (e.g. my nearest task will be to add support for Dictionaries). For this purpose, you just need to add one more InstanceCopier
to the DeepCloner
class code.
History