2014-02-06 3 views
2

Я должен реализовать метод расширенного расширения extclone, который можно использовать с любым экземпляром ссылочного типа, чтобы получить его глубокую копию. Я использую его как следующее:Реализация объектного глубокого клонирования

static class ClassCopy 
{ 
    static public T DeepClone<T> (this T instance) 
    { 
     if (instance == null) return null; 
     var type = instance.GetType(); 
     T copy; 
     var flags = BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.NonPublic | 
        BindingFlags.Instance; 

     var fields = type.GetFields(flags); 

     // If type is serializable - create instance copy using BinaryFormatter 
     if (type.IsSerializable) 
     { 
      using (var stream = new MemoryStream()) 
      { 
       var formatter = new BinaryFormatter(); 
       formatter.Serialize(stream, instance); 
       stream.Position = 0; 
       copy = (T) formatter.Deserialize(stream); 
      } 

      // Copy all fiels which are not marked as serializable 
      foreach (var field in fields) 
      { 
       if (!field.IsNotSerialized) continue; 
       var value = field.GetValue(instance); 

       //Recursion!!! 
       //for each embedded object also create deep copy 
       value = value != null ? value.DeepClone() : value; 
       field.SetValue(copy, value); 
      } 
     } 
     else 
     { 
      // If type is not serializable - create instance copy using Activator 
      //(if there is default constructor) 
      // or FormatterServices (if there is no constractor) 

      copy = CreateInstance<T>(type); 
      foreach (var field in fields) 
      { 
       var value = field.GetValue(instance); 

       //Recursion!!! 
       value = value != null ? value.DeepClone() : value; 
       field.SetValue(copy, value); 
      } 
     } 

     //Copy all properties 
     //In order to copy all backing fields for auto-implemented properties 

     var properties = type.GetProperties(flags|BindingFlags.SetProperty); 
     foreach (var property in properties) 
     { 
      if (property.CanWrite) 
      { 
       var value = property.GetValue(instance); 

       //Recursion!!! 
       value = value != null ? value.DeepClone() : null; 
       property.SetValue(copy, value); 
      } 
     } 
     return copy; 
    } 

    private static T CreateInstance<T>(Type t) where T: class 
    { 
     T instance; 
     var constructor = t.GetConstructor(Type.EmptyTypes); 
     if (constructor != null) 
     { 
      instance = Activator.CreateInstance(t) as T; 
      return instance; 
     } 
     instance = FormatterServices.GetUninitializedObject(t) as T; 
     return instance; 
    } 
} 

Он хорошо работает. Но если объект клонирован и его ссылочные типы полей имеют взаимные ссылки, этот код приводит к бесконечным циклам. , например.

private static void Main(string[] args) 
{ 
    var parent = new Parent(); 
    parent.Child = new Child(); 
    parent.Child.Parent = parent; 
    //Infinite Loop!!! 
    var parent1 = parent.DeepClone(); 
} 

class Parent 
{ 
    public Child Child { get; set; } 
} 
class Child 
{ 
    public Parent Parent { get; set; } 
} 

Кто-нибудь знает, как реализовать эту задачу? Это должно быть реализовано буквально, и никакие вариации не допускаются (это практический курс). Большое спасибо за любые советы!

+1

Есть причина, по которой вы не видите методы глубокого клонирования общего назначения.Чтобы быть выполненными правильно, они реалистично должны быть реализованы на основе конкретных типов, о которых идет речь. Учитывая, что вам очень нужно редко использовать такие реализации, это, как правило, не проблема. То, что вам нужно что-то подобное, на самом деле является признаком того, что что-то может быть неправильным. – Servy

+0

Проверьте https://cloneextensions.codeplex.com/ – MarcinJuraszek

+0

Вы можете попробовать этот код: http://code.msdn.microsoft.com/CSEFDeepCloneObject-12a5cb95 –

ответ

1

Что вы можете сделать, это передать Dictionary элементов, отображаемых на их клонах. Теперь этот метод будет выглядеть следующим образом:

static private T DeepClone<T> (this T instance, IDictionary<object, object> originalToAlreadyCloned) where T: class

Теперь первое, что вы делаете после того, как if (instance == null) return null;, это проверить, если instance присутствует в originalToAlreadyCloned и если да, то вернуть его.

Для того, чтобы заполнить ее, после

  1. copy = (T) formatter.Deserialize(stream);
  2. copy = CreateInstance<T>(type);

originalToAlreadyCloned.Add(instance, copy); вызов

Наконец, создание нового способа верхнего уровня:

static private T DeepClone<T> (this T instance) where T: class, который просто вызывает DeepClone(instance, new Dictionary<object, object>());

Кстати, value = value != null && value.GetType().IsClass? value.DeepClone() : null; кажется неправильным. Вы говорите, что если value не является классом, установите его равным null. Но если это не класс, вы не можете установить его равным null. Я не уверен, почему вы тоже не клонируете эти предметы, и почему DeepClone ограничен только классами.

+0

Спасибо, попробуем это. И спасибо за обнаружение ошибок - это результат 3-х комм. Кодирования. – user3101007

2

Старый трюк для объектов с глубоким клонированием состоит в сериализации и де-сериализации их, тем самым создавая новые экземпляры.

public T deepClone<T>(T toClone) where T : class 
{ 
    string tmp = JsonConvert.SerializeObject(toClone); 
    return JsonConvert.DeserializeObject<T>(tmp);    
} 

Я работал с Newtonsoft.Json и имеет встроенный в решении вашей проблемы. По умолчанию он обнаруживает, что объект уже сериализован и генерирует исключение. Тем не менее, вы можете настроить его для сериализации ссылок на объект для получения круговых ссылок. Вместо сериализации объектов в строке он сериализует ссылку на этот объект и гарантирует, что каждая ссылка на объект только сериализуется один раз.

Кроме того, по умолчанию используется только сериализация открытых полей/свойств. Существует дополнительная настройка для сериализации частных полей.

public T deepClone<T>(T toClone) where T : class 
{ 
    JsonSerializerSettings settings = new JsonSerializerSettings(); 
    settings.PreserveReferencesHandling = PreserveReferencesHandling.Objects; 

    DefaultContractResolver dcr = new DefaultContractResolver(); 
    dcr.DefaultMembersSearchFlags |= System.Reflection.BindingFlags.NonPublic; 
    settings.ContractResolver = dcr; 

    string tmp = JsonConvert.SerializeObject(toClone, settings); 
    return JsonConvert.DeserializeObject<T>(tmp); 
} 

Таким образом, можно либо «обмануть» и использовать такой код или скопировать, как она работает, чтобы реализовать клон, который сохраняет ссылку. Пример, который вы дали родительскому/ребенку, - это всего лишь одно клонирование пути. 1 для многих - другое.

+0

Не работает сериализация только с объектами Serializable, а также с сериализуемыми элементами? Я использовал двоичную сериализацию в примере, как вы можете видеть. Но это не решает проблему. – user3101007

+0

Хорошая точка. Обычно я работаю с DTO, которые являются общедоступными. Я обновил этот пример, чтобы включить частные поля. – raider33

Смежные вопросы