克隆对象而不更改原始对象的值C#

时间:2013-07-25 13:45:36

标签: c# winforms

我需要制作一个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. MemberwiseClone()
  2. (Board)this.MemberwiseClone()
  3. 董事会b =(董事会)this.Board
  4. 我已经阅读了很多关于这个主题的文章和论坛。答案最多的人 使用是C#中的深度克隆对象,我尝试了关于我的项目的样本,但我仍然 让我的模拟修改原始对象(MyGame类)而不是副本。

5 个答案:

答案 0 :(得分: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;
    }
}

答案 1 :(得分:1)

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

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

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

答案 2 :(得分: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(例如,否则复制构造函数方法也很好),假设{{1}也是可复制的:

Board

请注意,在您的示例中,public object Clone() { MyGame cloned = new MyGame(); cloned.Start = this.Start; cloned.Board = (Board)this.Board.Clone(); } 可以使用Board实施Clone(),因为其成员都是值类型。

如果你无法管理(例如因为代码不可访问)或者你需要一个快速/脏的解决方案,你可以考虑用户serializaiton(在内存中)。哪个序列化程序是一个很大的问题,每个都有一些限制(关于什么是序列化的以及如何)。例如,XML序列化程序不会序列化私有字段(它根本不会序列化字段)。更快的是二进制格式化程序,但您需要使用适当的属性标记每个类。

根据您喜欢的序列化程序进行更改(根据您的要求),在这种情况下,我假设您将MemberwiseClone()MyGame标记为Board以进行快速二进制序列化:

[Serializable]

答案 3 :(得分: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;
        }

答案 4 :(得分: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

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