Programowanie w języku C# » Gotowce

Deep Copy -klonowanie obiektow

  • 2011-03-06 11:11
  • 0 komentarzy
  • 2184 odsłony
  • Oceń ten tekst jako pierwszy
Spis treści

     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