simple - c# shallow copy




Como você faz uma cópia profunda de um objeto no.net(c#especificamente)? (8)

Esta questão já tem uma resposta aqui:

Eu quero uma cópia verdadeira e profunda. Em Java, isso foi fácil, mas como você faz isso em C #?


A documentação do MSDN parece sugerir que o Clone deve executar uma cópia profunda, mas nunca é explicitamente declarado:

A interface ICloneable contém um membro, Clone, que é destinado a oferecer suporte à clonagem além do fornecido pelo MemberWiseClone… O método MemberwiseClone cria uma cópia superficial…

Você pode achar minha postagem útil.

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


A melhor maneira é:

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

Eu escrevi um método de extensão de cópia de objeto profundo , baseado em "MemberwiseClone" recursivo. É rápido ( três vezes mais rápido que o BinaryFormatter) e funciona com qualquer objeto. Você não precisa de um construtor padrão ou de atributos serializáveis.

Código fonte:

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

}

Eu acredito que a abordagem BinaryFormatter é relativamente lenta (o que foi uma surpresa para mim!). Você pode usar o ProtoBuf .NET para alguns objetos se eles atenderem aos requisitos do ProtoBuf. A partir da página ProtoBuf Getting Started ( http://code.google.com/p/protobuf-net/wiki/GettingStarted ):

Notas sobre os tipos suportados:

Classes personalizadas que:

  • São marcados como contrato de dados
  • Tem um construtor sem parâmetros
  • Para o Silverlight: são públicos
  • Muitos primitivos comuns, etc.
  • Matrizes de dimensão única : T []
  • Listar <T> / IList <T>
  • Dicionário <TKey, TValue> / IDictionary <TKey, TValue>
  • qualquer tipo que implemente IEnumerable <T> e tenha um método Add (T)

O código pressupõe que os tipos serão mutáveis ​​em torno dos membros eleitos. Consequentemente, as estruturas personalizadas não são suportadas, pois devem ser imutáveis.

Se sua turma atender a esses requisitos, você pode tentar:

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

Que é muito rápido mesmo ...

Editar:

Aqui está o código de trabalho para uma modificação disso (testado no .NET 4.6). Ele usa System.Xml.Serialization e System.IO. Não há necessidade de marcar as classes como serializáveis.

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

Eu vi algumas abordagens diferentes para isso, mas eu uso um método utilitário genérico como tal:

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

Notas:

  • Sua classe DEVE ser marcada como [Serializable] para que isso funcione.
  • Seu arquivo de origem deve incluir o seguinte código:

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


Você pode usar o MemberwiseClone aninhado para fazer uma cópia detalhada . É quase a mesma velocidade que copiar um valor struct e é uma ordem de magnitude mais rápida do que (a) reflexão ou (b) serialização (como descrito em outras respostas nesta página).

Observe que, se você usar o Nested MemberwiseClone para uma cópia profunda , terá que implementar manualmente um ShallowCopy para cada nível aninhado na classe e um DeepCopy que chama todos os métodos ShallowCopy para criar um clone completo. Isso é simples: apenas algumas linhas no total, veja o código de demonstração abaixo.

Aqui está a saída do código mostrando a diferença de desempenho relativo (4,77 segundos para MemberwiseCopy aninhado em profundidade vs. 39,93 segundos para serialização). Usar o MemberwiseCopy aninhado é quase tão rápido quanto copiar uma estrutura, e copiar uma estrutura é muito próximo da velocidade máxima teórica com a qual o .NET é capaz.

    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

Para entender como fazer uma cópia profunda usando o MemberwiseCopy, aqui está o projeto de demonstração:

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

Então, ligue para a demonstração do 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();
    }

Novamente, observe que, se você usar o Nested MemberwiseClone para uma cópia profunda , precisará implementar manualmente um ShallowCopy para cada nível aninhado na classe e um DeepCopy que chama todos os métodos ShallowCopy para criar um clone completo. Isso é simples: apenas algumas linhas no total, veja o código de demonstração acima.

Note que quando se trata de clonar um objeto, existe uma grande diferença entre uma "struct" e uma "class":

  • Se você tem uma "struct", é um tipo de valor para que você possa copiá-lo, e o conteúdo será clonado.
  • Se você tem uma "classe", é um tipo de referência, então se você copiá-lo, tudo o que você está fazendo é copiar o ponteiro para ele. Para criar um clone verdadeiro, você precisa ser mais criativo e usar um método que crie outra cópia do objeto original na memória.
  • Clonar objetos incorretamente pode levar a erros muito difíceis de serem soltos. No código de produção, tenho a tendência de implementar uma soma de verificação para verificar novamente se o objeto foi clonado corretamente e não foi corrompido por outra referência a ele. Esta soma de verificação pode ser desativada no modo Release.
  • Eu acho este método bastante útil: muitas vezes, você só quer clonar partes do objeto, não a coisa toda. Também é essencial para qualquer caso de uso em que você esteja modificando objetos e, em seguida, alimentando as cópias modificadas em uma fila.

Atualizar

É provavelmente possível usar a reflexão para percorrer recursivamente o gráfico do objeto para fazer uma cópia profunda. O WCF usa essa técnica para serializar um objeto, incluindo todos os seus filhos. O truque é anotar todos os objetos filhos com um atributo que os torne detectáveis. Você pode perder alguns benefícios de desempenho, no entanto.

Atualizar

Cotação em teste de velocidade independente (veja comentários abaixo):

Eu executei meu próprio teste de velocidade usando o método de extensão serializar / desserializar de Neil, o método de extensão com base em reflexão de Alex Burtsev do Contango e o AutoMapper, 1 milhão de vezes cada. Serializar-desserializar foi mais lento, levando 15,7 segundos. Então veio o AutoMapper, levando 10,1 segundos. Muito mais rápido foi o método baseado em reflexão que levou 2,4 segundos. De longe, o mais rápido foi o Nested MemberwiseClone, levando 0,1 segundos. Vem para o desempenho versus o incômodo de adicionar código a cada classe para cloná-lo. Se o desempenho não é um problema, use o método de Alex Burtsev. - Simon Tewsi


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

Dessa forma, algumas vezes mais rápido que BinarySerialization E isso não requer o atributo [Serializable] .







clone