张赐荣——一位视障程序员。
赐荣小站: www.prc.cx

張賜榮

张赐荣的技术博客

博客园 首页 新随笔 联系 订阅 管理

C# 复制对象的副本

问题

您需要一种方法对可能引用其他类型的数据类型进行浅克隆操作、深克隆操作或者同时执行这两种操作,但是不应该使用 ICloneable 接口,因为它违反了 .NET Framework 设计准则。

解决方法

为了解决使用 ICloneable 的问题,创建另外两个接口 IShallowCopy 和 IDeepCopy 来建立一种复制模式,代码如下所示。

public interface IShallowCopy<T>  // 浅拷贝接口
{
 T ShallowCopy();
}

public interface IDeepCopy<T>  // 深拷贝接口
{
 T DeepCopy();
}

浅复制 意味着所复制对象的字段将引用与原始对象相同的对象。为了允许进行浅复制,可在类中实现 IShallowCopy 接口,代码如下所示。

using System;
using System.Collections;
using System.Collections.Generic;
public class ShallowClone : IShallowCopy<ShallowClone>
{
public int Data = 1;
public List<string> ListData = new List<string>();
public object ObjData = new object();
public ShallowClone ShallowCopy() => (ShallowClone)this.MemberwiseClone();
}

深复制 (或称克隆)意味着所复制对象的字段将引用原始对象的字段的新副本。为了进行深复制,可在类中实现 IDeepCopy 接口,代码如下所示。

using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
[Serializable] public class DeepClone : IDeepCopy<DeepClone>
{
public int data = 1;
public List<string> ListData = new List<string>();
public object objData = new object();
public DeepClone DeepCopy()
{
BinaryFormatter BF = new BinaryFormatter();
MemoryStream memStream = new MemoryStream();
BF.Serialize(memStream, this);
memStream.Flush();
memStream.Position = 0;
return (DeepClone)BF.Deserialize(memStream);
}
}

要同时支持浅复制和深复制方法,可同时实现这两个接口,代码如下所示。

using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
[Serializable] public class MultiClone : IShallowCopy<MultiClone>, IDeepCopy<MultiClone>
{
public int data = 1;
public List<string> ListData = new List<string>();
public object objData = new object();
public MultiClone ShallowCopy() => (MultiClone)this.MemberwiseClone();
public MultiClone DeepCopy()
{
BinaryFormatter BF = new BinaryFormatter();
MemoryStream memStream = new MemoryStream();
BF.Serialize(memStream, this);
memStream.Flush();
memStream.Position = 0;
return (MultiClone)BF.Deserialize(memStream);
}
}

讲解

.NET Framework 中包含一个名为 ICloneable 的接口,它最初被设计为在 .NET 中实现克隆的方法。设计建议现在不再在任何公开 API 中使用这个接口,因为它容易将自身导向不同的解释。此接口看起来如下所示。
public interface ICloneable { object Clone(); }
注意此接口只有一个方法 Clone ,它返回一个对象。该克隆是对象的浅副本还是深副本呢?无法通过该接口得知这一点,因为实现可以选择任何一个方式。这就是不应该再使用它,而是引入 IShallowCopy 和 IDeepCopy 接口的原因。
克隆 操作能够创建出类型实例的一个准确副本(克隆)。克隆可能采用两种形式之一:浅复制和深复制。浅复制相对容易一些,它对涉及复制的对象调用 ShallowCopy 方法。
在原始对象中,引用类型的字段像值类型的字段那样进行复制。例如,如果原始对象包含一个 StreamWriter 类型的字段,克隆的对象将指向原始对象的 StreamWriter 的同一个实例,并没有创建新对象。
 在执行克隆操作时无需处理静态字段。每个应用程序域中的每个类的每个静态字段只会保留一个内存位置。克隆出的对象与原始对象访问相同的静态字段。
