深度克隆对象

时间:2011-08-06 04:36:43

标签: c# deep-copy cloning

我正在使用Code项目中的函数深度克隆我的对象

http://www.codeproject.com/KB/tips/SerializedObjectCloner.aspx?msg=3984032#xx3984032xx

但是对于性能敏感的应用程序,我的性能损失约为执行时间的10%。

有些人可以向我建议另一种创建对象副本并且性能损失较小的方法吗?我的对象非常大,包含对象列表,而对象列表又包含对象列表等。

谢谢,

约瑟夫

4 个答案:

答案 0 :(得分:3)

我可以提出几种方法,但实施起来并不一定非常简单。我个人之间选择的两种方法是:

  1. 使用代码生成(如T4)生成克隆对象图的代码。 T4是Visual Studio 2008和Visual Studio 2010的一部分,Oleg Sych有一些很棒的T4文档:http://www.olegsych.com/2007/12/text-template-transformation-toolkit/

  2. 使用System.Linq.Expression在运行时生成克隆对象的委托。通常,由于GetValue / SetValue,反射很慢。但是,System.Linq.Expression允许您从反射中生成针对您的类“硬编码”的方法。然后缓存这些方法,因此只为反射支付一次价格。

  3. 这两种方法都应该提供与您手动编码深度克隆逻辑相当的性能。

    使深度克隆生活变得复杂的事情:

    1. 接口字段
    2. 抽象类字段
    3. 具有私有构造函数的类(有关帮助,请参阅http://msdn.microsoft.com/nb-no/library/system.runtime.serialization.formatterservices.getuninitializedobject.aspx
    4. 收集字段
    5. 写一个成熟的深度克隆者有点毛茸茸但是你知道你的域名,你可以对这个问题做一些简化。

      PS。我个人更喜欢T4而不是System.Linq.Expression,因为它不那么“神奇”

答案 1 :(得分:1)

您可以使用反射来获取对象的所有私有字段。创建一个循环访问对象私有字段的函数。获取任何值类型并复制值。如果对象支持ICloneable接口,请调用它。递归调用此克隆函数用于类中的引用类型。

编辑,继承人的代码:我相信我从互联网上的某个地方获得了CloneDictionary,但我不记得现在在哪里。另外,我刚将这个从VB.net转换为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.");
}

}

答案 2 :(得分:1)

如果您可以接受装饰对象图,可以使用protobuf-net。 (你可以使用nuget来获取它)

一个简单的例子:

[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);
        }
    }
}

注意:Serializer实际上有一个看起来很合适的DeepClone方法,但我发现它比执行Serialize然后反序列化要慢。

更新: 关于马克的问题,这似乎很奇怪。这是我的(非常有限的)测试,使用深度克隆似乎总是减慢约30%。 (注意:即使以不同的顺序运行测试而不是并行运行它们)

    [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);
        }
    }

答案 3 :(得分:1)

如果不使用常规序列化程序(如BinaryFormatter)或对整个层次结构实施手动复制,则无法创建常规clr对象的深层副本。如果BinaryFormatter太慢,您必须回退到手动序列化,或者找到/实现更快的格式化程序。请注意,大多数protobuf实现都不能与常规对象图一起使用(序列化委托,单例,空集合......)。因此,首先要研究你的图形是否允许protobuf序列化,可能你可以使用BinaryFormatter序列化并使用protobufs或手动二进制文件来处理某些子图(使用ISerializable存储)