Как сделать глубокую копию объекта in.NET (в частности, C#)? [дубликат]

этот вопрос уже есть ответ здесь:

Я хочу настоящую глубокую копию. На Java это было легко, но как это сделать на C#?

11 ответов


Я видел несколько разных подходов к этому, но я использую универсальный метод утилит, как например:

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

Примечания:

  • ваш класс должен быть помечен как [Serializable] для того, чтобы это работало.
  • ваш исходный файл должен содержать следующий код:

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

Я написал метод расширения глубокой копии объекта на основе рекурсивных "MemberwiseClone". Это быстро (три раза быстрее чем BinaryFormatter), и он работает с любым объектом. Вам не нужен конструктор по умолчанию или сериализуемые атрибуты.


основываясь на решении Килхоффера...

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

который расширяет любой класс, который был отмечен как [Serializable] с помощью метода DeepClone

MyClass copy = obj.DeepClone();

можно использовать вложенные MemberwiseClone сделать глубокую копию. Его почти такая же скорость, как копирование структуры значений, и на порядок быстрее, чем (a) отражение или (b) сериализация (как описано в других ответах на этой странице).

отметим, что если вы используете вложенный MemberwiseClone для глубокой копии, вы должны вручную реализовать ShallowCopy для каждого вложенного уровня в классе и DeepCopy, который вызывает все указанные ShallowCopy методы создания полного клона. Это просто: всего несколько строк, смотрите демо-код ниже.

вот вывод кода, показывающий относительную разницу в производительности (4,77 секунды для глубокого вложенного MemberwiseCopy против 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:

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

еще раз, обратите внимание, что если вы используете вложенный MemberwiseClone для глубокой копии, вы должны вручную реализовать ShallowCopy для каждого вложенного уровня в классе и DeepCopy, который вызывает все указанные методы ShallowCopy для создания полного клона. Это просто: всего несколько строк, см. демо-код выше.

обратите внимание, что когда дело доходит до клонирования объекта, существует большая разница между "структурой" и "класс":

  • если у вас есть" структура", это тип значения, поэтому вы можете просто скопировать его, и содержимое будет клонировано.
  • если у вас есть "класс", это ссылочный тип, поэтому если вы скопируете его, вы просто копируете ссылку на него. Чтобы создать истинный клон, вы должны быть более творческим и использовать метод, который создает другую копию исходного объекта в память.
  • неправильное клонирование объектов может привести к очень сложным ошибкам. В производственном коде я склонен реализовать контрольную сумму, чтобы дважды проверить, что объект был клонирован правильно и не был поврежден другой ссылкой на него. Эту контрольную сумму можно отключить в режиме Release.
  • я нахожу этот метод довольно полезным: часто вы хотите клонировать только части объекта, а не всю вещь. Это также важно для любого случая использования, когда вы изменяете объекты, затем подача измененных копий в очередь.

обновление

вероятно, можно использовать отражение для рекурсивного обхода графа объектов, чтобы сделать глубокую копию. WCF использует этот метод для сериализации объекта, включая все его дочерние элементы. Трюк состоит в том, чтобы аннотировать все дочерние объекты атрибутом, который делает его обнаруживаемым. Вы можете потерять некоторые преимущества производительности, однако.

обновление

цитата на независимом тесте скорости (см. комментарии ниже):

я запустил свой собственный тест скорости, используя сериализацию/десериализацию Нила метод расширения, вложенный MemberwiseClone контанго, Алексей Бурцев метод расширения на основе отражения и AutoMapper, 1 миллион раз каждый. Сериализация-десериализация была самой медленной, занимая 15,7 секунды. Затем пришел AutoMapper, заняв 10,1 секунды. Гораздо быстрее был отражение на основе метод, который занял 2,4 секунды. Самым быстрым был Вложенный MemberwiseClone, занимающий 0,1 секунды. Сводится к производительности против хлопот добавления кода в каждый класс, чтобы клонировать его. Если производительность с методом Алексея Бурцева проблем нет. – Симон Tewsi


Я считаю, что подход BinaryFormatter относительно медленный (что стало для меня сюрпризом!). Вы можете использовать ProtoBuf .NET для некоторых объектов, если они отвечают требованиям ProtoBuf. Со страницы "начало работы ProtoBuf" (http://code.google.com/p/protobuf-net/wiki/GettingStarted):

заметки о поддерживаемых типах:

пользовательские классы, которые:

  • отмечены как data-contract
  • есть конструктор без параметров
  • для Silverlight: являются публичными
  • много общих примитивов, etc.
  • один размерные массивы: T[]
  • Список / IList
  • Словарь / IDictionary
  • любой тип, который реализует IEnumerable и имеет метод Add (T)

код предполагает, что типы будут изменяться вокруг избранных членов. Соответственно, таможня структуры не поддерживаются, так как они должны быть неизменяемыми.

Если ваш класс соответствует этим требованиям, вы можете попробовать:

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). Он использует систему.XML.Сериализация и система.ИО. Нет необходимости отмечать классы как сериализуемые.

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

вы можете попробовать это

    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 статьи на код проекта.


Возможно, вам нужна только мелкая копия, в этом случае используйте Object.MemberWiseClone().

в документации есть хорошие рекомендации для MemberWiseClone() для стратегий глубокого копирования: -

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


лучший способ-это:

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

документация MSDN, похоже, намекает, что клон должен выполнять глубокую копию, но она никогда не указывается явно:

интерфейс ICloneable содержит один элемент, Клон, который предназначен для поддержки клонирования за пределами, предоставленных MemberWiseClone ... метод MemberwiseClone создает мелкую копию...

вы можете найти мой пост окажется полезным.

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


    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 и для этого не требуется .


У меня есть более простая идея. Используйте 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};
}