Como você faz uma cópia em profundidade de um objeto em .NET (C # especificamente)? [duplicado]

StackOverflow https://stackoverflow.com/questions/129389

  •  02-07-2019
  •  | 
  •  

Pergunta

Esta questão já tem uma resposta aqui:

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

Foi útil?

Solução

Eu vi algumas abordagens diferentes para isso, mas eu uso um método de utilidade genérica 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 marcado como [Serializable] em ordem para que isso funcione.
  • O arquivo de origem deve incluir o seguinte código:

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

Outras dicas

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

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

}

Com base na solução de Kilhoffer ...

Com C # 3.0, você pode criar um método de extensão da seguinte forma:

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

que se estende qualquer classe que tem sido marcado como [Serializável] com um método DeepClone

MyClass copy = obj.DeepClone();

Você pode usar Nested MemberwiseClone fazer uma cópia profunda . É quase a mesma velocidade que a cópia de um valor struct, e seu uma ordem de magnitude mais rápido do (a) reflexão ou (b) de serialização (como descrito em outras respostas desta página).

Note que se você usa Nested MemberwiseClone para uma cópia profunda , você tem que implementar manualmente uma cópiasuperficial para cada nível aninhado na classe, e uma deepcopy que chama todos disse métodos ShallowCopy para criar um clone completo. Este é simples:. Apenas algumas linhas no total, consulte 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 aninhada profunda vs. 39,93 segundos para serialização). Utilizando nested MemberwiseCopy quase é tão rápido como copiar um struct, e copiar um struct é muito danado perto do teórico velocidade máxima .NET é capaz de fazer, que é provavelmente muito perto da velocidade da mesma coisa em C ou C ++ (mas seria tem que correr alguns benchmarks equivalentes para verificar esta afirmação).

    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 MemberwiseCopy, aqui é 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;
    }
}

Em seguida, chame a demo da principal:

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

Mais uma vez, nota que se você usa Nested MemberwiseClone para uma cópia profunda , você tem que implementar manualmente uma cópiasuperficial para cada nível aninhado na classe, e uma deepcopy que chama todos disseram métodos ShallowCopy para criar um clone completo. Este é simples:. Apenas algumas linhas no total, consulte o código de demonstração acima

Note que, quando se trata de clonagem de um objeto, há uma grande diferença entre um "struct" e uma "classe":

  • Se você tem uma "estrutura", é um tipo de valor que você pode simplesmente copiá-lo, e os conteúdos serão clonados.
  • Se você tem uma "classe", é um tipo de referência, por isso, se você copiá-lo, tudo que você está fazendo é copiar o ponteiro para ele. Para criar um verdadeiro clone, você tem que ser mais criativo, e use um método que cria outra cópia do objeto original na memória.
  • Clonagem objetos incorretamente pode levar a erros muito difíceis de pin-down. No código de produção, que tendem a implementar uma soma de verificação para verifique que o objeto tenha sido clonado corretamente, e não foi corrompido por uma outra referência a ele. Esta verificação pode ser desligado no modo de versão.
  • I encontrar este método bastante útil: muitas vezes, você só quer partes clone do objeto, não a coisa inteira. É também essencial para qualquer caso de uso que você está modificando objetos, em seguida, alimentando as cópias modificadas em uma fila.

Atualizar

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

Atualizar

citações em teste de velocidade independente (ver comentários abaixo):

Eu executar o meu teste própria velocidade usando de Neil serialize / deserialize método de extensão, da Contango Nested MemberwiseClone, Alex Burtsev de À base de reflexão método de extensão e AutoMapper, 1 milhão de vezes cada. Serialize-deserialize foi mais lento, tendo 15,7 segundos. Então veio AutoMapper, tendo 10,1 segundos. Muito mais rápido foi o método baseado na reflexão que levou 2,4 segundos. De longe foi o mais rápido Nested MemberwiseClone, tendo 0,1 segundos. Se resume ao desempenho contra o aborrecimento de adicionar código para cada classe para cloná-lo. Se o desempenho não é um movimento problema com o método de Alex Burtsev. - Simon Tewsi

Eu acredito que a abordagem BinaryFormatter é relativamente lento (que veio como uma surpresa para mim!). Você pode ser capaz de usar Protobuf .NET para alguns objetos se cumprirem os requisitos do Protobuf. A partir da página Protobuf Introdução ( http://code.google.com/p/ protobuf-net / wiki / GettingStarted ):

Notas sobre os tipos suportados:

Classes personalizadas que:

  • são marcados como data-contrato
  • Have a parâmetros construtor
  • Para Silverlight: são públicos
  • Muitos primitivos comuns, etc.
  • Single matrizes de dimensão: T []
  • List / IList
  • Dictionary / IDictionary
  • qualquer tipo que implementa IEnumerable e tem um Add (T) método

O código assume que tipos vai ser mutável em torno dos membros eleitos. Assim, estruturas personalizados não são suportados, uma vez que deve ser imutável.

Se sua classe atende 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 na verdade ...

Editar:

Aqui está o código de trabalho para uma modificação desta (testado em .NET 4.6). Ele usa System.Xml.Serialization e System.IO. Não há necessidade de aulas de marca como serializável.

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

Você pode tentar este

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

Graças à DetoX83 artigo em código projeto.

Talvez você só precisa de uma cópia superficial, em que Object.MemberWiseClone() caso de uso.

Há boas recomendações na documentação para MemberWiseClone() de estratégias para cópia profunda: -

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

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

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

A interface ICloneable contém um membro, Clone, que se destina a apoiar a clonagem além disso fornecido pela MemberwiseClone ... O método MemberwiseClone Cria uma cópia superficial ...

Você pode encontrar meu post útil.

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

Desta forma é algumas vezes mais rápido do que BinarySerialization E isto não requer que o atributo [Serializable].

Eu tenho uma idéia simples. Use LINQ com uma nova seleção.

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};
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top