Situation: There is a generic class. In one of his methods, he takes as a parameter an object of a class with which the generic is initialized and stores it in an internal collection.

It is necessary to save in the internal collection a complete copy of the object passed in the parameters. (such saving the current state of the object)

Question: How to create a complete copy of an instance of an unknown at class compilation?

  • one
    ICloneable ? - VladD
  • one
    with the use of recursion and reflection, you can add a restriction on the generic parameter ICloneable - Grundy
  • one
    @Alexey, it's impossible to foresee everything - Grundy
  • one
    Plus, write scary bikes to circumvent circular references and manage to not go up the tree to the level of the entire application. - Monk
  • one
    And how do you imagine a copy of UIElementa in general? Most likely, you are doing something wrong. In some DDD, you can make copies only for aggregates (entities and child entities), for the rest, the copy has no meaning and value. - Monk

3 answers 3

Based on the discussion in the comments: you solve your problem in the wrong way. To implement undo / redo functionality, which you actually want to do in this way, you do not need to clone objects, much less UI objects.

For a start, why is there a problem with UI objects. For an arbitrary object, you do not know that there is a real property of this object (that is, a sub-object belonging to it), and that only a reference to another object. As a result, you have to clone all the properties. For example, Parent , Tag and DataContext .

Further, the properties of the object can be stored in unexpected places. For example, attached properties are stored in a place that is not accessible for reflection, having only a copy of the object, and not knowing anything else.

Since the root element is available through Parent / Children , and then all elements, you will have to clone the entire window. Since a VM is accessible through the DataContext , at the same time you will have to intercept it as well. And since the VM has links to the model, you will thus take the entire application with you . Including, by the way, the field in which the change history is stored.

Then, there is a set of objects that are not fundamentally cloned. Open file? Socket? Locked Monitor (aka lock )? Thrown exception? Any singleton from your program? Type ? Delegate? They all do not bend over.


What is really needed ? It is necessary to represent the state of the program or its parts using VM and model objects. If you want to save them, it makes sense to make these objects immutable, so that there is no risk that their state will change after you have saved a copy. (Understand immutable objects, they are easy to write so that when you change pieces, the source object is reused. If all pieces are immutable, they can be used anywhere without the need for cloning in the same way.)

Next, your View must strictly follow the MVVM pattern, and display the state of the VM, without ad-libbing and installing important pieces in the code behind. In this case, the state of your View will be completely determined by the state of the VM, which means that the need to remember the View will disappear.

At the same time, the only objects that you have to memorize (not clone, but memorize!) Is the current state.

  • Well, the question was rather theoretical. From a series of possible / impossible. Although yes, the reason is determined correctly - undo / redo with gadgets. I have already implemented in VM the functionality for saving / loading files correspondingly. implement undo \ redo is not a problem. Simply, undo / redo functionality was not originally provided. This is already editing so to speak ... in the process of thinking about the changes and additions that need to be made to the code and matured this question, matured because it seemed that it would be great once to implement a generic undo / redo class that can be reused later in other projects - Alexey
  • @Alexey: Well, theoretically it’s impossible to clone an arbitrary object. If ICloneable not appropriate (that is, not all objects are able / want to incline themselves as you need), then it is impossible to incline an object previously unknown. - VladD

There are several ways. The surest thing is to serialize the class and copy the bytes. But not the fastest, unfortunately. This is done like this:

 using System; using System.IO; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; /// <summary> /// Reference Article http://www.codeproject.com/KB/tips/SerializedObjectCloner.aspx /// Provides a method for performing a deep copy of an object. /// Binary Serialization is used to perform the copy. /// </summary> public static class ObjectCopier { /// <summary> /// Perform a deep Copy of the object. /// </summary> /// <typeparam name="T">The type of object being copied.</typeparam> /// <param name="source">The object instance to copy.</param> /// <returns>The copied object.</returns> public static T Clone<T>(T source) { if (!typeof(T).IsSerializable) { throw new ArgumentException("The type must be serializable.", "source"); } // Don't serialize a null object, simply return the default for that object if (Object.ReferenceEquals(source, null)) { return default(T); } IFormatter formatter = new BinaryFormatter(); Stream stream = new MemoryStream(); using (stream) { formatter.Serialize(stream, source); stream.Seek(0, SeekOrigin.Begin); return (T)formatter.Deserialize(stream); } } } 

The limitation is that the class must be serializable. Without the requirement of serialization, you will have to manually copy and make sure that the object and its serializing function are in compliance.

  • A good option, I somehow did not come to mind, although literally recently I used serialization. But, unfortunately, “my” objects are not serialized ... - Alexey
  • Then the interfaces. Those. not just a genetic, but Savior <T> where T: IStateContent, and then using the interface to hook up the fields and work with them ... - Arheus
  • one
    @Alexey use serialization surrogates (ISurrogateSelector) - kmv
  • You can try another reflex. But if the cyclic link comes across - then stackoverflow is provided. - Arheus
  • @ user3345929 and what for IStateContent? Couldn't google such an interface ... - Alexey

Try the following code:

 public sealed class CopyHelper { private static readonly Type array_type = typeof(Array); private static readonly MethodInfo memberwise_clone = typeof(object) .GetMethod("MemberwiseClone", BindingFlags.Instance | BindingFlags.NonPublic); private static void MakeArrayRowDeepCopy(Dictionary<object, object> state, Array array, int[] indices, int rank) { int next_rank = rank + 1; int upper_bound = array.GetUpperBound(rank); while (indices[rank] <= upper_bound) { object value = array.GetValue(indices); if (!ReferenceEquals(value, null)) array.SetValue(CreateDeepCopyInternal(state, value), indices); if (next_rank < array.Rank) MakeArrayRowDeepCopy(state, array, indices, next_rank); indices[rank] += 1; } indices[rank] = array.GetLowerBound(rank); } private static Array CreateArrayDeepCopy(Dictionary<object, object> state, Array array) { Array result = (Array)array.Clone(); int[] indices = new int[result.Rank]; for (int rank = 0; rank < indices.Length; ++rank) indices[rank] = result.GetLowerBound(rank); MakeArrayRowDeepCopy(state, result, indices, 0); return result; } private static object CreateDeepCopyInternal(Dictionary<object, object> state, object o) { object exist_object; if (state.TryGetValue(o, out exist_object)) return exist_object; if (o is Array) { object array_copy = CreateArrayDeepCopy(state, (Array)o); state[o] = array_copy; return array_copy; } else if (o is string) { object string_copy = string.Copy((string)o); state[o] = string_copy; return string_copy; } else { Type o_type = o.GetType(); if (o_type.IsPrimitive) return o; object copy = memberwise_clone.Invoke(o, null); state[o] = copy; foreach (FieldInfo f in o_type.GetFields( BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)) { object original = f.GetValue(o); if (!ReferenceEquals(original, null)) f.SetValue(copy, CreateDeepCopyInternal(state, original)); } return copy; } } public static T CreateDeepCopy<T>(T o) { object input = o; if (ReferenceEquals(o, null)) return o; return (T)CreateDeepCopyInternal(new Dictionary<object, object>(), input); } } 

Using:

 SomeClass copy = CopyHelper.CreateDeepCopy(original); 

The code copies any objects. Handles circular references correctly. It works quite quickly through the use of MemberwiseClone .

The code is taken from here (see below the last but one message, by hardcase).

PS: if someone tests the code, write down the results.

  • Any, including Remoting Proxy? :) - PashaPash
  • @PashaPash - well, we don’t take such hard cases into account. And, in general, the moped is not mine ... - Alexander Petrov