Deep Copy -klonowanie obiektow

Deti

1 Wstęp
2 Kod źródłowy
3 Przykład użycia
4 Ograniczenia

Wstęp

Wiele razy może zajść potrzeba sklonowania dowolnego obiektu w naszym programie. Klonowanie obiektów może być:

  • płytkie (Shallow Copy) - zostaje utworzony nowy obiekt ze skopiowanymi strukturami (wszystkie typy proste), natomiast pola referencyjne (klasy) nie są kopiowane - następuje jedynie przepisanie wskaźnika.

  • głębokie (Deep Copy) - wszystkie pola (proste i referencyjne) są kopiowane.

Pierwszy typ klonowania można uzyskać bardzo łatwo - dziedzicząc po ICloneable i używając metody MemberwiseClone().
Gotowiec ten pokazuje metodę na kopiowanie głębokie.

Kod źródłowy

Cała logika zawiera się w klasę HCloner:

using System;
using System.Reflection;
using System.Collections.Generic;

namespace HAKGERSoft {

    public static class HCloner {

        public static T DeepCopy<T>(T obj){
            if(obj==null)
                throw new ArgumentNullException("Object cannot be null");
            return (T)Process(obj,new Dictionary<object,object>(){ });
        }

        static object Process(object obj,Dictionary<object,object>circular) {
            if(obj==null)
                return null;
            Type type=obj.GetType();
            if(type.IsValueType || type==typeof(string)) {
                return obj;
            }
            if(circular.ContainsKey(obj))
                return circular[obj];
            if(type.IsArray) {
                string typeNoArray=type.FullName.Replace("[]",string.Empty);
                Type elementType=Type.GetType(string.Format("{0}, {1}",typeNoArray,type.Assembly.FullName));
                var array=obj as Array;
                Array copied=Array.CreateInstance(elementType,array.Length);
                circular[obj]=copied;
                for(int i=0; i<array.Length; i++) {
                    object element=array.GetValue(i);
                    object copy=null;
                    if(element!=null && circular.ContainsKey(element))
                        copy=circular[element];
                    else
                        copy=Process(element,circular);
                    copied.SetValue(copy,i);
                }
                return Convert.ChangeType(copied,obj.GetType());
            }
            if(type.IsClass) {
                object toret=Activator.CreateInstance(obj.GetType());
                circular[obj]=toret;
                FieldInfo[] fields=type.GetFields(BindingFlags.Public|BindingFlags.NonPublic|BindingFlags.Instance);
                foreach(FieldInfo field in fields) {
                    object fieldValue=field.GetValue(obj);
                    if(fieldValue==null)
                        continue;
                    object copy=circular.ContainsKey(fieldValue)?circular[fieldValue]: Process(fieldValue,circular);
                    field.SetValue(toret,copy);
                }
                return toret;
            }
            else
                throw new ArgumentException("Unknown type");
        }

    }
}

Przykład użycia

Nic prostszego:

SimpleTestObject sto=new SimpleTestObject();
SimpleTestObject copy=HCloner.DeepCopy(sto);

.. gdzie SimpleTestObject to nasza klasa testowa.

Ograniczenia

  • nie kopiuje już przypisanych delegatów do zdarzeń
  • nie radzi sobie z polami readonly
  • nie radzi sobie z klasami, które nie mają publicznego konstruktora bez parametrów

0 komentarzy