copy (deep copy, 深复制) / clone (shallow copy, 浅复制) - .NET/C#

创建时间:
2015-02-19 23:52
最近更新:
2018-11-06 09:26

Resource

  1. C# 快速高效率复制对象另一种方式 表达式树 - 2018-11-06 已整理为 Tl.MapByExpr 类。
  2. 克隆的各种实现思路
  3. C# 的内存拷贝

深复制 - 手工编码 实现

public class Person : ICloneable
{
    public int Age { get; set; }
    public string Address { get; set; }
    public Name Name { get; set; }
    public object Clone()
    {
        Person tem = new Person();
        tem.Address = this.Address;
        tem.Age = this.Age;
        tem.Name = new Name(this.Name.FristName, this.Name.LastName);
        return tem;
    }
}

深复制 - 基于 序列化 实现

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
[Serializable]
public class Person : ICloneable
{
    public Person Clone()
    {
        using(var ms = new MemoryStream(1000)) {
            Person deepCopy;
            var bf = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.Clone));
            bf.Serialize(ms, this);
            ms.Seek(0, SeekOrigin.Begin);
            // 反序列化至另一个对象 (即创建了一个原对象的深复制副本):
            deepCopy = (Person)bf.Deserialize(ms);
            ms.Close();
            return deepCopy;
        }
    }
}
public static T DeepCopy<T>(T obj)
{
    object retval;
    using(var ms = new MemoryStream()) {
        var bf = new BinaryFormatter();
        // 序列化成流
        bf.Serialize(ms, obj);
        ms.Seek(0, SeekOrigin.Begin);
        // 反序列化成对象
        retval = bf.Deserialize(ms);
        ms.Close();
    }
    return (T)retval;
}

深复制 - 基于 Base64 序列化 实现

/// <summary>
/// 通过序列化为 Base64String 的形式还复制:对于包含图片等Bit字节之类的实体,直接通过上方法复制出来的实体结果不正确(知道原因的朋友敬请评论),可以通过转化为Base64String的形式,就可以得到正确的结果,对保存到数据库也比较方便。
/// </summary>
public static T GetObjectClone<T>(T serializeObj)
{
    string base64String = SerializeObject<T>(serializeObj);
    return DeserializeObject<T>(base64String);
}

/// <summary>
/// 序列化为 Base64String。
/// </summary>
public static string SerializeObject<T>(T serializeObj)
{
    var ms = new MemoryStream();
    var formatter = new BinaryFormatter();
    try {
        formatter.Serialize(ms, serializeObj);
        string base64String = Convert.ToBase64String(ms.GetBuffer());
        return base64String;
    }
    catch(SerializationException e) {
        Console.WriteLine("Failed to serialize. Reason: " + e.Message);
        throw;
    }
    finally {
        ms.Close();
    }
}

/// <summary>
/// 从 Base64 反序列化。
/// </summary>
public static T DeserializeObject<T>(string base64String)
{
    var ms = new MemoryStream(Convert.FromBase64String(base64String));
    var formatter = new BinaryFormatter();

    try {
        // Deserialize the hashtable from the file and assign the reference to the local variable.
        return (T)formatter.Deserialize(ms);
    }
    catch(Exception e) {
        Console.WriteLine("Failed to deserialize. Reason: " + e.Message);
        throw;
    }
    finally {
        ms.Close();
    }
}

浅复制 - 基于 反射 实现

public static T CloneModel<T>(T oModel)
{
    var shallowCopy = default(T);
    var type = typeof(T);

    // 创建新实例:
    shallowCopy = (T)Activator.CreateInstance(type);

    // 给新的对象赋值:
    FieldInfo[] fields = type.GetFields();
    foreach(FieldInfo field in fields) {
        // 从旧对象里面取值
        object val = field.GetValue(oModel);
        // 复制给新的对象
        field.SetValue(shallowCopy, val);
    }

    return shallowCopy;
}

深复制 - 基于 Newtonsoft.Json 实现

foreach (var oModel in lstModel) {
    foreach (var oAttr in lstAttr) {
        var oPropModel = lstPropModel.FirstOrDefault(x => x.Name == oAttr.Name);
        if (oPropModel == null) {
            continue;
        }
        oAttr.Value = oPropModel.GetValue(oModel);
    }
    //深拷贝一个集合到另一个集合
    var lstResTmp = new List<DragElementProp>();
    var oJsonValue = Newtonsoft.Json.JsonConvert.SerializeObject(lstAttr);
    lstResTmp.AddRange(Newtonsoft.Json.JsonConvert.DeserializeObject<List<DragElementProp>>(oJsonValue));
    lstRes.Add(lstResTmp);
}

