Как вы делаете глубокую копию объекта в .NET(например, С#)?

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

Ответ 1

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

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;
    

Ответ 2

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

Исходный код:

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

}

Ответ 3

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

С С# 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();

Ответ 4

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

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

Вот выходные данные кода, показывающие относительную разницу в производительности (4,77 секунды для глубоко вложенного MemberwiseCopy против 39,93 секунды для сериализации). Использование вложенного MemberwiseCopy почти так же быстро, как копирование структуры, а копирование структуры чертовски близко к теоретической максимальной скорости, на которую способна .NET, что, вероятно, довольно близко к скорости того же самого в C или C++ ( но придется проверить некоторые эквивалентные тесты, чтобы проверить это утверждение).

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

Затем вызовите демо из основного:

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

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

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

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

Обновить

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

Обновить

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

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

Ответ 5

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

Примечания по поддерживаемым типам:

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

  • Помечены как данные контракта
  • Иметь конструктор без параметров
  • Для Silverlight: общедоступны
  • Много общих примитивов и т.д.
  • Одноразмерные массивы: T []
  • Список <T>/IList<T>
  • Словарь <TKey, TValue>/IDictionary <TKey, TValue>
  • любой тип, который реализует IEnumerable <T> и имеет метод 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);
    }
}

Что действительно очень быстро...

Редактировать:

Вот рабочий код для модификации этого (проверено на.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);
    }
}

Ответ 6

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

    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 в проекте кода.

Ответ 7

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

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

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

Ответ 8

Лучший способ:

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

Ответ 9

    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].

Ответ 10

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

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

Вы можете найти мое сообщение полезным.

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

Ответ 11

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