Some types of objects just aren't suitable for being cloned, so right off the bat we know that there is never going to be a general method that works in all cases. First you need to know that the type can be cloned. It needs to have a default constructor for one thing. And actual data, not fetch-on-demand.
For a deep clone you have additional problems that aren't necessarily going to be obvious up front. How do you deal with a field that has a non-clonable value? How do you distinguish between properties that are just proxies over a hidden field vs the ones that perform a variety of operations on read or write? How do you deal with event
members? There are any number of common structures that will interfere with your cloning process.
Let's assume for the moment that the type you're cloning doesn't have those problems, and nor do any of the objects in the graph. You want to create a recursive cloning method that can handle the basics.
In that case you just need to recursively copy field values, ignoring properties completely. For each value you copy - including the first - check the type and if it's something you can copy by value (null
values, all value types including structs and some invariant types like string
) and recursively clone the rest.
Assuming that you don't run into an object that doesn't have a default constructor you might be OK to just use Activator.CreateInstance()
to start each object clone.
You can get a naive implementation done in about 25 lines of code. Here's a terrible implementation that is for demonstration purposes only and should never be used in production:
static T DeepClone<T>(T instance)
where T : class, new()
{
var visited = new Dictionary<object, object>();
object DoClone(object inst)
{
if (inst is null)
return null;
if (visited.TryGetValue(inst, out var prev))
return prev;
var type = inst.GetType();
var result = visited[inst] = Activator.CreateInstance(type);
var fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
foreach (var field in fields)
{
var value = field.GetValue(inst);
if (field.FieldType.IsValueType || field.FieldType == typeof(string))
field.SetValue(result, value);
else
field.SetValue(result, DoClone(value));
}
return result;
}
return (T)DoClone(instance);
}
Standard disclaimers: it's not pretty, isn't well tested and is pretty much guaranteed to break on you at the most inconvenient moment. Does not handle arrays or collections of any sort. May be hazardous to children or small animals. Do not operate while intoxicated.