0

在我选择要玩的动作之前,我需要制作一个 MyGame 类的副本并在我的模拟中使用它来进行游戏试验。

例如 :

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. 会员克隆()
  2. (板)this.MemberwiseClone()
  3. Board b = (Board) this.Board

我已经阅读了很多关于这个主题的文章和论坛。大多数人使用的答案是 C# 中的深度克隆对象,我尝试了关于我的项目的示例,但我仍然得到修改原始对象(MyGame 类)而不是副本的模拟。

4

5 回答 5

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是负责的方法。然而,人们可能会粗心地期望在这里正确克隆MyAwesomeGamenew 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;
    }
}
于 2013-07-25T14:13:59.163 回答
1

实现深度克隆的最简单和最可靠的方法是序列化,然后反序列化您的对象。这可能会带来很大的性能成本。考虑来自这个命名空间的类进行序列化http://msdn.microsoft.com/en-us/library/System.Xml.Serialization.aspx

深度克隆需要递归地为每个不是值类型的属性创建一个新实例。克隆MyGame将需要 的新实例MyGame和 的新实例Board,两者都填充了与其原始相同的Start和值。Count这是一个繁琐的维护工作,也是一场噩梦。您可以猜到,它不是开箱即用的自动过程,但可以使用反射(这就是上面的 xml 序列化的工作方式。

MemberwiseClone仅创建您调用它的对象的新实例 - 所有引用保持不变。

于 2013-07-25T13:56:53.063 回答
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()using MemberwiseClone(),因为它的成员都是值类型。

如果您无法管理这个(例如因为代码不可访问)或者您需要一个快速/肮脏的解决方案,您可以考虑让用户序列化(在内存中)。哪个序列化器是一个大问题,每个都有一些限制(关于序列化的内容和方式)。例如,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);
    }
}
于 2013-07-25T13:57:07.970 回答
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;
        }
于 2013-07-25T14:02:26.847 回答
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 结果截图

我匆忙编写了这些快速而肮脏的扩展方法,因此它可能存在一些问题,而且它们的效率可能非常低,但它们似乎适用于我的用例。他们也可以帮助你。

于 2013-07-25T14:45:22.523 回答