2013-07-25 4 views
0

Мне нужно сделать копию класса MyGame и использовать его в моей симуляции для игровых испытаний, прежде чем я выберу переход к игре.Клонировать объект, не изменяя значения исходного объекта C#

Например:

public class MyGame 
    { 
    private int Start; 
    private Board board; 

    //Constructor 

    public void Play() 
    { 
     //play game 
    } 

    public object Clone() 
    {  


    } 

    } 

public class Board 
    { 
     private int Count; 

     //Constructor 

     //Some methods and properties 

    public object Clone() 
    {  

    } 

    } 

написания кода для метода Clone() Я попытался

  1. MemberwiseClone()
  2. (Board) this.MemberwiseClone()
  3. совета б = (Board) this.Board

Я читал много статей и форумов по этой теме. Ответ большинству людей использование объектов глубокого клонирования в C#, я пробовал выборки по отношению к моему проекту, но я все же получил мое симуляцию, модифицирующую исходный объект (класс MyGame), а не копию.

+0

Я думаю, что вы возились со ссылкой на новый объект (если ваше клонирование успешно). – Guanxi

+0

Член-клон не работает, если члены являются ** ссылочными типами ** (он будет клонировать указатели, а не заострять объекты). Каждый объект должен поддерживать клонирование. Грязным способом (для сериализуемых объектов) может быть ** сериализация корневого объекта в памяти ** (а затем десериализация клонированного). –

+0

Как вы называете Clone() и как/при изменении кода? –

ответ

4

Здесь у меня есть пример для глубокой копии, которая глубоко копирует все ссылочные объекты , которые используются с конструктором копии:

public sealed class MyGame 
{ 
    private int start; 
    private Board board; 

    public MyGame(MyGame orig) 
    { 
     // value types - like integers - can easily be 
     // reused 
     this.start = orig.start; 

     // reference types must be clones seperately, you 
     // must not use orig.board directly here 
     this.board = new Board(orig.board); 
    } 
} 

public sealed class Board 
{ 
    private int count; 

    public Board(Board orig) 
    { 
     // here we have a value type again 
     this.count = orig.count; 

     // here we have no reference types. if we did 
     // we'd have to clone them too, as above 
    } 
} 

Я думаю, что ваша копия может быть каким-то образом неглубоко и повторно использовать некоторые ссылки (как, например, this.board = orig.board вместо создания нового доски). Это предположение, хотя я не вижу вашей реализации клонирования.

Кроме того, я использовал копии конструкторов вместо внедрения ICloneable. Реализация почти такая же. Одним из преимуществ является то, что хотя вы облегчите дело с подклассов:

Предположим, вы имели MyAwesomeGame : MyGame, не переопределяемMyGame.Clone. Что бы вы получили от myAwesomeGame.Clone()? На самом деле, все еще новый MyGame, потому что MyGame.Clone - это метод. Однако можно небрежно ожидать, что здесь правильно клонировано MyAwesomeGame. new MyGame(myAwesomeGame) все еще копирует как-то не полностью, но это более очевидно. В моем примере я сделал классы sealed, чтобы избежать этих сбоев. Если вы можете их запечатать, есть хорошие изменения, это упростит вашу жизнь.

Реализация ICloneable не рекомендуется вообще, см. Why should I implement ICloneable in c#? для получения более подробной и общей информации.

Здесь я в ICloneable подход в любом случае, чтобы сделать вещи полной и позволит вам сравнить и контрастность:

public class MyGame : ICloneable 
{ 
    private int start; 
    private Board board; 

    public object Clone() 
    { 
     var copy = new MyGame(); 
     copy.start = this.start; 
     copy.board = (Board)this.board.Clone(); 
     return copy; 
    } 
} 

public class Board : ICloneable 
{ 
    private int count; 

    public object Clone() 
    { 
     var copy = new Board(); 
     copy.count = this.count; 
     return copy; 
    } 
} 
+1

