Deep cloning an object [closed]
Closed 4 years ago.
开发者_如何学Python- Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
- We don’t allow questions seeking recommendations for books, tools, software libraries, and more. You can edit the question so it can be answered with facts and citations.
I am using the function from Code project to deep clone my objects
http://www.codeproject.com/KB/tips/SerializedObjectCloner.aspx?msg=3984032#xx3984032xx
however for performance sensitive applications i am having a performance penalty that is about 10% of the execution time.
Can some please suggest to me another method of creating a copy of an object and having a smaller performance penalty? My object is quite large and contains lists of objects which in turn contain list of objects etc.
Thanks,
Joseph
I can suggest a couple of approaches but they are not necessarily super-simple to implement. The two approaches I personally would chose between to do this are:
Use code generation such as T4 to generate code that clone your object graphs. T4 is part of Visual Studio 2008 and Visual Studio 2010 and Oleg Sych has some great T4 documentation: http://www.olegsych.com/2007/12/text-template-transformation-toolkit/
Use System.Linq.Expression to at runtime generate delegates that clones your object. In general reflection is slow because of GetValue/SetValue. System.Linq.Expression however allows you generate methods from reflection that are "hard-coded" against your classes. These methods you then cache thus paying the price for reflection only once.
Both of these approaches should give your performance that is comparable to if you were hand-coding the deep clone logic.
Things that complicates the life for deep cloning:
- Interface fields
- Abstract class fields
- Classes with private constructors (for help see http://msdn.microsoft.com/nb-no/library/system.runtime.serialization.formatterservices.getuninitializedobject.aspx)
- Collection fields
Writing a fully-fledged deep-cloner is a bit hairy but as you know your domain you might be able to do some simplifications to the problem.
PS. I personally prefer T4 over System.Linq.Expression as that is less "magic"
If you can accept decorating your object graph a bit you could use protobuf-net. (You can get it using nuget for instance)
A trivial example:
[Serializable]
[ProtoContract]
public class TestObject
{
[ProtoMember(1)]
public string TestProperty { get; set; }
}
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);
}
Stream stream = new MemoryStream();
using (stream)
{
Serializer.Serialize<T>(stream, source);
stream.Seek(0, SeekOrigin.Begin);
return Serializer.Deserialize<T>(stream);
}
}
}
Note: Serializer actually has a DeepClone method which would seem suitable for this but I found it to be slower than doing Serialize followed by Deserialize.
UPDATE: With regards to Mark's question, that does seem very odd. This is my (very limited) test that seems to be consistently about 30% slower using deep clone. (Note: Even when running the tests in different order and not running them in parallell)
[TestMethod]
public void TestWithStream()
{
var objects = Enumerable.Range(0, 1000000).Select(_ => new TestObject { TestProperty = Guid.NewGuid().ToString() }).ToList();
Stopwatch w = Stopwatch.StartNew();
for (int i = 0; i < objects.Count; ++i)
{
ObjectCopier.CloneWithStream(objects[i]);
}
Console.WriteLine(w.Elapsed);
}
[TestMethod]
public void TestWithDeepClone()
{
var objects = Enumerable.Range(0, 1000000).Select(_ => new TestObject { TestProperty = Guid.NewGuid().ToString() }).ToList();
Stopwatch w = Stopwatch.StartNew();
for (int i = 0; i < objects.Count; ++i)
{
ObjectCopier.CloneWithDeepClone(objects[i]);
}
Console.WriteLine(w.Elapsed);
}
public static class ObjectCopier
{
public static T CloneWithStream<T>(T source)
{
if (!typeof(T).IsSerializable)
{
throw new ArgumentException("The type must be serializable.", "source");
}
if (Object.ReferenceEquals(source, null))
{
return default(T);
}
Stream stream = new MemoryStream();
using (stream)
{
Serializer.Serialize<T>(stream, source);
stream.Seek(0, SeekOrigin.Begin);
return Serializer.Deserialize<T>(stream);
}
}
public static T CloneWithDeepClone<T>(T source)
{
if (!typeof(T).IsSerializable)
{
throw new ArgumentException("The type must be serializable.", "source");
}
if (Object.ReferenceEquals(source, null))
{
return default(T);
}
return Serializer.DeepClone(source);
}
}
Creating a deep copy of a general clr object is not possible without using a general serializer (such as BinaryFormatter) or implementing manual copying for your entire hierarchy. If the BinaryFormatter is too slow you must either fall back to manual serialization, or find/ implement a faster formatter. Note that most protobuf implementations will not work out of the box with general object graphs (serializing delegates, singletons, null collections, ...). So first investigate if your graph allows protobuf serialization, potentially you can serialize with BinaryFormatter and use protobufs or manual binarywriting for certain subgraphs (stored using ISerializable) where possible
You can use reflection to get all the private fields of the object. Create a function to loop through the private fields of the object. Take any value types and copy the value. If the object supports the ICloneable interface, call that. Recursive call this clone function for the reference types in the class.
Edit, heres the code for this: I believe I got the CloneDictionary from somewhere on the internet but i don't remember where now. Also, I just converted this from VB.net To C#.
public static object GenericClone(object Obj)
{
object Out = null;
Out = Activator.CreateInstance(Obj.GetType());
Type mytype = Obj.GetType();
while (mytype != null) {
foreach (System.Reflection.FieldInfo item in mytype.GetFields(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)) {
object itemValue = item.GetValue(Obj);
object newvalue = null;
if (itemValue != null) {
if (typeof(System.ICloneable).IsAssignableFrom(itemValue.GetType())) {
newvalue = ((System.ICloneable)itemValue).Clone();
} else {
if (itemValue.GetType().IsValueType) {
newvalue = itemValue;
} else {
if (itemValue.GetType().Name == "Dictionary`2") {
newvalue = DataInterface.CloneDictionary(itemValue);
} else if (object.ReferenceEquals(itemValue.GetType(), typeof(System.Text.StringBuilder))) {
newvalue = new System.Text.StringBuilder(((System.Text.StringBuilder)itemValue).ToString());
} else if (itemValue.GetType().Name == "List`1") {
newvalue = DataInterface.CloneList(itemValue);
} else {
throw (new Exception(item.Name + ", member of " + mytype.Name + " is not cloneable or of value type."));
}
}
}
}
//set new obj copied data
mytype.GetField(item.Name, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance).SetValue(Out, newvalue);
}
//must move up to base type, GetFields does not return inherited fields
mytype = mytype.BaseType;
}
return Out;
}
public static Dictionary<K, V> CloneDictionary<K, V>(Dictionary<K, V> dict)
{
Dictionary<K, V> newDict = null;
// The clone method is immune to the source dictionary being null.
if (dict != null) {
// If the key and value are value types, clone without serialization.
if (((typeof(K).IsValueType || object.ReferenceEquals(typeof(K), typeof(string))) && (typeof(V).IsValueType) || object.ReferenceEquals(typeof(V), typeof(string)))) {
newDict = new Dictionary<K, V>();
// Clone by copying the value types.
foreach (KeyValuePair<K, V> kvp in dict) {
newDict[kvp.Key] = kvp.Value;
}
} else {
newDict = new Dictionary<K, V>();
// Clone by copying the value types.
foreach (KeyValuePair<K, V> kvp in dict) {
newDict[kvp.Key] = DataInterface.GenericClone(kvp.Value);
}
}
}
return newDict;
}
public static List<T> CloneList<T>(List<T> list)
{
List<T> Out = new List<T>();
if (typeof(System.ICloneable).IsAssignableFrom(typeof(T))) {
return (from x in list(T)((ICloneable)x).Clone()).ToList;
} else if (typeof(T).IsValueType) {
return (from x in list(T)x).ToList;
} else {
throw new InvalidOperationException("List elements not of value or cloneable type.");
}
}
精彩评论