如何为List Class实现ICloneable? [英] How to implement ICloneable for a List Class?

查看:65
本文介绍了如何为List Class实现ICloneable?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经查看了几个这样的示例,但似乎无法正确实现它。我正在写一个游戏,想要对游戏块进行备份,这样玩家就可以撤消一个移动,阶段或转弯。我的所有副本似乎都是浅拷贝,我想用ICloneable实现深度拷贝,而不是使用ConvertAll,但经过几天没有得到任何工作,我已经准备好尝试任何东西。我想要一个简单,直接的解决方案。以下是我所得到的样本:



I've looked over several examples for this but can't seem to implement it properly. I'm writing a game and want to make a backup of the game pieces so the player can 'undo' a move, phase, or turn. All my copies seem to be shallow copies, and I'd like to implement a deep copy with ICloneable instead of using ConvertAll, but after several days of not getting anything to work, I'm ready to try anything. I'd like a simple, straight forward, solution though. Here's a sample of what I've got:

using System;
using System.Configuration;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace CiG
{
    public partial class CiG : Form
    {	
		public List<UnitsInPlayData> UiP = new List<UnitsInPlayData>();

		public class UnitsInPlayData
		{
			public int Item1 { get; set; }
			public string Item2 { get; set; }
			public bool Item3 { get; set; }
			public int[] Item4 { get; set; }
		}
	}
}





有人可以让我再次前进吗?谢谢!



Can someone please get me moving forward again? Thanks!

推荐答案

如果你想一点,你可能会看到, IClonable 赢了' t帮助你实现深度克隆,因为实现这个接口的库类型实现了,只是浅层克隆。



问题是假设的递归浅层克隆。理想情况下,您需要根据接口实现克隆所有对象,并尝试克隆所有引用而不是复制它们,但是如何?某些其他对象的引用可能实现 IClonable ,但该实现可能会或可能不会很深。实际上,界面意味着实现很浅。



你如何解决这个问题?其中一种方法可能是:实现并行机制。假设您希望引入另一个接口,假设实现它的对象将实现深度克隆:

If you think a bit, you will probably be able to see, that IClonable won't help you to implement deep cloning, because the library type implementing this interface implement, well, just the shallow cloning.

The problem is the recursion assumed by the idea of shallow cloning. Ideally, you would need to clone all objects according to the interface implementation and try to clone all references instead of copying them, but how? A reference of some other object may implement IClonable, but that implementation may or may not be deep. Actually, the interface implies that the implementation is shallow.

How can you work around this problem? One of approaches could be this: implement the parallel mechanism. Let's say, you want to introduce another interface assuming that the objects implementing it would implement the deep cloning:
    public interface IDeepCloneable {
        IDeepCloneable DeepClone();
    } //interface IDeepCloneable
}



但是,你不能保证实现这个接口的类型实际上会实现深度克隆,但是你可以要求他们这样做并假设他们确实这样做了。在这种假设下,您甚至可以实现通用深度克隆算法,您可以将其传递给某些基本深度可克隆类的派生类,并使用所需的递归。然后它看起来像这样:


Still, you cannot guarantee that the types implementing this interface will actually implement deep cloning, but you can require that they do it and assume that they actually do. Under this assumption, you can even implement "universal" deep cloning algorithm which you can pass to the derived classes of some base "deep clonable" class and use the required recursion. Then it could look like this:

using System.Reflection;

//...

    public class DeepClonable : IDeepCloneable {
        IDeepCloneable IDeepCloneable.DeepClone() {
            Type deepClonableType = typeof(IDeepCloneable);
            FieldInfo[] fields = this.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
            IDeepCloneable clone = new DeepClonable();
            foreach (FieldInfo field in fields) {
                Type fieldType = field.FieldType;
                object fieldValue = field.GetValue(this);
                if (fieldValue == null) {
                    field.SetValue(clone, fieldValue);
                    continue;
                } //if 
                if (deepClonableType.IsAssignableFrom(fieldType)) { // deep:
                    IDeepCloneable fieldClone = ((IDeepCloneable)fieldValue).DeepClone();
                    field.SetValue(clone, fieldClone);
                } else // shallow:
                    field.SetValue(clone, fieldValue);
            } //loop
            return clone;            
        } //IDeepCloneable.DeepClone
    } //class DeepClonable









另外,您还可以使用 ICloneable 与上述相结合,提供后退机制使用 ICloneable 对象实现 ICloneable 进行浅层克隆:





Alternatively/additionally, you can use ICloneable in combination with the above, to provide a fall-back mechanism using ICloneable objects not implementing ICloneable for shallow cloning:

