编写高质量代码改善C#程序的157个建议——建议14: 正确实现浅拷贝和深拷贝
建议14: 正确实现浅拷贝和深拷贝
为对象创建副本的技术称为拷贝(也叫克隆)。我们将拷贝分为浅拷贝和深拷贝。
- 浅拷贝 将对象中的所有字段复制到新的对象(副本)中。其中,值类型字段的值被复制到副本中后,在副本中的修改不会影响到源对象对应的值。而引用类型的字段被复制到副本中的是引用类型的引用,而不是引用的对象,在副本中对引用类型的字段值做修改会影响到源对象本身。
- 深拷贝 同样,将对象中的所有字段复制到新的对象中。不过,无论是对象的值类型字段,还是引用类型字段,都会被重新创建并赋值,对于副本的修改,不会影响到源对象本身。
无论是浅拷贝还是深拷贝,微软都建议用类型继承ICloneable接口的方式明确告诉调用者:该类型可以被拷贝。当然,ICloneable接口 只提供了一个声明为Clone的方法,我们可以根据需求在Clone方法内实现浅拷贝或深拷贝。一个简单的浅拷贝的实现代码如下所示:
class Employee : ICloneable
{
public string IDCode { get; set; }
public int Age { get; set; }
public Department Department { get; set; } #region ICloneable 成员 public object Clone()
{
return this.MemberwiseClone();
} #endregion
} class Department
{
public string Name { get; set; }
public override string ToString()
{
return this.Name;
}
}
调用方代码如下所示:
Employee mike = new Employee() { IDCode = "NB123", Age = , Department = new Department() { Name = "Dep1" } };
Employee rose = mike.Clone() as Employee;
Console.WriteLine(rose.IDCode);
Console.WriteLine(rose.Age);
Console.WriteLine(rose.Department);
Console.WriteLine("开始改变mike的值:");
mike.IDCode = "NB456";
mike.Age = ;
mike.Department.Name = "Dep2";
Console.WriteLine(rose.IDCode);
Console.WriteLine(rose.Age);
Console.WriteLine(rose.Department);
输出为:
NB123 Dep1
开始改变mike的值:
NB123 Dep2
注意到Employee的IDCode属性是string类型。理论上string类型是引用类型,但是由于该引用类型的特殊性(无论是实现还是语义),Object.MemberwiseClone方法仍旧为其创建了副本。也就是说,在浅拷贝过程,我们应该将字符串看成是值类型。 Employee的Department属性是一个引用类型,所以,如果改变了源对象mike中的值,副本rose中的值也会随之一起变动。 Employee的深拷贝有多种实现方法,最简单的方法是手动对字段逐个进行赋值。但这种方法容易出错,也就是说,如果类型的字段发生变化或有增减,那么 该拷贝方法也要发生相应的变化,所以,建议使用序列化的形式来进行深拷贝。Employee深拷贝的一个简单实现代码如下所示:
class Employee : ICloneable
{
public string IDCode { get; set; }
public int Age { get; set; }
public Department Department { get; set; } #region ICloneable 成员 public object Clone()
{
using (Stream objectStream = new MemoryStream())
{
IFormatter formatter = new BinaryFormatter();
formatter.Serialize(objectStream, this);
objectStream.Seek(, SeekOrigin.Begin);
return formatter.Deserialize(objectStream) as Employee;
}
}
#endregion
}
使用浅拷贝中的调用者代码,输出为:
NB123 Dep1
开始改变mike的值:
NB123 Dep1
可以发现,再次更改mike的值已经不会影响副本rose的值了。 由于接口ICloneable只有一个模棱两可的Clone方法,所以,如果要在一个类中同时实现深拷贝和浅拷贝,只能由我们自己实现两个额外的方法,声明为DeepClone和Shallow。Employee的最终版本看起来应该像如下的形式:
[Serializable]
class Employee : ICloneable
{
public string IDCode { get; set; }
public int Age { get; set; }
public Department Department { get; set; } #region ICloneable 成员 public object Clone()
{
return this.MemberwiseClone();
} #endregion public Employee DeepClone()
{
using (Stream objectStream = new MemoryStream())
{
IFormatter formatter = new BinaryFormatter();
formatter.Serialize(objectStream, this);
objectStream.Seek(, SeekOrigin.Begin);
return formatter.Deserialize(objectStream) as Employee;
}
} public Employee ShallowClone()
{
return Clone() as Employee;
}
}
转自:《编写高质量代码改善C#程序的157个建议》陆敏技
编写高质量代码改善C#程序的157个建议——建议14: 正确实现浅拷贝和深拷贝的更多相关文章
- 编写高质量代码改善C#程序的157个建议[1-3]
原文:编写高质量代码改善C#程序的157个建议[1-3] 前言 本文主要来学习记录前三个建议. 建议1.正确操作字符串 建议2.使用默认转型方法 建议3.区别对待强制转换与as和is 其中有很多需要理 ...
- 读书--编写高质量代码 改善C#程序的157个建议
最近读了陆敏技写的一本书<<编写高质量代码 改善C#程序的157个建议>>书写的很好.我还看了他的博客http://www.cnblogs.com/luminji . 前面部 ...
- 编写高质量代码改善C#程序的157个建议——建议157:从写第一个界面开始,就进行自动化测试
建议157:从写第一个界面开始,就进行自动化测试 如果说单元测试是白盒测试,那么自动化测试就是黑盒测试.黑盒测试要求捕捉界面上的控件句柄,并对其进行编码,以达到模拟人工操作的目的.具体的自动化测试请学 ...
- 编写高质量代码改善C#程序的157个建议——建议156:利用特性为应用程序提供多个版本
建议156:利用特性为应用程序提供多个版本 基于如下理由,需要为应用程序提供多个版本: 应用程序有体验版和完整功能版. 应用程序在迭代过程中需要屏蔽一些不成熟的功能. 假设我们的应用程序共有两类功能: ...
- 编写高质量代码改善C#程序的157个建议——建议155:随生产代码一起提交单元测试代码
建议155:随生产代码一起提交单元测试代码 首先提出一个问题:我们害怕修改代码吗?是否曾经无数次面对乱糟糟的代码,下决心进行重构,然后在一个月后的某个周一,却收到来自测试版的报告:新的版本,没有之前的 ...
- 编写高质量代码改善C#程序的157个建议——建议154:不要过度设计,在敏捷中体会重构的乐趣
建议154:不要过度设计,在敏捷中体会重构的乐趣 有时候,我们不得不随时更改软件的设计: 如果项目是针对某个大型机构的,不同级别的软件使用者,会提出不同的需求,或者随着关键岗位人员的更替,需求也会随个 ...
- 编写高质量代码改善C#程序的157个建议——建议153:若抛出异常,则必须要注释
建议153:若抛出异常,则必须要注释 有一种必须加注释的场景,即使异常.如果API抛出异常,则必须给出注释.调用者必须通过注释才能知道如何处理那些专有的异常.通常,即便良好的命名也不可能告诉我们方法会 ...
- 编写高质量代码改善C#程序的157个建议——建议152:最少,甚至是不要注释
建议152:最少,甚至是不要注释 以往,我们在代码中不写上几行注释,就会被认为是钟不负责任的态度.现在,这种观点正在改变.试想,如果我们所有的命名全部采用有意义的单词或词组,注释还有多少存在的价值. ...
- 编写高质量代码改善C#程序的157个建议——建议151:使用事件访问器替换公开的事件成员变量
建议151:使用事件访问器替换公开的事件成员变量 事件访问器包含两部分内容:添加访问器和删除访问器.如果涉及公开的事件字段,应该始终使用事件访问器.代码如下所示: class SampleClass ...
- 编写高质量代码改善C#程序的157个建议——建议150:使用匿名方法、Lambda表达式代替方法
建议150:使用匿名方法.Lambda表达式代替方法 方法体如果过小(如小于3行),专门为此定义一个方法就会显得过于繁琐.比如: static void SampeMethod() { List< ...
随机推荐
- php://input解决APP发送图片问题
今天公司要求用APP发送一个图片到PHP程序接收并保存起来,而且中间还需要很多参数! 以前没有做过APP和PHP交互,这次算是一个挑战吧(对一个没有人指导实习生来说) 1.APP发1.jpg,而且带有 ...
- implicit declaration of function 'init_MUTEX'
'init_MUTEX' 在新版本系统上已被淘汰 改用sema_init(sem, 1);
- composer安装特别慢的解决方案
在项目开发的时候 有许多新的依赖要按照 就需要用到composer 但是由于国内安装下载速度贼慢(国外的网站连接速度太慢,并且随时可能被墙) 安装一个excel扩展(composer require ...
- laravel中session的过期时间
在项目开发的过程中,前后端分离 需要用session保存用户的登陆信息 这就涉及到session的有效期了 session又分为php中的session有效期和laravel中的session的有效期 ...
- 【转】Jmeter做功能测试的优点和不足
利用Jmeter做功能测试有以下优点: ● 不依赖于界面,如果服务正常启动,传递参数明确就可以添加测试用例,执行测试 ● 测试脚本不需要编程,熟悉http请求,熟悉业务流程,就可以根据页面 ...
- 杂项-公司:摩根大通百科-un
ylbtech-杂项-公司:摩根大通百科 摩根大通集团(JPMorgan Chase & Co,NYSE:JPM:),2000年12月由J.P.摩根公司和大通-曼哈顿公司合并而成,是美国主要的 ...
- AD9如何设置原点位置
Edit --> Origin --> Set
- Python 正则表达式之选择
In [4]: import re In [5]: re.findall(r"\d+","jjj1234mm222")Out[5]: ['1234', '222 ...
- Lambda语句中创建自定义类型时,也可指定某种特定类型,方法是在new与{}之间写上类型名称
如: var fc =...ChildFath = fc.Select(c => new Child_Father { child = c.child, father = c.father }) ...
- IE6的checkbox, radio是通过defaultChecked决定是否选中
今天五群提到的BUG,说checked没有生效,一番百度谷歌,发现是它作怪. data.handler = function() { //IE6是通过defaultChecked来实现打勾效果 ele ...