Kaip padaryti gilų objekto kopiją .NET (pvz., C #)?

Noriu tikros gilios kopijos. Java, tai buvo lengva, bet kaip tai padaryti C #?

505
24 сент. nustatė vartotojo18931 24 sek . 2008-09-24 22:39 '08, 10:39 pm 2008-09-24 22:39
@ 11 atsakymų

Aš mačiau keletą skirtingų požiūrių į tai, tačiau naudoju bendrą naudingumo metodą:

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

Pastabos:

  • Jūsų klasė turi būti pažymėta kaip [Serializable] kad tai veiktų.
  • Šaltinio faile turi būti toks kodas:

     using System.Runtime.Serialization.Formatters.Binary; using System.IO; 
552
24 сент. Atsakymas pateikiamas „ Kilhoffer“ 24 sep . 2008-09-24 22:40 '08 10:40 pm 2008-09-24 22:40

Parašiau pratęsimo metodą, skirtą giliai objekto kopijai, remiantis rekursiniu „MemberwiseClone“ . Jis yra greitas ( tris kartus greičiau nei BinaryFormatter) ir veikia su bet kuriuo objektu. Jums nereikia numatytojo konstruktoriaus ar serializuojamų atributų.

Šaltinio kodas:

 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; } } } } 
264
03 июля '12 в 13:20 2012-07-03 13:20 Atsakymą pateikė Alex Burtsev liepos 12 d. 12 val. 2012-07-03 13:20

Pagal „Kilhoffer“ sprendimą ...

Naudodami C # 3.0 galite sukurti išplėtimo būdą taip:

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

kuri praplečia klasę, pažymėtą [Serializable], naudojant DeepClone metodą

 MyClass copy = obj.DeepClone(); 
153
31 июля '09 в 19:51 2009-07-31 19:51 atsakymą davė Neil liepos 31 d., 09:51, 2009-07-31 19:51

Galite naudoti „ Nested MemberwiseClone“, kad atliktumėte gilų kopiją . Jo greitis yra beveik toks pat, kaip kopijuojant reikšmių struktūrą, ir jos tvarka yra greitesnė nei (a) atspindys arba (b) serializacija (kaip aprašyta kituose šio puslapio atsakymuose).

Atkreipkite dėmesį, kad jei naudojate „ Nested MemberwiseClone“ giliam kopijavimui , turite rankiniu būdu įdiegti „ShallowCopy“ kiekvienam įdėtam lygiui klasėje, ir „DeepCopy“, kuris visus „ShallowCopy“ metodus ragina sukurti pilną kloną. Tai paprasta: vos kelios eilutės, žr. Toliau pateiktą demo kodą.

Čia yra kodo rezultatas, rodantis santykinį našumo skirtumą (4,77 sekundės giliai įdėtam „MemberwiseCopy“ lygiui, palyginti su 39,93 sekundėmis serijiniam nustatymui). Naudojant įdėtą „MemberwiseCopy“ yra beveik taip pat greitai, kaip ir kopijavimas, o struktūros kopijavimas yra gana artimas teoriniam maksimaliam greičiui, kurį gali .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 

Jei norite suprasti, kaip padaryti gilų kopiją naudodami „MemberwiseCopy“, čia yra pavyzdinis projektas:

 // 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; } } 

Tada skambinkite demo iš pagrindinių:

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

Atkreipkite dėmesį, kad jei naudojate „ Nested MemberwiseClone“ giliam kopijavimui , turite rankiniu būdu įdiegti „ShallowCopy“ kiekvienam klasės lygiui ir „DeepCopy“, kuris ragina visus minėtus „ShallowCopy“ metodus sukurti pilną kloną. Tai paprasta: vos kelios eilutės, žr.

Atkreipkite dėmesį, kad kalbant apie objekto klonavimą, yra didelis skirtumas tarp „struktūros“ ir „klasės“:

  • Jei turite "struktūrą", tai yra vertės rūšis, todėl galite tiesiog nukopijuoti ir turinys bus klonuotas.
  • Jei turite „klasę“, tai yra referencinis tipas, todėl, jei jį kopijuojate, viskas, ką darote, yra nukopijuoti žymeklį. Norėdami sukurti tikrą kloną, turite būti kūrybingesni ir naudoti metodą, kuris sukuria kitą originalo objekto kopiją atmintyje.
  • Netinkamai klonuojant daiktus, gali kilti labai sudėtingų klaidų. Gamybos kode aš stengiuosi įgyvendinti kontrolinę sumą, kad dvigubai patikrintumėte, ar objektas buvo teisingai klonuotas ir nebuvo pažeista kita nuoroda į jį. Ši kontrolinė suma gali būti išjungta atleidimo režimu.
  • Manau, kad šis metodas yra labai naudingas: dažnai norite klonuoti objekto dalis, o ne viską. Tai taip pat svarbu bet kokiems naudojimo atvejams, kai keičiate objektus, o tada įdėkite modifikuotas kopijas į eilę.

Atnaujinti

