深度克隆对象

发布于 2024-11-28 11:29:11 字数 1718 浏览 2 评论 0原文

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

不可一世的女人 2024-12-05 11:29:11

我可以建议几种方法,但它们实施起来并不一定超级简单。我个人选择的两种方法是:

  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 允许您从反射生成针对您的类“硬编码”的方法。然后,您可以缓存这些方法,从而只需为反射付出一次代价。

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

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

  1. 接口字段
  2. 抽象类字段
  3. 具有私有构造函数的类(有关帮助,请参阅 http://msdn.microsoft.com/nb-no/library/system.runtime.serialization.formatterservices.getuninitializedobject.aspx)
  4. 集合字段

编写成熟的深度克隆程序是有点毛茸茸的,但当你知道你的领域时,你也许可以对问题做一些简化。

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

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:

  1. 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/

  2. 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:

  1. Interface fields
  2. Abstract class fields
  3. Classes with private constructors (for help see http://msdn.microsoft.com/nb-no/library/system.runtime.serialization.formatterservices.getuninitializedobject.aspx)
  4. 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"

|煩躁 2024-12-05 11:29:11

如果您可以接受稍微装饰一下对象图,您可以使用 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);
        }
    }
}

注意:序列化器实际上有一个 DeepClone 方法,该方法似乎适合于此,但我发现它比执行序列化和反序列化要慢。

更新:
关于马克的问题,这确实看起来很奇怪。这是我的(非常有限的)测试,使用深度克隆似乎始终慢 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);
        }
    }

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);
        }
    }
安人多梦 2024-12-05 11:29:11

如果不使用通用序列化程序(例如 BinaryFormatter)或为整个层次结构实现手动复制,则无法创建通用 clr 对象的深层副本。如果 BinaryFormatter 太慢,您必须要么退回到手动序列化,要么找到/实现更快的格式化程序。请注意,大多数 protobuf 实现无法在一般对象图(序列化委托、单例、空集合等)中开箱即用。因此,首先调查您的图是否允许 protobuf 序列化,您可能可以使用 BinaryFormatter 进行序列化,并在可能的情况下对某些子图使用 protobuf 或手动二进制写入(使用 ISerialized 存储)

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

凯凯我们等你回来 2024-12-05 11:29:11

您可以使用反射来获取对象的所有私有字段。创建一个函数来循环访问对象的私有字段。获取任何值类型并复制该值。如果该对象支持 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.");
}

}

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.");
}

}

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文