克隆方法是原型设计模式中必须使用的方式,它将返回一个与当前对象数据一致的对象。正如其名,犹如一个模子雕刻而出。克隆类型分为两种:浅克隆、深克隆。

浅复制就是仅复制类中的值类型成员

深复制就是复制类中的值类型成员和引用类型的成员。

浅复制一般由object.MemberwiseClone自动实现,由于是用反射机制,所以在实现时,最好还是自己来写。

要想实现深复制,则要确保类中的所有引用类型成员都有浅复制的功能,这样就能确保深复制成功



为什么把类中的所有引用类型成员都有浅复制的功能,这样就……

1、浅克隆

浅克隆方式是最简单、最直接的方式。只需要类实现接口ICloneable(在命名空间System.Runtime.InteropServices下)的Clone方法,在方法中使用加入对当前类的MemberwiseClone()方法即可。在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象。

public class Student:ICloneable
{
/// <summary>
/// 值类型
/// </summary>
public int ID { get; set; }   /// <summary>
/// 引用类型
/// </summary>
public object obj { get; set; } public object Clone()
{
return this.MemberwiseClone();
}
}

以上方法实现了对类对象的浅克隆方式。但是在该类中具有引用类型字段,浅克隆方法无法对引用字段进行克隆,引用字段仅仅是对其进行了地址引用。所以,当修改原本或者副本的引用字段的数据时,另一个对象的引用对象的数据同样会变化。深克隆将有效的解决此问题。

2、深克隆

深克隆相对于浅克隆方式比较复杂。深克隆是无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。

深克隆实现的机制是将对象进行序列化为数据后,再次将数据反序列化为新的对象。序列化就是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原对象仍然存在于内存中。通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,可以实现深克隆。注意,在实现序列化前需要在类的上方标记为可序列化。本文采用的序列化方式为二进制序列化。

主要实现的代码如下:

[Serializable]//标记特性:可序列化
public class Student
{
/// <summary>
/// 值类型
/// </summary>
public int ID { get; set; }
/// <summary>
/// 引用类型
/// </summary>
public object obj { get; set; } public Student Clone( )
{
Student clone = new Student();
using (Stream stream = new MemoryStream())
{
IFormatter formatter = new BinaryFormatter();
try
{
formatter.Serialize(stream, this);
stream.Seek(0, SeekOrigin.Begin);
clone = formatter.Deserialize(stream) as Student;
}
catch (SerializationException e)
{
Console.WriteLine("Failed to serialize. Reason: " + e.Message);
throw;
}
}
return clone;
}
}

深克隆实现机制相对复杂、效率稍慢,但它克服了浅克隆方式的不足,使得克隆对象时将类中的引用类型数据完全克隆为新的对象,而不是引用原本中的对象。如此,在修改双方的引用类型对象的数据时不会对另一方造成干扰。

但为每一个类都实现克隆方式,而重复书写相同代码未免麻烦。因此引入泛型方法。

3、泛型方法实现克隆

泛型的出现使得可以良好的解决在多个类或结构体中都需要进行克隆时重复编写代码的麻烦。在外部只需要使用相关方法即可。其代码如下:

public class Clone
{
/// <summary>
/// 深克隆
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
/// <returns></returns>
public static T DepthClone<T>(T t)
{
T clone = default(T);
using (Stream stream = new MemoryStream())
{
IFormatter formatter = new BinaryFormatter();
try
{
formatter.Serialize(stream, t);
stream.Seek(0, SeekOrigin.Begin);
clone = (T)formatter.Deserialize(stream);
}
catch (SerializationException e)
{
Console.WriteLine("Failed to serialize. Reason: " + e.Message);
throw;
}
}
return clone;
}
}

在外部使用的方法如下:

 Student stu1 = new Student();//实例化一个对象
stu1.obj = new object();//实例化对象中的引用对象
Student stu2 = Clone.DepthClone(stu1);//深克隆对象

4、扩展方法

扩展方法的出现可以很好的解决类自身直接调用克隆方法,而不需要调用静态类的方法,返回对象值。但其本身与泛型方法类似,不过为了使所有类都能使用定义的深克隆方法,此处使用对顶级类Object进行方法的扩展,其返回的值也是object类型。具体方法如下:

/// <summary>
/// 注:扩展方法必须在静态类中
/// </summary>
public static class Clone
{
/// <summary>
/// 深克隆
/// </summary>
/// <param name="obj">原始版本对象</param>
/// <returns>深克隆后的对象</returns>
public static object DepthClone(this object obj)
{
object clone = new object();
using (Stream stream = new MemoryStream())
{
IFormatter formatter = new BinaryFormatter();
try
{
formatter.Serialize(stream, obj);
stream.Seek(0, SeekOrigin.Begin);
clone = formatter.Deserialize(stream);
}
catch (SerializationException e)
{
Console.WriteLine("Failed to serialize. Reason: " + e.Message);
throw;
}
}
return clone;
}
}

使用方法示例:

 Student stu1 = new Student();//实例化一个对象
stu1.obj = new object();//实例化对象中的引用对象
Student stu2 = stu1.DepthClone() as Student;//深克隆对象;注意:在此需要将object对象转换为我们需要的对象类型

