首页 文章

如何在.NET中对对象进行深层复制(特别是C#)? [重复]

提问于
浏览
507

这个问题在这里已有答案:

我想要一个真正的深层复制品 . 在Java中,这很容易,但是你如何在C#中做到这一点?

11 回答

  • 47

    我已经看到了一些不同的方法,但我使用了一个通用的实用方法:

    public static T DeepClone<T>(T obj)
    {
     using (var ms = new MemoryStream())
     {
       var formatter = new BinaryFormatter();
       formatter.Serialize(ms, obj);
       ms.Position = 0;
    
       return (T) formatter.Deserialize(ms);
     }
    }
    

    笔记:

    • 您的 class 必须标记为 [Serializable] 才能生效 .

    • 您的源文件必须包含以下代码:

    using System.Runtime.Serialization.Formatters.Binary;
    using System.IO;
    
  • 266

    wrote a deep object copy extension method,基于递归 "MemberwiseClone" . 它比BinaryFormatter快( three times faster ),适用于任何对象 . 您不需要默认构造函数或可序列化属性 .

    源代码:

    using System.Collections.Generic;
    using System.Reflection;
    using System.ArrayExtensions;
    
    namespace System
    {
        public static class ObjectExtensions
        {
            private static readonly MethodInfo CloneMethod = typeof(Object).GetMethod("MemberwiseClone", BindingFlags.NonPublic | BindingFlags.Instance);
    
            public static bool IsPrimitive(this Type type)
            {
                if (type == typeof(String)) return true;
                return (type.IsValueType & type.IsPrimitive);
            }
    
            public static Object Copy(this Object originalObject)
            {
                return InternalCopy(originalObject, new Dictionary<Object, Object>(new ReferenceEqualityComparer()));
            }
            private static Object InternalCopy(Object originalObject, IDictionary<Object, Object> visited)
            {
                if (originalObject == null) return null;
                var typeToReflect = originalObject.GetType();
                if (IsPrimitive(typeToReflect)) return originalObject;
                if (visited.ContainsKey(originalObject)) return visited[originalObject];
                if (typeof(Delegate).IsAssignableFrom(typeToReflect)) return null;
                var cloneObject = CloneMethod.Invoke(originalObject, null);
                if (typeToReflect.IsArray)
                {
                    var arrayType = typeToReflect.GetElementType();
                    if (IsPrimitive(arrayType) == false)
                    {
                        Array clonedArray = (Array)cloneObject;
                        clonedArray.ForEach((array, indices) => array.SetValue(InternalCopy(clonedArray.GetValue(indices), visited), indices));
                    }
    
                }
                visited.Add(originalObject, cloneObject);
                CopyFields(originalObject, visited, cloneObject, typeToReflect);
                RecursiveCopyBaseTypePrivateFields(originalObject, visited, cloneObject, typeToReflect);
                return cloneObject;
            }
    
            private static void RecursiveCopyBaseTypePrivateFields(object originalObject, IDictionary<object, object> visited, object cloneObject, Type typeToReflect)
            {
                if (typeToReflect.BaseType != null)
                {
                    RecursiveCopyBaseTypePrivateFields(originalObject, visited, cloneObject, typeToReflect.BaseType);
                    CopyFields(originalObject, visited, cloneObject, typeToReflect.BaseType, BindingFlags.Instance | BindingFlags.NonPublic, info => info.IsPrivate);
                }
            }
    
            private static void CopyFields(object originalObject, IDictionary<object, object> visited, object cloneObject, Type typeToReflect, BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy, Func<FieldInfo, bool> filter = null)
            {
                foreach (FieldInfo fieldInfo in typeToReflect.GetFields(bindingFlags))
                {
                    if (filter != null && filter(fieldInfo) == false) continue;
                    if (IsPrimitive(fieldInfo.FieldType)) continue;
                    var originalFieldValue = fieldInfo.GetValue(originalObject);
                    var clonedFieldValue = InternalCopy(originalFieldValue, visited);
                    fieldInfo.SetValue(cloneObject, clonedFieldValue);
                }
            }
            public static T Copy<T>(this T original)
            {
                return (T)Copy((Object)original);
            }
        }
    
        public class ReferenceEqualityComparer : EqualityComparer<Object>
        {
            public override bool Equals(object x, object y)
            {
                return ReferenceEquals(x, y);
            }
            public override int GetHashCode(object obj)
            {
                if (obj == null) return 0;
                return obj.GetHashCode();
            }
        }
    
        namespace ArrayExtensions
        {
            public static class ArrayExtensions
            {
                public static void ForEach(this Array array, Action<Array, int[]> action)
                {
                    if (array.LongLength == 0) return;
                    ArrayTraverse walker = new ArrayTraverse(array);
                    do action(array, walker.Position);
                    while (walker.Step());
                }
            }
    
            internal class ArrayTraverse
            {
                public int[] Position;
                private int[] maxLengths;
    
                public ArrayTraverse(Array array)
                {
                    maxLengths = new int[array.Rank];
                    for (int i = 0; i < array.Rank; ++i)
                    {
                        maxLengths[i] = array.GetLength(i) - 1;
                    }
                    Position = new int[array.Rank];
                }
    
                public bool Step()
                {
                    for (int i = 0; i < Position.Length; ++i)
                    {
                        if (Position[i] < maxLengths[i])
                        {
                            Position[i]++;
                            for (int j = 0; j < i; j++)
                            {
                                Position[j] = 0;
                            }
                            return true;
                        }
                    }
                    return false;
                }
            }
        }
    
    }
    
  • 6

    以Kilhoffer的解决方案为基础......

    使用C#3.0,您可以创建一个扩展方法,如下所示:

    public static class ExtensionMethods
    {
        // Deep clone
        public static T DeepClone<T>(this T a)
        {
            using (MemoryStream stream = new MemoryStream())
            {
                BinaryFormatter formatter = new BinaryFormatter();
                formatter.Serialize(stream, a);
                stream.Position = 0;
                return (T) formatter.Deserialize(stream);
            }
        }
    }
    

    它扩展了使用DeepClone方法标记为[Serializable]的任何类

    MyClass copy = obj.DeepClone();
    
  • 0

    你可以使用 Nested MemberwiseClone to do a deep copy . 它与复制值结构的速度几乎相同,并且比(a)反射或(b)序列化(如本页其他答案中所述)快一个数量级 .

    请注意 if 您使用 Nested MemberwiseClone for a deep copy ,您必须为类中的每个嵌套级别手动实现ShallowCopy,并使用DeepCopy调用所有所述ShallowCopy方法来创建完整克隆 . 这很简单:总共只有几行,请参阅下面的演示代码 .

    以下是显示相对性能差异的代码输出(深度嵌套的MemberwiseCopy为4.77秒,序列化为39.93秒) . 使用嵌套的MemberwiseCopy几乎与复制结构一样快,复制结构非常接近.NET能够达到的理论最大速度 .

    Demo of shallow and deep copy, using classes and MemberwiseClone:
          Create Bob
            Bob.Age=30, Bob.Purchase.Description=Lamborghini
          Clone Bob >> BobsSon
          Adjust BobsSon details
            BobsSon.Age=2, BobsSon.Purchase.Description=Toy car
          Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:
            Bob.Age=30, Bob.Purchase.Description=Lamborghini
          Elapsed time: 00:00:04.7795670,30000000
        Demo of shallow and deep copy, using structs and value copying:
          Create Bob
            Bob.Age=30, Bob.Purchase.Description=Lamborghini
          Clone Bob >> BobsSon
          Adjust BobsSon details:
            BobsSon.Age=2, BobsSon.Purchase.Description=Toy car
          Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:
            Bob.Age=30, Bob.Purchase.Description=Lamborghini
          Elapsed time: 00:00:01.0875454,30000000
        Demo of deep copy, using class and serialize/deserialize:
          Elapsed time: 00:00:39.9339425,30000000
    

    要了解如何使用MemberwiseCopy执行深层复制,以下是演示项目:

    // Nested MemberwiseClone example. 
    // Added to demo how to deep copy a reference class.
    [Serializable] // Not required if using MemberwiseClone, only used for speed comparison using serialization.
    public class Person
    {
        public Person(int age, string description)
        {
            this.Age = age;
            this.Purchase.Description = description;
        }
        [Serializable] // Not required if using MemberwiseClone
        public class PurchaseType
        {
            public string Description;
            public PurchaseType ShallowCopy()
            {
                return (PurchaseType)this.MemberwiseClone();
            }
        }
        public PurchaseType Purchase = new PurchaseType();
        public int Age;
        // Add this if using nested MemberwiseClone.
        // This is a class, which is a reference type, so cloning is more difficult.
        public Person ShallowCopy()
        {
            return (Person)this.MemberwiseClone();
        }
        // Add this if using nested MemberwiseClone.
        // This is a class, which is a reference type, so cloning is more difficult.
        public Person DeepCopy()
        {
                // Clone the root ...
            Person other = (Person) this.MemberwiseClone();
                // ... then clone the nested class.
            other.Purchase = this.Purchase.ShallowCopy();
            return other;
        }
    }
    // Added to demo how to copy a value struct (this is easy - a deep copy happens by default)
    public struct PersonStruct
    {
        public PersonStruct(int age, string description)
        {
            this.Age = age;
            this.Purchase.Description = description;
        }
        public struct PurchaseType
        {
            public string Description;
        }
        public PurchaseType Purchase;
        public int Age;
        // This is a struct, which is a value type, so everything is a clone by default.
        public PersonStruct ShallowCopy()
        {
            return (PersonStruct)this;
        }
        // This is a struct, which is a value type, so everything is a clone by default.
        public PersonStruct DeepCopy()
        {
            return (PersonStruct)this;
        }
    }
    // Added only for a speed comparison.
    public class MyDeepCopy
    {
        public static T DeepCopy<T>(T obj)
        {
            object result = null;
            using (var ms = new MemoryStream())
            {
                var formatter = new BinaryFormatter();
                formatter.Serialize(ms, obj);
                ms.Position = 0;
                result = (T)formatter.Deserialize(ms);
                ms.Close();
            }
            return (T)result;
        }
    }
    

    然后,从main调用demo:

    void MyMain(string[] args)
        {
            {
                Console.Write("Demo of shallow and deep copy, using classes and MemberwiseCopy:\n");
                var Bob = new Person(30, "Lamborghini");
                Console.Write("  Create Bob\n");
                Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
                Console.Write("  Clone Bob >> BobsSon\n");
                var BobsSon = Bob.DeepCopy();
                Console.Write("  Adjust BobsSon details\n");
                BobsSon.Age = 2;
                BobsSon.Purchase.Description = "Toy car";
                Console.Write("    BobsSon.Age={0}, BobsSon.Purchase.Description={1}\n", BobsSon.Age, BobsSon.Purchase.Description);
                Console.Write("  Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:\n");
                Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
                Debug.Assert(Bob.Age == 30);
                Debug.Assert(Bob.Purchase.Description == "Lamborghini");
                var sw = new Stopwatch();
                sw.Start();
                int total = 0;
                for (int i = 0; i < 100000; i++)
                {
                    var n = Bob.DeepCopy();
                    total += n.Age;
                }
                Console.Write("  Elapsed time: {0},{1}\n", sw.Elapsed, total);
            }
            {               
                Console.Write("Demo of shallow and deep copy, using structs:\n");
                var Bob = new PersonStruct(30, "Lamborghini");
                Console.Write("  Create Bob\n");
                Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
                Console.Write("  Clone Bob >> BobsSon\n");
                var BobsSon = Bob.DeepCopy();
                Console.Write("  Adjust BobsSon details:\n");
                BobsSon.Age = 2;
                BobsSon.Purchase.Description = "Toy car";
                Console.Write("    BobsSon.Age={0}, BobsSon.Purchase.Description={1}\n", BobsSon.Age, BobsSon.Purchase.Description);
                Console.Write("  Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:\n");
                Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);                
                Debug.Assert(Bob.Age == 30);
                Debug.Assert(Bob.Purchase.Description == "Lamborghini");
                var sw = new Stopwatch();
                sw.Start();
                int total = 0;
                for (int i = 0; i < 100000; i++)
                {
                    var n = Bob.DeepCopy();
                    total += n.Age;
                }
                Console.Write("  Elapsed time: {0},{1}\n", sw.Elapsed, total);
            }
            {
                Console.Write("Demo of deep copy, using class and serialize/deserialize:\n");
                int total = 0;
                var sw = new Stopwatch();
                sw.Start();
                var Bob = new Person(30, "Lamborghini");
                for (int i = 0; i < 100000; i++)
                {
                    var BobsSon = MyDeepCopy.DeepCopy<Person>(Bob);
                    total += BobsSon.Age;
                }
                Console.Write("  Elapsed time: {0},{1}\n", sw.Elapsed, total);
            }
            Console.ReadKey();
        }
    

    同样,请注意 if 您使用 Nested MemberwiseClone for a deep copy ,您必须为类中的每个嵌套级别手动实现ShallowCopy,并使用DeepCopy调用所有所述ShallowCopy方法来创建完整的克隆 . 这很简单:总共只有几行,请参阅上面的演示代码 .

    请注意,在克隆对象时,“struct”和“class”之间存在很大差异:

    • 如果您有一个"struct",它是一个值类型,因此您可以复制它,并且将克隆内容 .

    • 如果你有"class",它是一个引用类型,所以如果你复制它,你所做的只是将指针复制到它 . 要创建真正的克隆,您必须更具创造性,并使用在内存中创建原始对象的另一个副本的方法 .

    • 错误地克隆对象可能导致非常难以确定的错误 . 在 生产环境 代码中,我倾向于实现校验和以仔细检查对象是否已正确克隆,并且没有被另一个对它的引用破坏 . 可以在释放模式下关闭此校验和 .

    • 我发现这种方法非常有用:通常,你只想克隆对象的一部分,而不是整个事物 . 对于修改对象,然后将修改后的副本提供到队列中的任何用例,它也是必不可少的 .

    Update

    可能使用反射来递归遍历对象图以进行深层复制 . WCF使用此技术序列化对象,包括其所有子对象 . 诀窍是使用使其可被发现的属性来注释所有子对象 . 但是,您可能会失去一些性能优势 .

    Update

    引用独立速度测试(见下面的评论):

    我使用Neil的序列化/反序列化扩展方法,Contango的嵌套MemberwiseClone,Alex Burtsev的基于反射的扩展方法和AutoMapper,每次100万次运行我自己的速度测试 . Serialize-deserialize最慢,耗时15.7秒 . 然后是AutoMapper,耗时10.1秒 . 基于反射的方法要快2.4秒,速度要快得多 . 到目前为止,最快的是嵌套的MemberwiseClone,耗时0.1秒 . 归结为性能与为每个类添加代码以克隆它的麻烦 . 如果表现不是问题,请选择Alex Burtsev的方法 . - Simon Tewsi

  • 552

    我相信BinaryFormatter方法相对较慢(这让我感到惊讶!) . 如果符合ProtoBuf的要求,您可以将ProtoBuf .NET用于某些对象 . 从ProtoBuf入门页面(http://code.google.com/p/protobuf-net/wiki/GettingStarted):

    Notes on types supported:

    自定义类:

    • 被标记为数据 Contract

    • 有一个无参数构造函数

    • 对于Silverlight:是公开的

    • 许多常见的原语等

    • 单维数组:T []

    • List <T> / IList <T>

    • Dictionary <TKey,TValue> / IDictionary <TKey,TValue>

    • 任何实现IEnumerable <T>并具有Add(T)方法的类型

    该代码假定类型将在当选成员周围变化 . 因此,不支持自定义结构,因为它们应该是不可变的 .

    如果你的 class 遇到这些您可以尝试的要求:

    public static void deepCopy<T>(ref T object2Copy, ref T objectCopy)
    {
        using (var stream = new MemoryStream())
        {
            Serializer.Serialize(stream, object2Copy);
            stream.Position = 0;
            objectCopy = Serializer.Deserialize<T>(stream);
        }
    }
    

    哪个确实非常快......

    Edit:

    这是修改此代码的工作代码(在.NET 4.6上测试) . 它使用System.Xml.Serialization和System.IO . 无需将类标记为可序列化 .

    public void DeepCopy<T>(ref T object2Copy, ref T objectCopy)
    {
        using (var stream = new MemoryStream())
        {
            var serializer = new XS.XmlSerializer(typeof(T));
    
            serializer.Serialize(stream, object2Copy);
            stream.Position = 0;
            objectCopy = (T)serializer.Deserialize(stream);
        }
    }
    
  • 5

    你可以试试这个

    public static object DeepCopy(object obj)
        {
            if (obj == null)
                return null;
            Type type = obj.GetType();
    
            if (type.IsValueType || type == typeof(string))
            {
                return obj;
            }
            else if (type.IsArray)
            {
                Type elementType = Type.GetType(
                     type.FullName.Replace("[]", string.Empty));
                var array = obj as Array;
                Array copied = Array.CreateInstance(elementType, array.Length);
                for (int i = 0; i < array.Length; i++)
                {
                    copied.SetValue(DeepCopy(array.GetValue(i)), i);
                }
                return Convert.ChangeType(copied, obj.GetType());
            }
            else if (type.IsClass)
            {
    
                object toret = Activator.CreateInstance(obj.GetType());
                FieldInfo[] fields = type.GetFields(BindingFlags.Public |
                            BindingFlags.NonPublic | BindingFlags.Instance);
                foreach (FieldInfo field in fields)
                {
                    object fieldValue = field.GetValue(obj);
                    if (fieldValue == null)
                        continue;
                    field.SetValue(toret, DeepCopy(fieldValue));
                }
                return toret;
            }
            else
                throw new ArgumentException("Unknown type");
        }
    

    感谢DetoX83 article代码项目 .

  • 153

    也许你只需要一个浅拷贝,在这种情况下使用 Object.MemberWiseClone() .

    有关深层复制策略的文档中有很好的建议 MemberWiseClone()

    http://msdn.microsoft.com/en-us/library/system.object.memberwiseclone.aspx

  • 3

    最好的方法是:

    public interface IDeepClonable<T> where T : class
        {
            T DeepClone();
        }
    
        public class MyObj : IDeepClonable<MyObj>
        {
            public MyObj Clone()
            {
                var myObj = new MyObj();
                myObj._field1 = _field1;//value type
                myObj._field2 = _field2;//value type
                myObj._field3 = _field3;//value type
    
                if (_child != null)
                {
                    myObj._child = _child.DeepClone(); //reference type .DeepClone() that does the same
                }
    
                int len = _array.Length;
                myObj._array = new MyObj[len]; // array / collection
                for (int i = 0; i < len; i++)
                {
                    myObj._array[i] = _array[i];
                }
    
                return myObj;
            }
    
            private bool _field1;
            public bool Field1
            {
                get { return _field1; }
                set { _field1 = value; }
            }
    
            private int _field2;
            public int Property2
            {
                get { return _field2; }
                set { _field2 = value; }
            }
    
            private string _field3;
            public string Property3
            {
                get { return _field3; }
                set { _field3 = value; }
            }
    
            private MyObj _child;
            private MyObj Child
            {
                get { return _child; }
                set { _child = value; }
            }
    
            private MyObj[] _array = new MyObj[4];
        }
    
  • 0

    MSDN文档似乎暗示克隆应该执行深层复制,但它从未明确说明:

    ICloneable接口包含一个成员Clone,它旨在支持除MemberWiseClone提供的克隆之外的克隆... MemberwiseClone方法创建一个浅拷贝...

    你可以找到我的帖子 .

    http://pragmaticcoding.com/index.php/cloning-objects-in-c/

  • -5
    public static object CopyObject(object input)
        {
            if (input != null)
            {
                object result = Activator.CreateInstance(input.GetType());
                foreach (FieldInfo field in input.GetType().GetFields(Consts.AppConsts.FullBindingList))
                {
                    if (field.FieldType.GetInterface("IList", false) == null)
                    {
                        field.SetValue(result, field.GetValue(input));
                    }
                    else
                    {
                        IList listObject = (IList)field.GetValue(result);
                        if (listObject != null)
                        {
                            foreach (object item in ((IList)field.GetValue(input)))
                            {
                                listObject.Add(CopyObject(item));
                            }
                        }
                    }
                }
                return result;
            }
            else
            {
                return null;
            }
        }
    

    这种方式比 BinarySerialization 快几倍,并且这不需要 [Serializable] 属性 .

  • 13

    我有一个更简单的想法 . 使用LINQ进行新选择 .

    public class Fruit
    {
      public string Name {get; set;}
      public int SeedCount {get; set;}
    }
    
    void SomeMethod()
    {
      List<Fruit> originalFruits = new List<Fruit>();
      originalFruits.Add(new Fruit {Name="Apple", SeedCount=10});
      originalFruits.Add(new Fruit {Name="Banana", SeedCount=0});
    
      //Deep Copy
      List<Fruit> deepCopiedFruits = from f in originalFruits
                  select new Fruit {Name=f.Name, SeedCount=f.SeedCount};
    }
    

相关问题