Хорошо читать. Так что теперь я смущен .... о том, какой подход. Я собираюсь реализовать оба, которые когда-либо работают для меня, я буду работать с этим. Благодарю. –

+0

@CrateDuke Прошу прощения за то, что вы сбиваете с толку. Реализация очень похожа на «ICloneable», и оба подхода будут работать. Удачи в реализации и не стесняйтесь спрашивать, когда вам нужна дополнительная помощь. –

+1

IClonable - это санкционированный Microsoft метод. Конструктор копирования наиболее доступен для поиска. Нет причин, по которым метод Clone не может вызвать конструктор копирования. Затем вы получаете оба шаблона бесплатно! – Gusdor

1

Самый простой и надежный способ реализации глубокого клонирования - сериализовать, а затем десериализовать объекты. Это может иметь большие эксплуатационные расходы, связанные с этим. Рассмотрим классы из этого пространства имен для сериализации http://msdn.microsoft.com/en-us/library/System.Xml.Serialization.aspx

Глубокое клонирование требует рекурсивного создания нового экземпляра каждый Свойство, которое не является типом значения. Для клонирования MyGame потребуется новый экземпляр MyGame и новый экземпляр Board, оба из которых заполнены теми же Start и Count значениями в качестве их оригиналов. Это неудобно и кошмар для поддержания. Как вы можете догадаться, это не автоматический процесс из коробки, но он может быть, используя отражение (как работает сериализация xml выше.

MemberwiseClone создает только новый экземпляр объекта, на который вы его назвали, все ссылки остаются неизменными.

1

MemberwiseClone() создает глупое неглубокого клона каждый члена объекта. Это хорошо работает, когда члены являются типами значений, но в случае ссылочных типов это не удается, потому что это будет клонировать указатели и не указало Объекты

Начиная с вашего кода, членный клон примерно такой:

public object Clone() 
{ 
    MyGame cloned = new MyGame(); 
    cloned.Start = this.Start; // Copied (cloned) because value type 
    cloned.Board = this.Board; // This is not a copy, just a reference! 
} 

Лучшее решение для глубокого клона было бы реализовать ICloneable (например, в противном случае конструктор копирования подход также хорошо) для каждого ссылочного типа, давайте предположим, что Board является клонируемыми тоже:

public object Clone() 
{ 
    MyGame cloned = new MyGame(); 
    cloned.Start = this.Start; 
    cloned.Board = (Board)this.Board.Clone(); 
} 

Обратите внимание, что в вашем примере Board может реализовать Clone() с использованием MemberwiseClone(), поскольку его члены являются всеми типами значений.

Если вы не можете управлять этим (например, потому что код не доступен), или вам нужно быстрое/грязное решение, которое вы можете использовать для пользователя serializaiton (в памяти). Какой сериализатор - большой вопрос, у каждого есть некоторые ограничения (о том, что сериализовано и как). Например, XML-сериализатор не будет сериализовать частные поля (он вообще не будет сериализовать поля). Более быстрый - двоичный форматтер, но вам нужно отметить каждый класс соответствующим атрибутом.

Изменения по сериализатору вы предпочитаете (в соответствии с вашими требованиями), в этом случае, я полагаю, вы отметили MyGame и Board в [Serializable] для быстрой бинарной серийности:

public object Clone() 
{ 
    using (var stream = new MemoryStream()) 
    { 
     var formatter = new BinaryFormatter(); 
     formatter.Serialize(stream, this); 
     stream.Seek(0, SeekOrigin.Begin); 

     return formatter.Deserialize(stream); 
    } 
} 
+0

Я думаю * глупая копия * может быть * мелкой копией * если я правильно понимаю. Это правильно и, возможно, стоит изменить? –

+0

@MatthiasMeid абсолютно, моим английским иногда нужны праздники ... –

+0

@Adriano это означает, что любой объект внутри любого из этих классов также должен быть [Serializable]? –

0

Попробуйте

public static T DeepCopy<T>(this T obj) 
     { 
      T result; 
      var serializer = new DataContractSerializer(typeof(T)); 
      using (var ms = new MemoryStream()) 
      { 

       serializer.WriteObject(ms, obj); 
       ms.Position = 0; 

       result = (T)serializer.ReadObject(ms); 
       ms.Close(); 
      } 

      return result; 
     } 
+2

Просто код для копирования и вставки без каких-либо объяснений не будет полезен будущим посетителям с аналогичными проблемами, и он не будет полезен OP, если он или она хочет * понять *, что происходит. –

0

У меня есть два метода расширения, которые я использую для достижения этой цели. Демонстрационный код ниже:

using System; 
using System.Collections.Generic; 
using System.Diagnostics; 
using System.Linq; 
using System.Reflection; 

namespace SimpleCloneDemo 
{ 
    public class Program 
    { 
     public static void Main(string[] args) 
     { 
      var person = new Person { Id = 1, FirstName = "John", Surname = "Doe" }; 

      var clone = person.Clone(); 
      clone.Id = 5; 
      clone.FirstName = "Jane"; 

      Console.WriteLine(@"person: {0}", person); 
      Console.WriteLine(@"clone: {0}", clone); 

      if (Debugger.IsAttached) 
       Console.ReadLine(); 
     } 
    } 

    public class Person 
    { 
     public int Id { get; set; } 

     public string FirstName { get; set; } 

     public string Surname { get; set; } 

     public override string ToString() 
     { 
      return string.Format("Id: {0}, Full Name: {1}, {2}", Id, Surname, FirstName); 
     } 
    } 

    public static class ObjectExtensions 
    { 
     public static T Clone<T>(this T entity) where T : class 
     { 
      var clone = Activator.CreateInstance(entity.GetType()); 
      var entityPropValueDictionary = entity.AsPropValueDictionary(); 
      foreach (var prop in clone.GetType().GetProperties()) 
      { 
       clone.GetType().GetProperty(prop.Name).SetValue(clone, entityPropValueDictionary[prop.Name]); 
      } 
      return clone as T; 
     } 

     public static IDictionary<string, object> AsPropValueDictionary<T>(this T instance, params BindingFlags[] bindingFlags) 
     { 
      var runtimeBindingFlags = BindingFlags.Default; 

      switch (bindingFlags.Count()) 
      { 
       case 0: 
        runtimeBindingFlags = BindingFlags.Default; 
        break; 
       case 1: 
        runtimeBindingFlags = bindingFlags[0]; 
        break; 
       default: 
        runtimeBindingFlags = bindingFlags.Aggregate(runtimeBindingFlags, (current, bindingFlag) => current | bindingFlag); 
        break; 
      } 

      return runtimeBindingFlags == BindingFlags.Default 
       ? instance.GetType().GetProperties().ToDictionary(prop => prop.Name, prop => prop.GetValue(instance)) 
       : instance.GetType().GetProperties(runtimeBindingFlags).ToDictionary(prop => prop.Name, prop => prop.GetValue(instance)); 
     } 
    } 
} 

Результат:

SimpleCloneDemo Result Screenshot

Я написал эти быстрые и грязные методы расширения в спешке, так что, вероятно, некоторые проблемы, связанные с ним, и они, вероятно, ужасно неэффективны, но они, похоже, работали для моего прецедента. Они могут помочь вам.

+0

Будет ли это делать глубокие копии любых свойств, которые являются ссылочными типами? –

+0

@ChrisDunaway Я пробовал этот код ... но у вас есть подкласс в основном классе, тогда у вас все еще будут проблемы. Самое лучшее, что до сих пор работало для меня, это Copy Constructor. –

+0

@CrateDuke да, я заметил, что ... к сожалению, мой код работает только в простейшем случае, и я не вижу, как можно было бы обобщить «Activator.CreateInstance», чтобы определить, какой конструктор использовать для создания клона экземпляра, без какой-либо тяжелой рефлексии. Конструктор копирования на самом деле обманчиво простое решение - мне это нравится! –