protected object System.Object.MemberwiseClone()

  1. System.Object.MemberwiseClone() - Creates a shallow copy of the current Object. 创建当前 Object 的浅表副本。The MemberwiseClone method creates a shallow copy by creating a new object, and then copying the nonstatic fields of the current object to the new object. If a field is a value type, a bit-by-bit copy of the field is performed. If a field is a reference type, the reference is copied but the referred object is not; therefore, the original object and its clone refer to the same object. MemberwiseClone() 创建一个浅表副本,具体来说就是: 创建一个新对象,然后将当前对象的非静态字段复制到该新对象。如果字段是值类型的,则对该字段执行逐位复制。如果字段是引用类型,则复制引用但不复制引用的对象;因此,原始对象及其复本引用同一对象。

Example from Official

public class IdInfo
{
    public int _IdNumber;
    public IdInfo(int idNumber)
    {
        this._IdNumber = idNumber;
    }
}

public class Person
{
    public int Age;
    public string Name;
    public IdInfo IdInfo;

    public Person ShallowCopy()
    {
       return (Person) this.MemberwiseClone();
    }

    public Person DeepCopy()
    {
       Person other = (Person) this.MemberwiseClone();
       other.IdInfo = new IdInfo(IdInfo._IdNumber);
       other.Name = String.Copy(Name);
       return other;
    }
}

用法一: 封装为扩展方法

2018-02-12 尝试封装未果,似乎此路不通,日后再研究。

用法二: 封装为基类

http://stackoverflow.com/questions/6066029/how-can-i-call-MemberwiseClone
Here is an example, this is what I did and no problems so far.

namespace Net451Console
{
    class Program
    {
        static void Main(string[] args)
        {
            var user0 = new User(3, "3");
            User shallow = user0.ShallowCopy<User>();
        }
    }
    public class ModelBase
    {
        public T ShallowCopy<T>() where T : ModelBase
        {
            return (T)this.MemberwiseClone();
        }
    }
    public class User : ModelBase
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public User(int id, string name)
        {
            this.Id = id;
            this.Name = name;
        }
    }
}

测试一

using System;
namespace Net451Console
{
    class Program
    {
        static void Main(string[] args)
        {
            var a = new A(3, "3");
            A copy = a.ShallowCopy();

            a.Id = 6;
            a.Name = "6";

            Console.WriteLine(a);
            Console.WriteLine(copy);
        }
    }
    public class A
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public A(int id, string name)
        {
            this.Id = id;
            this.Name = name;
        }
        public A ShallowCopy()
        {
            return (A)this.MemberwiseClone();
        }
        public override string ToString()
        {
            string s = string.Format(
                "Id:{0}, Name:{1}",
                this.Id,
                this.Name
            );
            return s;
        }
    }
}
/*
output:
Id:6, Name:6
Id:3, Name:3
*/

protected MarshalByRefObject System.MarshalByRefObject.MemberwiseClone(bool cloneIdentity)

  1. System.MarshalByRefObject 类 - Enables access to objects across application domain boundaries in applications that support remoting. 在支持远程处理的应用程序中,允许跨应用程序域边界访问对象。
  2. System.MarshalByRefObject.MemberwiseClone() - Creates a shallow copy of the current object.

基于 序列化 实现 深复制 的原理

为了实现深复制,必须遍历有相互引用的对象构成的图,并需要处理其中的循环引用。这无疑十分复杂。但是借助 .NET 序列化和反序列化机制,可以十分简单的 深复制 一个对象。原理很简单,首先将对象序列化到内存流中,此时对象和对象引用的所有对象的状态都被写入流中,.net 的 序列化机制 会自动处理循环引用。然后将内存流中的状态信息反序列化到一个新的对象中,这样一个对象的 深复制 就完成了。

Example:

namespace CloneDemo
{
    [Serializable]
    class DemoClass
    {
        public int _I = 0;
        public int[] _Array = { 1, 2, 3 };

        public DemoClass Shallow()
        {
            //return this.MemberwiseClone();
            return this.MemberwiseClone() as DemoClass;
        }

        public DemoClass Deep()
        {
            var stream = new MemoryStream();
            var formatter = new BinaryFormatter();
            formatter.Serialize(stream, this);
            stream.Position = 0;
            return formatter.Deserialize(stream) as DemoClass;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var a = new DemoClass();
            a._I = 10;
            a._Array = new int[] { 8, 9, 10 };
            DemoClass b = a.Shallow();
            DemoClass c = a.Deep();

            // a._Array[0] 的更改导致 b._Array[0] 变化 但 c._Array[0] 不会变化。
            a._Array[0] = 88;

            Console.WriteLine("Shallow");
            Console.WriteLine(b._I);
            foreach (var item in b._Array)
            {
                Console.WriteLine(item);
            }

            Console.WriteLine("Deep");
            Console.WriteLine(c._I);
            foreach (var item in c._Array)
            {
                Console.WriteLine(item);
            }
        }
    }
}

---------------