public class DeepClonable : IDeepCloneable {
    IDeepCloneable IDeepCloneable.DeepClone() {
        Type deepClonableType = typeof(IDeepCloneable);
        Type clonableType = typeof(ICloneable);
        FieldInfo[] fields = this.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
        IDeepCloneable clone = new DeepClonable();
        foreach (FieldInfo field in fields) {
            Type fieldType = field.FieldType;
            object fieldValue = field.GetValue(this);
            if (fieldValue == null) {
                field.SetValue(clone, fieldValue);
                continue;
            } //if
            if (deepClonableType.IsAssignableFrom(fieldType)) { // deep:
                IDeepCloneable fieldClone = ((IDeepCloneable)fieldValue).DeepClone();
                field.SetValue(clone, fieldClone);
            } else if (clonableType.IsAssignableFrom(fieldType)) { // shallow, based in ICloneable
                object fieldClone = ((ICloneable)fieldValue).Clone();
                field.SetValue(clone, fieldClone);
            } else // shallowest :-) :
                field.SetValue(clone, fieldValue);
        } //loop
        return clone;
    } //IDeepCloneable.DeepClone
} //class DeepClonable





[结束编辑#1]



请注意,在这个通用实现中,我们需要假设实现接口的类型也支持某些构造函数通用签名,比如说,无参数构造函数,如代码所示。但是,我们不需要假设在递归期间我们可能遇到的其他 IDeepCloneable 实现,因为其他实现可能不同,我们只使用接口这一事实实施。另请注意,上面代码中使用的反射本质上很慢,因此您可能希望创建更快的 ad-hoc 实现。另请注意,您不仅可以通过结构( struct )按类实现任何接口。例如:



[END EDIT #1]

Note that in this "universal" implementation, we need to assume that that the type implementing the interface also support the constructor of some "universal" signature, say, parameterless constructor, as shown in the code. However, we don't need to assume it's the case for other IDeepCloneable implementations we may face during recursion, because other implementations can be different, we only use the fact that the interface is implemented. Also note that reflection used in the code above is inherently slow, so you may want to create faster ad-hoc implementation. Also note that you can implement any interface not only by classes by also by structures (struct). For example:

public struct Child {
    public int IntegerValue;
    public string Name;
    public Child(int integerValue, string name) { IntegerValue = integerValue; Name = name; }
    //...
} //struct Child

public struct Parent : IDeepCloneable {
    public Parent(bool dummy) {
        childNodes = new System.Collections.Generic.List<IDeepCloneable>();
        children = new System.Collections.Generic.List<Child>();
    } //Parent
    public System.Collections.Generic.List<IDeepCloneable> ChildNodes { get { return this.childNodes; } }
    public System.Collections.Generic.List<Child> Children { get { return this.children; } }
    System.Collections.Generic.List<IDeepCloneable> childNodes;
    System.Collections.Generic.List<Child> children;
    IDeepCloneable IDeepCloneable.DeepClone() {
        Parent clone = new Parent(false);
        foreach (Child child in this.children)
            clone.children.Add(new Child(child.IntegerValue, child.Name)); // implicit clone
        foreach (IDeepCloneable childNode in this.childNodes) {
            IDeepCloneable childClone = childNode.DeepClone(); // all recursion is here
            clone.ChildNodes.Add(childClone);
        } //loop
        return clone;
    } //IDeepCloneable.DeepClone
    //...
} //struct Parent

我故意更难以解决 struct 这个不允许无参数构造函数的情况,所以我介绍一下带有伪参数的构造函数,仅用于说明。我还为 struct Child 演示了逻辑隐式浅层克隆。



一个更精致的细节:你需要理解类 System.String ,即使它是一个引用对象,实际上根本不需要克隆。它在逻辑上表现得像一个值类型,因为它具有实习功能和实习池的使用。请参阅:

http://msdn.microsoft.com /en-us/library/system.string.intern.aspx [ ^ ],

http://msdn.microsoft.com/en-us/library/system.string.isinterned.aspx [ ^ ](无需实际使用任何这些属性)。



实习生池的说明: http://broadcast.oreilly .com / 2010/08 / understanding-c-stringintern-m.html [ ^ ]。



参见:

http://en.wikipedia.org/wiki/Recursion [ ^ ],

http://en.wikipedia.org/wiki/Recursion_%28computer_science%29 [ ^ ]。







我忘了解释你确实需要以专门的方式实现列表的克隆,因为列表没有实现 ICloneable 。由于这是你的主要关注点,它将需要一个单独的答案。



[结束编辑#2]



-SA

I have intentionally more difficult case of struct which does not allow parameterless constructor, so I introduce a constructor with a dummy parameter, just for illustration. I also demonstrated "logical" implicit shallow clone for the struct Child.

One more delicate detail: you need to understand that the class System.String, even though it is a reference object, actually does not require cloning at all. It logically behaves like a value type, due to its "interning" feature and the use of "intern pool". Please see:
http://msdn.microsoft.com/en-us/library/system.string.intern.aspx[^],
http://msdn.microsoft.com/en-us/library/system.string.isinterned.aspx[^] (no need to actually use any of these properties).

Explanation of intern pool: http://broadcast.oreilly.com/2010/08/understanding-c-stringintern-m.html[^].

See also:
http://en.wikipedia.org/wiki/Recursion[^],
http://en.wikipedia.org/wiki/Recursion_%28computer_science%29[^].



I forgot to explain that you really need to implement cloning of the list in a specialized way, because lists don't implement ICloneable. As it have been your major concern, it will take a separate answer.

[END EDIT #2]

—SA


在你的澄清之后,我想我遇到了你的问题:你真的不希望得到深刻的克隆。相反,您希望获得 ad-hoc 特定克隆,一级更深克隆,以覆盖列表元素,克隆,未引用。你迷路了。



你可以这样做:
After your clarifications, I think I got your problem: you really didn't hope to get deep cloning. Rather, you wanted to get ad-hoc specific clone, "one-level-deeper" clone, to cover your list elements, cloned, not referenced. And you are lost.

Here is how you could do it:
public partial class CiG : Form, ICloneable { // inheriting from Form is bad! I just follow your decision here

    public List<UnitsInPlayData> UiP = new List<UnitsInPlayData>();

    public class UnitsInPlayData : ICloneable {
        public int Item1 { get; set; }
        public string Item2 { get; set; }
        public bool Item3 { get; set; }
        public int[] Item4 { get; set; }
        object ICloneable.Clone() {
            UnitsInPlayData clone = new UnitsInPlayData();
            clone.Item1 = this.Item1;
            clone.Item2 = this.Item2;
            clone.Item3 = this.Item3;
            clone.Item4 = new int[this.Item4.Length];
            for (int index = 0; index < this.Item4.Length; ++index)
                clone.Item4[index] = this.Item4[index];
            return clone;
        } //ICloneable.Clone
    } //class UnitsInPlayData

    object ICloneable.Clone() {
        CiG clone = new CiG();
        foreach (UnitsInPlayData item in this.UiP)
            // untyped character of ICloneable
            // leads to ugly type casts:
            clone.UiP.Add((UnitsInPlayData)((ICloneable)item).Clone());
        return clone;
    } //ICloneable.Clone()

} //CiG





注意:你最大的战略错误不是克隆(我希望你现在得到它),但在你的类型中混合 Form 类型和数据类型 CIG 。混淆UI和数据层真的很糟糕,但这是一个单独的大谈话的主题。



我演示了ad-hoc的2级层次结构克隆,覆盖数组和列表。您确实需要在自定义级别上执行此操作,因为数组和集合未实现 ICloneable 。我在解决方案1中已经提到 ICloneable 对你不会有太大的帮助...



不过,我建议你找到更通用的解决方案。它需要加载大脑而不是手。但是,有一种更简单但更通用的方法...



Note: your biggest strategic mistake was not cloning (I hope now you got it), but mixing up Form type and data type in your type CiG. This is really bad to mix up UI and data layer, but this is a subject of a separate big talk.

I demonstrated the 2-level hierarchy of ad-hoc cloning, to cover both the array and list. You really need to do it on a custom level, because arrays and collections don't implement ICloneable. I already mentioned in Solution 1 that ICloneable would not help you too much…

Still, I would recommend to find a more universal solution. It would take loading brain rather than hands. However, there is one, easier but more universal approach…


我找到了另一种深度克隆方法,它根本不需要任何接口,并且基于 Data Conract
I found another approach to deep cloning, which does not require any interfaces at all and is based on Data Conract and may seem quite weird, but it is really the most universal. It may seep not efficient at first glance, but this is not really true. We will discuss it later. First, I want to show this universal solution:
using System.Runtime.Serialization;
using System.IO;

//...

        static object DeepClone(object source) { // only one universal method for all cases
            if (source == null) return null;
            DataContractSerializer serializer = new DataContractSerializer(
                    source.GetType(),
                    null,
                    int.MaxValue,
                    true,
                    true, // not only trees, any arbitrary graphs
                    null);
            MemoryStream stream = new MemoryStream();
            serializer.WriteObject(stream, source);
            stream.Seek(0, SeekOrigin.Begin);
            return serializer.ReadObject(stream);
        } //DeepClone





这就是全部。现在,让我们来讨论一下。首先,它不会克隆一切。但这很好。大多数类型都有一些冗余通常用于更好的性能,但冗余对于克隆是不利的。通用克隆方法应该只克隆非冗余数据。因此,显示的代码将仅克隆​​数据协定中提到的数据。这是绝对非侵入性的方法,因此通过为成员添加[DataContract]属性和成员的[DataMember]属性来定义合同,我们需要在合同中包含所有这些属性。添加此属性不会影响合同中类型的行为,它只影响持久性和克隆行为。



这是它的工作原理:http://msdn.microsoft.com/en-us/library/ms733127.aspx [ ^ ]。



实际上,就使用而言,这是最容易使用,最强大,最可靠和通用的方法。只有一种通用方法。我们不仅支持通用克隆,而且还免费提供XML持久性。此外,我们不仅可以克隆一些对象的层次结构(树),而且可以克隆任何对象的任意图形(请参阅上面代码中的注释,//不仅是树,任何任意图形)。但性能怎么样?这是XML,一个包含数据的大字符串。会不会太慢?



实际上,这取决于它,但实际上速度非常快。对于非常大的对象图,限制将是在内存中同时具有3个数据副本:源,流(最大部分,因为XML文本冗余)和克隆。但是,对于绝大多数数据集来说,这不会是一个问题。退出 DeepClone 的上下文后,垃圾收集器最终将回收流内存。



至于性能,事情真的很有趣。代码通常比我在解决方案1中显示的代码快得多。为什么?这就是序列化的工作原理:缓慢的部分,反射,主要是第一次发生。稍后,每次再次克隆相同类型的对象时,都会创建序列化程序集并重复使用。由于这种复杂的技巧,性能提升是惊人的。序列化程序集的生成基于 System.Reflection.Emit 。要了解一些信息,请参阅:

http://stackoverflow.com / questions / 3185121 / assembly-serialization-why [ ^ ],

http://msdn.microsoft.com /en-us/library/bk3w6240.aspx [ ^ ]。



参见:

http://msdn.microsoft.com/en-us/library/system.runtime.serialization.datacontractserializer.aspx [<一个href =http://msdn.microsoft.com/en-us/library/system.runtime.serialization.datacontractserializer.aspx\"target =_ blanktitle =New Window> ^ ],

http://msdn.microsoft.com/en-us/library/system.io.memorystream.aspx [< a href =http://msdn.microsoft.com/en-us/library/system.io.memorystream.aspxtarget =_ blanktitle =New Window> ^ ]。



-SA



That's all. Now, let's discuss it. First of all, it won't clone everything. But this is good. Most types have some redundancies used for, usually, better performance, but redundancy is bad for cloning. Universal cloning method should only clone non-redundant data. So, the code shown will clone only the data mentioned in the data contract. This is absolutely non-intrusive method, so the contract is defined by adding [DataContract] attributes for types and [DataMember] attributes for member, all we need to include in the contract. Adding this attributes cannot affect the behavior of types making in contract, it only affect persistent and cloning behavior.

This is how it works: http://msdn.microsoft.com/en-us/library/ms733127.aspx[^].

Actually, in terms of usage, this is the easiest to use, most robust, reliable and universal approach. Only one universal method. Not only we enable universal cloning, but it gives us XML persistence for free. Besides, we can clone not just some hierarchy of objects (a tree), but any arbitrary graph of objects (please see my comment in the code above, "// not only trees, any arbitrary graphs"). But how about performance? This is XML, a big string with data. Won't it be too slow?

Actually, it depends, but it is actually amazingly fast. For really big object graphs, the limitation would be having in the memory 3 copies of data at the same time: source, stream (the biggest part, because of XML textual redundancy) and the clone. However, for vast majority of data sets, it won't be a problem. After going out of the context of DeepClone, the stream memory will eventually be reclaimed by the Garbage Collector.

As to the performance, things are really interesting. The code is usually much faster then the code I've shown in my Solution 1. Why? This is how serialization works: the slow part, reflection, happens mostly only for the first time. Later on, a serialization assembly is created and reused each time you clone the objects of the same types again. Performance gain, due to this intricate trick, is amazing. The generation of the serialization assemblies is based on System.Reflection.Emit. To get some idea, please see, for example:
http://stackoverflow.com/questions/3185121/assembly-serialization-why[^],
http://msdn.microsoft.com/en-us/library/bk3w6240.aspx[^].

See also:
http://msdn.microsoft.com/en-us/library/system.runtime.serialization.datacontractserializer.aspx[^],
http://msdn.microsoft.com/en-us/library/system.io.memorystream.aspx[^].

—SA


这篇关于如何为List Class实现ICloneable?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