Ø  简介

在 C# 中分为两种数据类型,值类型和引用类型。我们知道,值类型之间赋值是直接将值赋值给另一个变量,两个变量值的改变都互不影响;而引用类型赋值则是将引用赋值给另一个变量,其中一个变量中的成员的值被改变,会影响到另一个变量。

好,以上论述不是我们今天要讨论的重点,只是起抛砖引玉的作用。现在假设有这样一个需求,我现在有一个对象(别想歪了,不是女朋友~),而我不想使用 new 去创建这个类型的另一个对象,该如何实现呢?这时我们会立马想到使用反射去创建这个对象,然后再逐个属性进行赋值。OK,没问题,其实这一操作正是对象拷贝中深拷贝的一种体现。

那什么是浅拷贝与深拷贝呢?

首先,我们说一下对象拷贝。对象拷贝,就是将一个现有的对象,克隆为一个全新的对象,它们的引用是相互独立的

浅拷贝:是指在对象拷贝的过程中,成员中的值类型进行逐一赋值,而引用类型只赋值引用,并不会创建新的对象实例,结果它们指向的是同一个引用

深拷贝:其实是对引用类型而言的,它除了会对值类型赋值;还可以将引用类型创建新的实例,结果它们指向的是不同引用。说还可以的意思是,其实深拷贝是需要我们写代码去实现的。

好了,理论就是这么多。下面进入实战,主要包括以下几点:

1.   对象拷贝的用途

2.   实现浅拷贝

3.   实现深拷贝

1.   对象拷贝的用途

我们学习它,当然要知道它能干嘛是吧?

说实话,对象拷贝用的不多。回忆以下,之前在写 EF 代码时,有接触到将一个对象赋值到另一个对象的场景,只是当时没有对象拷贝这个概念。也仅此一次而已!下面是本人能想到的应用场景:

1)   当一个对象中的成员较多,而又不想写代码逐个成员去赋值时;

2)   当需要将一个对象自动完成克隆为一个全新对象时;

3)   当前已经存在一个对象,而使用 new 关键字去创建该对象比较耗时时。

2.   实现浅拷贝

我们知道,System.Object 这个基类有一个 MemberwiseClone() 方法,该方法的声明如下:

[MethodImpl(MethodImplOptions.InternalCall), SecuritySafeCritical]

protected extern object MemberwiseClone();

1)   该方法采用外部方法实现;

2)   该方法是受保护的,采用了 protected 修饰符(所以只能由子类去访问);

3)   该方法可以翻译为成员逐一克隆

没错,该方法就是用于实现浅拷贝的,具体实现如下:

1)   首先,定义一个 CloneBase 抽象基类

[Serializable]

public abstract class CloneBase

{

/// <summary>

/// 浅拷贝

/// </summary>

public object ShallowCopy()

{

return base.MemberwiseClone();

}

}

2)   定义实体类

/// <summary>

/// 性格

/// </summary>

public enum Natures

{

/// <summary>

/// 外向

/// </summary>

Extroversion = 1,

/// <summary>

/// 内向

/// </summary>

Introversion = 2

}

[Serializable]

/// <summary>

/// 部门

/// </summary>

public class Department

{

public int DepId { get; set; }

public string Name { get; set; }

}

[Serializable]

/// <summary>

/// 员工

/// </summary>

public class Employee : CloneBase

{

//public Employee(int EmpId) { }

public int EmpId { get; set; }

public bool Sex { get; set; }

public double Salary { get; set; }

public DateTime EntryDate { get; set; }

/// <summary>

/// 性格

/// </summary>

public Natures Nature { get; set; }

public string Name { get; set; }

public Department Department { get; set; }

/// <summary>

/// 业余爱好

/// </summary>

public string[] Hobbys { get; set; }

/// <summary>

/// 技能

/// </summary>

public List<string> SkillList { get; set; }

/// <summary>

/// 工作内容

/// </summary>

public Dictionary<int, string> JobContent { get; set; }

/// <summary>

/// 绩效考核

/// </summary>

public DataTable Performance { get; set; }

}

3)   测试代码

Employee zhangsan1 = new Employee()

{

EmpId = 1,

Sex = true,

Salary = 10000,

EntryDate = DateTime.Parse("2011-01-01"),

Nature = Natures.Extroversion,

Name = "张三",

Department = new Department() { DepId = 1, Name = "研发部" },

Hobbys = new string[] { "打篮球", "健身", "学习" },

SkillList = new List<string>() { "SQL Server", "Redis" },

JobContent = new Dictionary<int, string>()

{

{ 1, "后端开发" },

{ 2, "移动端开发" }

},

Performance = new DataTable("MyTable1")

};