对浅复制的支持是通过 Object 类的 MemberwiseClone 方法来实现的,Object 类充当了所有 .NET 类的基类。因此,下面的代码通过 Clone 方法允许创建和返回一个浅复制。
public ShallowClone ShallowCopy() => (ShallowClone)this.MemberwiseClone();
克隆一个对象的另一种方式是创建深复制。就像浅复制那样,深复制将创建原始对象的一个副本。不同的是,深复制还会创建原始对象中每个引用类型的字段的单独副本。因此,如果原始对象包含一个 StreamWriter 类型的字段,复制的对象也会包含一个 StreamWriter 类型的字段,但是复制对象的 StreamWriter 字段将指向一个新的 StreamWriter 对象,而不是原始对象的 StreamWriter 对象。
.NET Framework 没有直接提供对深复制的支持,但是下面的代码提供了一种实现深复制的简单方式。
BinaryFormatter BF = new BinaryFormatter(); MemoryStream memStream = new MemoryStream(); BF.Serialize(memStream, this); memStream.Flush(); memStream.Position = 0; return (BF.Deserialize(memStream));
总而言之,这使用二进制序列化将原始对象序列化到一个内存流中,然后将其反序列化到一个新对象中,并将该对象返回给调用者。在调用 Deserialize 方法之前将内存流指针重新定位到流的开始处是十分重要的;否则就会引发一个异常,指示序列化的对象中不包含任何数据。
使用对象序列化执行深复制时,不必修改执行深复制的代码就能改下层的对象。如果您手动执行深复制,就必须对原始对象的每个实例字段创建新实例,并把新实例复制到克隆的对象。这是一件非常琐碎的事情。如果修改了原始对象的字段,您必须修改深复制的代码以反映出这些修改。使用序列化可以依靠序列化器动态查找和序列化对象中包含的所有字段。如果修改了对象,序列化器仍然不需要修改就可以进行深复制。
您可能想手动执行深复制的一个原因是,仅当对象中的一切都可序列化时,本范例中介绍的序列化技术才会正确工作。当然,手动复制有时也于事无补,因为有些对象天生就是不可复制的。假设您有一个网络管理应用,其中一个对象代表网络上的一台特定打印机。当您复制它时会指望它做什么呢?传真一份订购单以购买一台新的打印机吗?
深复制与生俱来的一个问题是在具有循环引用的嵌套数据结构上执行深复制。本范例使得处理循环引用成为可能,尽管这仍是一个难题。因此,事实上,如果您使用本范例中的方法,就无需避免循环引用。

参考源码

以下是本文用到的完整代码:


        #region "Building Cloneable Classes"
using System;
using System.Runtime.InteropServices;
using System.Globalization;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Text;
using System.Collections.ObjectModel;

        public interface IShallowCopy<T>
        {
            T ShallowCopy();
        }
        public interface IDeepCopy<T>
        {
            T DeepCopy();
        }
        public class ShallowClone : IShallowCopy<ShallowClone>
        {
            public int Data = 1;
            public List<string> ListData = new List<string>();
            public object ObjData = new object();
            public ShallowClone ShallowCopy() => (ShallowClone)this.MemberwiseClone();
        }
        [Serializable]
        public class DeepClone : IDeepCopy<DeepClone>
        {
            public int data = 1;
            public List<string> ListData = new List<string>();
            public object objData = new object();
            public DeepClone DeepCopy()
            {
                BinaryFormatter BF = new BinaryFormatter();
                MemoryStream memStream = new MemoryStream();
                BF.Serialize(memStream, this);
                memStream.Flush();
                memStream.Position = 0;
                return (DeepClone)BF.Deserialize(memStream);
            }
        }
        [Serializable]
        public class MultiClone : IShallowCopy<MultiClone>,
                                  IDeepCopy<MultiClone>
        {
            public int data = 1;
            public List<string> ListData = new List<string>();
            public object objData = new object();
            public MultiClone ShallowCopy() => (MultiClone)this.MemberwiseClone();
            public MultiClone DeepCopy()
            {
                BinaryFormatter BF = new BinaryFormatter();
                MemoryStream memStream = new MemoryStream();
                BF.Serialize(memStream, this);
                memStream.Flush();
                memStream.Position = 0;
                return (MultiClone)BF.Deserialize(memStream);
            }
        }
        #endregion
posted on 2022-04-10 10:24  张赐荣  阅读(518)  评论(0编辑  收藏  举报

感谢访问张赐荣的技术分享博客!
博客地址:https://cnblogs.com/netlog/
知乎主页:https://www.zhihu.com/people/tzujung-chang
个人网站:https://prc.cx/