# 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; } } ```