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