Galima naudoti refleksiją, kad rekursyviai vaikščiotumėte per objektų grafiką, kad padarytumėte gilų kopiją. WCF naudoja šį metodą objekto serializavimui, įskaitant visus jo vaikus. Apgaulė yra pažymėti visus vaikus, kurių atributas leidžia jį rasti. Tačiau galite prarasti kai kuriuos našumo privalumus.

Atnaujinti

Citata apie nepriklausomą greičio bandymą (žr. Toliau pateiktus komentarus):

Bėgau savo greičio testą naudodamas „Neil“ serializacijos / deserializacijos pratęsimo metodą, „Contango Nested MemberwiseClone“, „Alex Burtse“ atspindžio pagrindą ir „AutoMapper“, 1 mln. Serializacija-deserializacija buvo lėčiausia, atsižvelgiant į 15,7 sekundes. tada atėjo AutoMapper, 10,1 sekundės. Daug greičiau remiantis refleksijomis, kurios užtruko 2,4 sekundės. Greičiausias buvo įdėtas „MemberwiseClone“, užtrunka 0,1 sekundės. Veikimas mažėja, palyginti su sudėtingumu, kodėl kiekvienai klasei reikia koduoti klonuojant. Jei atlikimas nėra problema su Aleksejus Burtsevo metodu. - Simon Tewsey

47
30 дек. Atsakymas pateikiamas Contango 30 d. 2011-12-30 22:05 '12 10:05 val. 2011-12-30 22:05

Manau, kad „BinaryFormatter“ požiūris yra gana lėtas (tai man teko nustebinti!). ProtoBuf.NET galite naudoti kai kuriems objektams, jei jie atitinka ProtoBuf reikalavimus. Puslapio „ProtoBuf“ pradžia ( http://code.google.com/p/protobuf-net/wiki/GettingStarted ):

Pastabos apie palaikomus tipus:

Individualios klasės, kurios:

  • Pažymėta kaip sutarties duomenys
  • Turėkite konstruktorių be parametrų
  • „Silverlight“: viešai prieinama
  • Daug bendrų primityvų ir tt
  • Vienviečiai matricos: T []
  • <T> / IList <T> sąrašas
  • Žodynas <TKey, TValue> / IDictionary <TKey, TValue>
  • bet kokio tipo, kuris įgyvendina IEnumerable <T> ir turi Add (T) metodą

Kodekse daroma prielaida, kad tipai bus redaguojami aplink išrinktus narius. Atitinkamai, naudotojo struktūros nėra palaikomos, nes jos turi būti nekeičiamos.

Jei jūsų klasė atitinka šiuos reikalavimus, galite pabandyti:

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

Kas iš tikrųjų yra labai greita ...

Redaguoti:

Čia yra darbo kodas, skirtas modifikuoti (išbandytas .NET 4.6). Jis naudoja System.Xml.Serialization ir System.IO. Nereikia pažymėti klasių kaip serializuotinų.

 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); } } 
13
03 янв. Atsakyti Kurt Richardson Jan 03 2012-01-03 08:21 '12, 08:21 AM 2012-01-03 08:21

Galite jį išbandyti

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

Kodekso projekto DetoX83 straipsnis .

6
01 апр. Atsakymas, kurį pateikė Suresh Kumar Veluswamy Apr 01 2012-04-01 06:59 '12, 6:59 2012-04-01 06:59

Galbūt jums reikia tik mažos kopijos, šiuo atveju naudokite „ Object.MemberWiseClone() .

MemberWiseClone() dokumentacijoje yra gerų rekomendacijų, kaip giliai kopijuoti strategijas:

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

5
01 дек. Atsakymas, kurį pateikė David Thornley Dec 01 2010-12-01 06:00 '10, 6:00, 2010-12-01 06:00

Geriausias būdas:

  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]; } 
3
22 апр. atsakymas pateikiamas alex 22 balandžio. 2012-04-22 14:23 '12 12:23 PM 2012-04-22 14:23
  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; } } 

Šis metodas yra kelis kartus greitesnis nei BinarySerialization ir tai nereikalauja atributo [Serializable] .

0
04 июля '11 в 16:57 2011-07-04 16:57 atsakymas pateikiamas Bazilikui liepos 4 d., 11 d., 16:57 2011-07-04 16:57

Atrodo, kad MSDN dokumentai rodo, kad Klonas turėtų atlikti gilų kopiją, tačiau niekada aiškiai nenurodyta:

„ICloneable“ sąsaja turi vieną „Clone“ narį, kuris yra skirtas palaikyti „OnWiseClone“ teikiamą ne linijos klonavimą ... „MemberwiseClone“ metodas sukuria seklią kopiją ...

Mano žinutė gali būti naudinga.

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

0
13 февр. atsakymą pateikė Eugenijus vasario 13 d. 2013-02-13 16:50 '13, 16:50, 2013-02-13 16:50

Turiu paprastesnę idėją. Naudokite LINQ su nauju pasirinkimu.

 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}; } 
-5
25 янв. Atsakymą pateikė Jordan Morris sausio 25 d 2013-01-25 14:42 13 iš 14:42 2013-01-25 14:42

Kiti klausimai apie žymes arba Užduoti klausimą