Click here to Skip to main content
15,885,141 members
Articles / Programming Languages / C#

Just Another Deep Cloning Implementation

Rate me:
Please Sign up or sign in to vote.
4.43/5 (5 votes)
10 May 2010CPOL4 min read 21.2K   188   12   3
Deep cloning in .NET via reflection

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:

C#
#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:

C#
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 descendants
    • IList descendants
    • List<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:
    1. if the 'nonWritableNullListsCanBeSkipped' parameter (or the DeepClonerSettings.NonWritableNullListsCanBeSkipped property) is set to 'true', the field/property will be simply skipped
    2. 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

  • 11 May 2010: v.1.0 added

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)
Russian Federation Russian Federation
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralSome exception handling is needed Pin
sip_slava4-Nov-10 12:23
sip_slava4-Nov-10 12:23 
QuestionWhy the as operator? Pin
Paulo Zemek11-May-10 3:11
mvaPaulo Zemek11-May-10 3:11 
AnswerYou're right! Pin
Yumashin Alex11-May-10 4:29
Yumashin Alex11-May-10 4:29 

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.