zhangsan1.Performance.Columns.AddRange(new DataColumn[]

{

new DataColumn("Quarter", typeof(int)),     //季度

new DataColumn("Score", typeof(double)),    //分数

});

zhangsan1.Performance.Rows.Add(1, 80);

Employee zhangsan2 = zhangsan1.ShallowCopy() as Employee;

zhangsan2.EmpId = 2;

zhangsan2.Sex = false;

zhangsan2.Salary = 20000;

zhangsan2.EntryDate = DateTime.Parse("2012-02-02");

zhangsan2.Nature = Natures.Introversion;

zhangsan2.Name = "张山";

zhangsan2.Department.DepId = 2;

zhangsan2.Hobbys[0] = "打桌球";

zhangsan2.SkillList[0] = "Oracle";

zhangsan2.JobContent[1] = "前端开发";

zhangsan2.Performance.Rows[0][1] = 90;

Console.WriteLine($"zhangsan1 equal to zhangsan2 : {object.ReferenceEquals(zhangsan1, zhangsan2)}");

Console.WriteLine("");

Console.WriteLine($"int type: \t\t{zhangsan1.EmpId} -> {zhangsan2.EmpId}");

Console.WriteLine($"bool type: \t\t{zhangsan1.Sex} -> {zhangsan2.Sex}");

Console.WriteLine($"double type: \t\t{zhangsan1.Salary} -> {zhangsan2.Salary}");

Console.WriteLine($"DateTime type: \t\t{zhangsan1.EntryDate} -> {zhangsan2.EntryDate}");

Console.WriteLine($"enum type: \t\t{zhangsan1.Nature} -> {zhangsan2.Nature}");

Console.WriteLine("");

Console.WriteLine($"string type: \t\t{zhangsan1.Name} -> {zhangsan2.Name}");

Console.WriteLine($"class type: \t\t{zhangsan1.Department.DepId} -> {zhangsan2.Department.DepId}");

Console.WriteLine($"array type: \t\t{zhangsan1.Hobbys[0]} -> {zhangsan2.Hobbys[0]}");

Console.WriteLine($"List type: \t\t{zhangsan1.SkillList[0]} -> {zhangsan2.SkillList[0]}");

Console.WriteLine($"Dictionary type: \t{zhangsan1.JobContent[1]} -> {zhangsan2.JobContent[1]}");

Console.WriteLine($"DataTable type: \t{zhangsan1.Performance.Rows[0][1]} -> {zhangsan2.Performance.Rows[0][1]}");

4)   运行结果

3.   实现深拷贝

深拷贝相比浅拷贝,稍微复杂一点。其实现方式大致分为反射方式序列化方式,而序列化通常可以借助(XmlSerializer、DataContractSerializer、BinaryFormatter)这三个对象来完成。下面是实现代码:

1)   首先,在 CloneBase 基类中加入深拷贝的实现代码:

/// <summary>

/// 反射方式

/// </summary>

private object Reflection<T>(T obj)

{

Type sourceType;

if (obj is string || (sourceType = obj.GetType()).IsValueType)

{

return obj;

}

else if (sourceType.IsArray)

{

Array sourceArr = obj as Array;

Type arrType = Type.GetType(sourceType.FullName.Replace("[]", string.Empty));   //System.String[] 取数组类型

//创建数组实例,并遍历和递归赋值每个元素

Array targetArr = Array.CreateInstance(arrType, sourceArr.Length);

for (int i = 0; i < sourceArr.Length; i++)

{

targetArr.SetValue(Reflection(sourceArr.GetValue(i)), i);

}

return Convert.ChangeType(targetArr, sourceType);

}

else if (sourceType.Assembly.GetName().Name != "mscorlib"

&& !sourceType.FullName.StartsWith("System"))   //认为是自定义 Class 类型

{

//创建当前类型的实例,并遍历成员进行赋值

object instance = Activator.CreateInstance(sourceType);

FieldInfo[] fields = sourceType.GetFields(

BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance);

foreach (var field in fields)

{

field.SetValue(instance, Reflection(field.GetValue(obj)));  //递归调用(如果是引用类型)

}

return instance;

}

else

{

//到这里可能是 List<T>、Dictionary<TKey, TValue>、DataTable 等复杂数据类型

//其实并不好实现克隆,所有这里为了简单,就直接赋值源对象引用了

return obj;

}

}