详解 c# 克隆的更多相关文章

  1. TortoiseGit学习系列之TortoiseGit基本操作克隆项目(图文详解)

    前面博客 全网最详细的Git学习系列之介绍各个Git图形客户端(Windows.Linux.Mac系统皆适用ing)(图文详解) 全网最详细的Git学习系列之安装各个Git图形客户端(Windows. ...

  2. Git学习系列之Git基本操作克隆项目(图文详解)

    不多说,直接上干货! 想必,能进来看我写的这篇博文的朋友,肯定是了解过. 比如SVN的操作吧,最常见的是 检出(Check out ...), 更新 (Update ...), 以及 提交(Commi ...

  3. Git 常用命令详解

    Git 是一个很强大的分布式版本管理工具,它不但适用于管理大型开源软件的源代码(如:linux kernel),管理私人的文档和源代码也有很多优势(如:wsi-lgame-pro) Git 的更多介绍 ...

  4. 【iOS 使用github上传代码】详解

    [iOS 使用github上传代码]详解 一.github创建新工程 二.直接添加文件 三.通过https 和 SSH 操作两种方式上传工程 3.1https 和 SSH 的区别: 3.1.1.前者可 ...

  5. Hadoop集群搭建安装过程(三)(图文详解---尽情点击!!!)

    Hadoop集群搭建安装过程(三)(图文详解---尽情点击!!!) 一.JDK的安装 安装位置都在同一位置(/usr/tools/jdk1.8.0_73) jdk的安装在克隆三台机器的时候可以提前安装 ...

  6. GitHub详解(GitHub for Windows)

    GitHub详解 GitHub 是一个共享虚拟主机服务,用于存放使用Git版本控制的软件代码和内容项目.它由GitHub公司(曾称Logical Awesome)的开发者Chris Wanstrath ...

  7. Git详解之三 Git分支

    相关文档 — 更多 Git 基础培训.ppt GIT 使用经验.ppt GIT 介绍.pptx GIT 分支管理是一门艺术.docx Eclipse上GIT插件EGIT使用手册.docx git/gi ...

  8. [转载]JavaEE学习篇之——JQuery技术详解

    原文链接:http://blog.csdn.net/jiangwei0910410003/article/details/32102187 1.简介2.工具3.jQuery对象 1.DOM对象转化成j ...

  9. Git 常用命令详解(二)

    Git 是一个很强大的分布式版本管理工具,它不但适用于管理大型开源软件的源代码(如:linux kernel),管理私人的文档和源代码也有很多优势(如:wsi-lgame-pro) Git 的更多介绍 ...

随机推荐

  1. Natasha 4.0 探索之路系列(一) 概况

    Natasha 简介 Natasha 是一个基于 Roslyn 的动态编译类库, 它以极简的 API 完成了动态编译的大部分功能, 使用它可以在程序运行时编译出新的程序集. Natasha 允许开发人 ...

  2. 「Python实用秘技04」为pdf文件批量添加文字水印

    本文完整示例代码及文件已上传至我的Github仓库https://github.com/CNFeffery/PythonPracticalSkills 这是我的系列文章「Python实用秘技」的第4期 ...

  3. java继承子父类构造函数-子类的实例化过程

    1 /* 2 * 子父类中的构造函数的特点. 3 * 在子类构造对象时,发现,访问子类构造函数时,父类也运行了. 4 * 为什么呢? 5 * 原因是:在子类的构造函数中第一行有一个默认的隐式语句.su ...

  4. 简单socket服务器编程

    package socket; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; ...

  5. AT2402 [ARC072D] Dam

    首先我们可以将 \(t_i \times v_i\) 看作一个整体,不妨令 \(x_i = v_i, y_i = t_i \times v_i\) 这样两堆水混合后相当于将两个维度相加,方便了计算. ...

  6. Spring service本类中方法互相调用事物失效问题

    简介 Spring事物利用的是AOP,动态代理采用CGLIB代理(默认,也可以用Proxy代理,但是Proxy代理效率低于CGLIB代理).故只要弄懂Spring的AOP实现,就知道为什么servic ...

  7. JS 高阶函数

    笔记整理自:廖雪峰老师的JS教程 目录 概述 Array中的高阶函数 map(返回新的Array) reduce(返回新的Array) filter(返回新的Array) sort(返回同一Array ...

  8. JS let, var, const的用法以及区别

    本文摘自多位前辈的博文,另外还有一些我的多余补充,摘自地址已补充.非常感谢各位前辈.仅以笔记学习为目的! 深入学习ES6的知识还请访问阮一峰老师的ES6教程 如果不使用let或者const,在JS只有 ...

  9. Win10 提示凭证不工作问题

    感谢大佬:https://cloud.tencent.com/developer/article/1337081 在公司局域网远程自己计算机的时候,突然无法远程了,提示"您的凭据不工作 之前 ...

  10. PHP的这些基础知识你应该熟知

    PHP变量的值类型和引用类型 四种基本类型(int,float,string,boolean)以及复合类型(array)均为值类型,变量间的赋值传递的是值,相当于创建一个副本给新变量. 对象(obje ...