【问题标题】:Clone object without changing the values of the original object C#克隆对象而不更改原始对象的值 C#
【发布时间】:2013-07-25 23:03:57
【问题描述】:

在我选择要玩的动作之前,我需要复制一个 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. (板)this.MemberwiseClone()
  3. Board b =(Board)this.Board

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

【问题讨论】:

  • 我认为你搞砸了对新对象的引用(如果你的克隆成功的话)。
  • 如果成员是引用类型,则按成员方式克隆不起作用(它将克隆指针,而不是指向对象)。每个对象都必须支持克隆。一种肮脏的方式(对于可序列化的对象)可以是序列化内存中的根对象(然后反序列化克隆的对象)。
  • 如何调用 Clone() 以及如何/何时修改代码?
  • 哦,好吧抱歉我忘了,每个类都实现了 ICloneable 接口。
  • 只能创建一个新对象并将其属性设置为与原始对象相同的值吗?

标签: c# winforms


【解决方案1】:

这里我有一个深拷贝的例子,它深拷贝所有与拷贝构造函数一起使用的引用类型对象:

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;
    }
}

【讨论】:

  • 好书。所以现在我很困惑....关于哪种方法。我将同时实施这两种方法,这对我有用,我会使用它。谢谢。
  • @CrateDuke 很抱歉让您感到困惑。实现与ICloneable 非常相似,两种方法都可以工作。祝您实施顺利,当您需要进一步帮助时,请随时提出。
  • IClonable 是 Microsoft 认可的方法。复制构造函数是最容易发现的。 Clone 方法没有理由不能调用复制构造函数。然后你就可以免费获得这两种模式了!
【解决方案2】:

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

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

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

【讨论】:

    【解决方案3】:

    MemberwiseClone() 为对象的每个成员创建一个 stupid 浅克隆。当成员是值类型时这很好用,但在引用类型的情况下它会失败,因为它会克隆指针而不是指向对象。

    从您的代码开始,一个成员克隆是这样的:

    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 可以使用 MemberwiseClone() 实现 Clone(),因为它的成员都是值类型。

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

    【讨论】:

    • 如果我理解正确的话,我认为 stupid copy 可能是 shallow copy。这是正确的,也许值得编辑?
    • @Adriano 这意味着任何此类中的任何对象也必须是 [Serializable] ?
    • @CrateDuke 是的。这是使用序列化程序所要付出的代价(每个都有自己的局限性,有时并不那么明显)。这就是为什么它是一个快速/肮脏的解决方案,IMO 最好的方法是一个 explicit ICloneable 实现(或者一个复制构造函数,即使它有点冗长并且你不会受益于 MemberwiseClone 如果适用)。
    • +1 用于简化序列化实现。 ICloneable 的一个问题是它没有指定副本的深度或浅度:它没有指定克隆操作是执行深拷贝、浅拷贝还是介于两者之间。 @ 987654321@
    • @MatthiasMeid 我同意,在文献中每个人都建议使用其他东西(自定义界面但具有明确定义的行为)。 ICloneable 旨在成为一个深度克隆(适用于它的成员),但它没有足够的文档记录(或者当时人们不理解其中的区别)所以现在不鼓励它(甚至在框架内部也有这个规则的例外, 责备!)。我仍然使用它(当我需要它时),因为我觉得在完全相同的可用时引入一个新界面会更令人困惑(我只是记录它是一个深度克隆)但是......这只是我的意见。
    【解决方案4】:

    试试这个

       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;
            }
    

    【讨论】:

    • 只是复制和粘贴的代码,没有任何解释,对未来有类似问题的访问者没有帮助,如果他或她想理解,对OP也没有真正的帮助> 发生了什么。
    【解决方案5】:

    我有两种扩展方法来实现这一点。演示代码如下:

    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));
            }
        }
    }
    

    结果:

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

    【讨论】:

    • 这是否会对引用类型的任何属性进行深度复制?
    • @ChrisDunaway 我试过这段代码......但如果你在主类中有一个子类,那么你仍然会遇到问题。迄今为止对我有用的最好的是复制构造函数。
    • @CrateDuke 是的,我注意到......不幸的是,我的代码只适用于最简单的情况,我看不出如何推广 Activator.CreateInstance 来确定使用哪个构造函数来创建传入的实例,没有一些繁重的反射。复制构造函数实际上是一个看似简单的解决方案——我喜欢它!
    猜你喜欢
    • 2016-02-21
    • 1970-01-01
    • 2017-07-20
    • 1970-01-01
    • 2020-03-03
    • 1970-01-01
    • 2015-01-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多