/// <summary>

/// 序列化方式

/// </summary>

private object Serialize<T>(T obj, int serializeMode)

{

//1. 采用XML序列化和反序列化

if (serializeMode == 1)

{

using (MemoryStream ms = new MemoryStream())

{

var xmlSerializer = new System.Xml.Serialization.XmlSerializer(obj.GetType());  //无法序列化 System.Collections.Generic.Dictionary`

xmlSerializer.Serialize(ms, this);

ms.Seek(0, SeekOrigin.Begin);

return xmlSerializer.Deserialize(ms);

}

}

//2. 采用数据契约序列化和反序列化

else if (serializeMode == 2)

{

using (MemoryStream ms = new MemoryStream())

{

var serializer = new System.Runtime.Serialization.DataContractSerializer(obj.GetType());

serializer.WriteObject(ms, obj);

ms.Seek(0, SeekOrigin.Begin);

return serializer.ReadObject(ms);

}

}

//3. 采用二进制格式序列化和反序列化

else if (serializeMode == 3)

{

using (MemoryStream ms = new MemoryStream())

{

var binaryFormatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();

binaryFormatter.Serialize(ms, this);

ms.Seek(0, SeekOrigin.Begin);

return binaryFormatter.Deserialize(ms);

}

}

else { throw new ArgumentException("serializeMode 参数不在支持的范围内"); }

}

/// <summary>

/// 深拷贝

/// </summary>

public virtual object DeepCopy()

{

//return Reflection(this);

return Serialize(this, 3);

}

2)   修改测试代码

Employee zhangsan2 = zhangsan1.ShallowCopy() as Employee;

改为

Employee zhangsan2 = zhangsan1.DeepCopy() as Employee;

3)   测试结果

Ø  总结

以下是两种拷贝方式表现形式:

拷贝方式

实现方式

无参构造函数

Serializable 特性

复杂类型(List、Dictionary、DataTable等)

浅拷贝

MemberwiseClone()

非必须

非必须

支持(引用赋值)

深拷贝

反射

必须

非必须

不支持(或比较复杂)

XmlSerializer

必须

非必须

不支持

DataContractSerializer

非必须

必须

支持

BinaryFormatter

非必须

必须

支持

深拷贝:

1.   反射,对于泛型集合(List、Dictionary等)类型,实现起来比较复杂。不推荐使用。

