If you think a bit, you will probably be able to see, that
IClonable
won't help you to implement deep cloning, because the library type implementing this interface implement, well, just the shallow cloning.
The problem is the recursion assumed by the idea of shallow cloning. Ideally, you would need to clone all objects according to the interface implementation and try to clone all references instead of copying them, but how? A reference of some other object may implement
IClonable
, but that implementation may or may not be deep. Actually, the interface implies that the implementation is shallow.
How can you work around this problem? One of approaches could be this: implement the parallel mechanism. Let's say, you want to introduce another interface assuming that the objects implementing it would implement the deep cloning:
public interface IDeepCloneable {
IDeepCloneable DeepClone();
}
}
Still, you cannot guarantee that the types implementing this interface will actually implement deep cloning, but you can require that they do it and assume that they actually do. Under this assumption, you can even implement "universal" deep cloning algorithm which you can pass to the derived classes of some base "deep clonable" class and use the required recursion. Then it could look like this:
using System.Reflection;
public class DeepClonable : IDeepCloneable {
IDeepCloneable IDeepCloneable.DeepClone() {
Type deepClonableType = typeof(IDeepCloneable);
FieldInfo[] fields = this.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
IDeepCloneable clone = new DeepClonable();
foreach (FieldInfo field in fields) {
Type fieldType = field.FieldType;
object fieldValue = field.GetValue(this);
if (fieldValue == null) {
field.SetValue(clone, fieldValue);
continue;
}
if (deepClonableType.IsAssignableFrom(fieldType)) {
IDeepCloneable fieldClone = ((IDeepCloneable)fieldValue).DeepClone();
field.SetValue(clone, fieldClone);
} else
field.SetValue(clone, fieldValue);
}
return clone;
}
}
[EDIT #1]
Alternatively/additionally, you can use
ICloneable
in combination with the above, to provide a
fall-back mechanism using
ICloneable
objects not implementing
ICloneable
for shallow cloning:
public class DeepClonable : IDeepCloneable {
IDeepCloneable IDeepCloneable.DeepClone() {
Type deepClonableType = typeof(IDeepCloneable);
Type clonableType = typeof(ICloneable);
FieldInfo[] fields = this.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
IDeepCloneable clone = new DeepClonable();
foreach (FieldInfo field in fields) {
Type fieldType = field.FieldType;
object fieldValue = field.GetValue(this);
if (fieldValue == null) {
field.SetValue(clone, fieldValue);
continue;
}
if (deepClonableType.IsAssignableFrom(fieldType)) {
IDeepCloneable fieldClone = ((IDeepCloneable)fieldValue).DeepClone();
field.SetValue(clone, fieldClone);
} else if (clonableType.IsAssignableFrom(fieldType)) {
object fieldClone = ((ICloneable)fieldValue).Clone();
field.SetValue(clone, fieldClone);
} else
field.SetValue(clone, fieldValue);
}
return clone;
}
}
[END EDIT #1]
Note that in this "universal" implementation, we need to assume that that the type implementing the interface also support the constructor of some "universal" signature, say, parameterless constructor, as shown in the code. However, we don't need to assume it's the case for other
IDeepCloneable
implementations we may face during recursion, because other implementations can be different, we only use the fact that the interface is implemented. Also note that reflection used in the code above is inherently slow, so you may want to create faster
ad-hoc implementation. Also note that you can implement any interface not only by classes by also by structures (
struct
). For example:
public struct Child {
public int IntegerValue;
public string Name;
public Child(int integerValue, string name) { IntegerValue = integerValue; Name = name; }
}
public struct Parent : IDeepCloneable {
public Parent(bool dummy) {
childNodes = new System.Collections.Generic.List<IDeepCloneable>();
children = new System.Collections.Generic.List<Child>();
}
public System.Collections.Generic.List<IDeepCloneable> ChildNodes { get { return this.childNodes; } }
public System.Collections.Generic.List<Child> Children { get { return this.children; } }
System.Collections.Generic.List<IDeepCloneable> childNodes;
System.Collections.Generic.List<Child> children;
IDeepCloneable IDeepCloneable.DeepClone() {
Parent clone = new Parent(false);
foreach (Child child in this.children)
clone.children.Add(new Child(child.IntegerValue, child.Name));
foreach (IDeepCloneable childNode in this.childNodes) {
IDeepCloneable childClone = childNode.DeepClone();
clone.ChildNodes.Add(childClone);
}
return clone;
}
}
I have intentionally more difficult case of
struct
which does not allow parameterless constructor, so I introduce a constructor with a dummy parameter, just for illustration. I also demonstrated "logical" implicit shallow clone for the
struct Child
.
One more delicate detail: you need to understand that the class
System.String
, even though it is a reference object, actually does not require cloning at all. It logically behaves like a value type, due to its "interning" feature and the use of "intern pool". Please see:
http://msdn.microsoft.com/en-us/library/system.string.intern.aspx[
^],
http://msdn.microsoft.com/en-us/library/system.string.isinterned.aspx[
^] (no need to actually use any of these properties).
Explanation of intern pool:
http://broadcast.oreilly.com/2010/08/understanding-c-stringintern-m.html[
^].
See also:
http://en.wikipedia.org/wiki/Recursion[
^],
http://en.wikipedia.org/wiki/Recursion_%28computer_science%29[
^].
[EDIT #2]
I forgot to explain that you really need to implement cloning of the list in a specialized way, because lists don't implement
ICloneable
. As it have been your major concern, it will take a separate answer.
[END EDIT #2]
—SA