1

I need to make a deep copy of an object with references to other objects that have references to yet another layer of objects, and so on.

I do not have direct access to the object, so I cannot serialize it. Further, I need to copy fields marked as private, protected, or internal (from an assembly other than the one holding the object).

I've read many, many answers on how to deepcopy in C#, and they all come back to serializing or implementing clone() or similar. Unfortunately, it seems to be very difficult to find an answer on how to deepcopy when these methods aren't available.

Can anyone tell me how, in C#, I can deepcopy an object without access/ability to modify the object's source code? Is this even possible?

Thank you in advance.

ProgrammingLlama
  • 36,677
  • 7
  • 67
  • 86
Beep13
  • 11
  • 2
  • 1
    Reflection will let you do it, but you'd want some boundaries. For example, what happens if you do a deep copy of the first node in a graph with cycles. Do you want to copy both fields and Read/Write properties. If you do this, you probably want to build some copier objects and then store them in a dictionary (with Type as an index). It seems like a lot of work. I'm curious why you want to do this? – Flydog57 Aug 20 '21 at 02:55
  • I assume you've seen the questions I've selected as [duplicate](https://stackoverflow.com/questions/78536/deep-cloning-objects)... you may want to [edit] this question to list *all* suggestions from there with brief reasons why it does not work for you. I don't believe there would be any other options (as those should be already added to the overall "deep-copy" one) but at least it would be more clear what your actual restrictions are. I.e. reflection that @Flydog57 suggested is already covered in the duplicate but this question does not explain why it did not work for your case. – Alexei Levenkov Aug 20 '21 at 04:30
  • _and so on._ And here's the catch. – TaW Aug 20 '21 at 04:58

1 Answers1

1

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.

Corey
  • 15,524
  • 2
  • 35
  • 68