2.   XmlSerializer,对(Dictionary等)类型支持不够,会报错:无法序列化 System.Collections.Generic.Dictionary`2。不推荐使用。

3.   DataContractSerializer,需要额外添加对“System.Runtime.Serialization.dll”程序集的引用,不是很推荐。

4.   BinaryFormatter,位于“mscorlib.dll”程序集,对象序列化方面支持较好,推荐使用

其他:

1.   在 .NET Framework 中提供了用于支持拷贝的接口 System.ICloneable, 该接口定义了一个 object Clone() 方法。但是使用该方法并不好区分是浅拷贝深拷贝,所以个人觉得并不建议去使用它。

2.   如果非要定义深拷贝由实现类去实现,这时可以考虑定义深拷贝接口 IDeepCopy,具体的拷贝实现由该类来完成;另外,从某种程度上说,深拷贝其实可以看做是一种功能或者能力,也可以考虑定义为帮助方法去实现该功能;而浅拷贝逻辑上不用,因为仅一行代码即可实现,而且父类也能完成该功能。

C# 中的浅拷贝与深拷贝的更多相关文章

  1. 【转】JAVA中的浅拷贝和深拷贝

    原文网址:http://blog.bd17kaka.net/blog/2013/06/25/java-deep-copy/ JAVA中的浅拷贝和深拷贝(shallow copy and deep co ...

  2. js中的浅拷贝和深拷贝

    说说最近所学:浅拷贝和深拷贝也叫做浅克隆和深克隆,深浅主要针对的是对象的"深度",常见的对象都是"浅"的,也就是对象里的属性就是单个的属性,而"深&q ...

  3. Javascript中的浅拷贝和深拷贝

    很多开发语言中都有浅拷贝和深拷贝的说法,这里简单区分一下它们在Javascript中的区别,以及jQuery中深拷贝的实现. 在谈浅拷贝和深拷贝之前,先要屡清楚Javascript中的按值访问和按引用 ...

  4. 浅谈JS中的浅拷贝与深拷贝

    前端工程师应该都比较熟悉浅拷贝和深拷贝的概念,在日常业务代码的过程中,特别是做数据处理的时候,经常行的会遇到,比如如何在不修改原对象的基础上,重新生成一个一模一样的对象,加以利用,又或是,如何巧妙地运 ...

  5. javascript中的浅拷贝和深拷贝(拷贝引用和拷贝实例)

    作者:千锋教育链接:https://www.zhihu.com/question/23031215/answer/326129003来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请 ...

  6. JS中的浅拷贝与深拷贝

    浅拷贝与深拷贝的区别: 浅拷贝: 对基本类型和引用类型只进行值的拷贝,即,拷贝引用对象的时候,只对引用对象的内存地址拷贝,新旧引用属性指向同一个对象,修改任意一个都会影响所有引用当前对象的变量. 深拷 ...

  7. java中的浅拷贝和深拷贝

    复制 将一个对象的引用复制给另一个对象,一共有三种方式.第一种方式是直接赋值,第二种方式是浅复制,第三种方式是深复制. 1.直接赋值 在Java中,A a1 = a2,这实际上复制的是引用,也就是说 ...

  8. python中的浅拷贝,深拷贝

    直接引用,间接引用 # 1.列表存储的是索引对应值的内存地址,值会单独的开辟一个内存空间 list = ["a","b"] 内存里面存储的就是list[0],l ...

  9. Objective-C中的浅拷贝和深拷贝(转载)

    本文转自:http://segmentfault.com/blog/channe/1190000000604331 浅拷贝 浅拷贝就是对内存地址的复制,让目标对象指针和源对象指向同一片内存空间.如: ...

随机推荐

  1. tomcat9上传文件失败错误

    项目上线正常运行一段时间后,有一天突然所有的附件上传都出现了错误,查找项目本身的日志系统也一致没有跟踪到错误.经过几番折腾,在tomcat9-stdout.log日志中发现如下异常: ERROR or ...

  2. windows 安装使用jupyter及 基础配置

    jupyter 是什么Jupyter Notebooks 是一个交互式笔记本,支持运行 40 多种编程语言,它的本质是一个 开源的 Web 应用程序,我们可以将其用于创建和共享代码与文档,他可以支持实 ...

  3. DedeCMS V5.7 SP2后台代码执行漏洞复现(CNVD-2018-01221)

    dedeCMS  V5.7 SP2后台代码执行漏洞复现(CNVD-2018-01221) 一.漏洞描述 织梦内容管理系统(Dedecms)是一款PHP开源网站管理系统.Dedecms V5.7 SP2 ...

  4. python连接sqlserver工具类

    上代码: # -*- coding:utf-8 -*- import pymssql import pandas as pd class MSSQL(object): def __init__(sel ...

  5. 深入理解JVM,类加载器

    虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流(即字节码)”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类.实现这个动作的代码模块称 ...

  6. JQuery操作样式以及JQuery事件机制

    1.操作样式     1.1 css的操作     功能:设置或者修改样式,操作的是style属性 操作单个样式 // name:需要设置的样式名称 // value:对应的样式值 // $obj.c ...

  7. LinuxShell脚本——变量和数据类型

    LinuxShell脚本——变量和数据类型 摘要:本文主要学习了Shell脚本中的变量和数据类型. 变量 定义变量的语法 定义变量时,变量名和变量值之间使用“=”分隔,并且等号两边不能有空格: 变量名 ...

  8. 多线程学习二:线程池 ExecutorService

    创建线程池的2种方式: 使用线程池方式1--Runnable接口: 通常,线程池都是通过线程池工厂创建,再调用线程池中的方法获取线程,再通过线程去执行任务方法. Executors:线程池创建工厂类: ...

  9. gyp编译工具

    最近用到了 node-gyp 这个工具, 是node 社区对 google gyp 编译工具的一个封装, 使用 node-gyp 工具可以用C++为node 项目编写 addon. 了解了一下 goo ...

  10. [b0008] Windows 7 下 hadoop 2.6.4 eclipse 本地开发调试配置

    目的: 基于上篇的方法介绍,开发很不方便 .[0007] windows 下 eclipse 开发 hdfs程序样例 装上插件,方便后续直接在windows下的IDE开发调试. 环境: Linux  ...