# Deep Clone in C#
Here's a (hopefully) simple C# implementation of making deep object clones in .NET.
Delegates are shallow copied (any subscribers will get clone's events too). That may not be what you want!
Deep clone uses the [[Trampoline]] to keep the stack height consistent, [[Getting all Inherited Fields Using Reflection]] to get the inherited fields and the [[Reference Equality Comparer]] for the `AlreadyCloned` dictionary.
Keep in mind that it's important to deep clone structs, as they may contain references to reference types (which need to be deep cloned)! This is why the `IsPrimitiveValueOrDelegate` does not include an `IsValueType` check.
```csharp
public static class DeepClone
{
private class DeepCloneContext
{
public readonly Dictionary<object, object> AlreadyCloned =
new(new ReferenceEqualityComparer());
public Trampoline<object?> DeepClone(
object? source, Func<object?, Trampoline<object?>> continuation)
{
if (source == null)
{
return Trampoline.Continue(() => continuation(null));
}
var t = source.GetType();
if (IsPrimitiveValueOrDelegate(t) || source is MemberInfo)
{
return Trampoline.Continue(() => continuation(source));
}
if (AlreadyCloned.TryGetValue(source, out var previous))
{
return Trampoline.Continue(() => continuation(previous));
}
//Things like Dictionaries and HashSets cannot be cloned because they rely on hash codes,
//which can change for clones (if they are tied to a memory address for instance).
//These types should all implement ISerializable, so we can use that to clone them.
if (source is ISerializable serializable and IDeserializationCallback &&
t.GetConstructor(
BindingFlags.Instance | BindingFlags.NonPublic,
null,
new[] {typeof(SerializationInfo), typeof(StreamingContext) },
null) is { } constructor)
{
var formatterConverter = new FormatterConverter();
var info = new SerializationInfo(t, formatterConverter);
var infoClone = new SerializationInfo(t, formatterConverter);
var context = new StreamingContext();
serializable.GetObjectData(info, context);
var clone = constructor.Invoke(new object[] {infoClone, context});
AlreadyCloned[source] = clone;
return Trampoline.Continue(() => DeepCloneSerializationInfo(
info, infoClone, () =>
{
((IDeserializationCallback)clone).OnDeserialization(this);
return continuation(clone);
}));
}
var shallow = source.ShallowClone();
AlreadyCloned[source] = shallow;
if (t.IsArray)
{
var et = t.GetElementType()!;
if (IsPrimitiveValueOrDelegate(et))
{
return Trampoline.Continue(() => continuation(shallow));
}
var castedSource = (Array)source;
if (castedSource.Rank > 1)
{
return Trampoline.Continue(() =>
DeepCloneMultiDimensionalArray(castedSource, (Array)shallow, continuation));
}
return Trampoline.Continue(() =>
DeepCloneSimpleArray(castedSource, (Array)shallow, 0, continuation));
}
var fields = GetFields(t);
return Trampoline.Continue(() =>
DeepCloneFields(source, shallow, fields, 0, continuation));
}
Trampoline<object?> DeepCloneSerializationInfo(
SerializationInfo source,
SerializationInfo destination,
Func<Trampoline<object?>> continuation)
{
var enumerator = source.GetEnumerator();
Trampoline<object?> Inner()
{
if (enumerator.MoveNext())
{
return DeepClone(enumerator.Value, clonedValue =>
{
destination.AddValue(enumerator.Name, clonedValue, enumerator.ObjectType);
return Inner();
});
}
return continuation();
}
return Inner();
}
private Trampoline<object?> DeepCloneFields(
object source,
object destination,
IReadOnlyList<FieldInfo> fields,
int index,
Func<object?, Trampoline<object?>> continuation)
{
if (index >= fields.Count)
{
return Trampoline.Continue(() => continuation(destination));
}
var currentField = fields[index];
var sourceValue = currentField.GetValue(source);
return DeepClone(sourceValue, sourceValueDeepClone =>
{
currentField.SetValue(destination, sourceValueDeepClone);
return DeepCloneFields(source, destination, fields, index + 1, continuation);
});
}
private Trampoline<object?> DeepCloneSimpleArray(
Array source,
Array destination,
int index,
Func<object?, Trampoline<object?>> continuation)
{
var endReached = index >= source.Length;
if (endReached)
{
return Trampoline.Continue(() => continuation(destination));
}
var currentSourceElement = source.GetValue(index);
return DeepClone(currentSourceElement, sourceElementDeepClone =>
{
destination.SetValue(sourceElementDeepClone, index);
return DeepCloneSimpleArray(source, destination, index + 1, continuation);
});
}
private Trampoline<object?> DeepCloneMultiDimensionalArray(
Array source,
Array destination,
Func<object?, Trampoline<object?>> continuation)
{
var sourceRank = source.Rank;
int[] index = new int[source.Rank];
void IncrementIndex()
{
for (var i = 0; i < sourceRank; i++)
{
var dimensionLength = source.GetLength(i);
var next = index[i] + 1;
if (next < dimensionLength || sourceRank - 1 == i)
{
index[i] = next;
break;
}
index[i] = 0;
}
}
Trampoline<object?> Inner()
{
var endReached = index.Last() >= source.GetLength(sourceRank - 1);
if (endReached)
{
return Trampoline.Continue(() => continuation(destination));
}
var currentSourceElement = source.GetValue(index);
return DeepClone(currentSourceElement, sourceElementDeepClone =>
{
destination.SetValue(sourceElementDeepClone, index);
IncrementIndex();
return Inner();
});
}
return Inner();
}
}
private static readonly MethodInfo MemberwiseCloneMethodInfo =
typeof(object).GetMethod(
"MemberwiseClone",
BindingFlags.NonPublic | BindingFlags.Instance)!;
private static T ShallowClone<T>(this T target) =>
(T)MemberwiseCloneMethodInfo.Invoke(target, null)!;
private static bool IsPrimitiveValueOrDelegate(Type t) =>
t.IsPrimitive ||
t == typeof(string) ||
typeof(Delegate).IsAssignableFrom(t);
[return: NotNullIfNotNull("target")]
public static T? Make<T>(T? target, params object[] valuesToReturnShallow)
{
var context = new DeepCloneContext();
foreach (var shallowValue in valuesToReturnShallow)
{
context.AlreadyCloned[shallowValue] = shallowValue;
}
var result = context.DeepClone(target, Trampoline.Done).Run();
return (T?)result;
}
}
```