除非语言里只有基础类型,没有引用类型,否则用任何一种编程语言克隆对象都是很棘手的事情。

1、前言

“老沉,什么是深克隆,什么是浅克隆?”
“哈,迷茫了? 这深深浅浅的体验是不是把你搞晕了?”
“嗯,这都是啥程序员黑话吗?”
“这是专业术语!因为有“引用类型”这个概念,所以才引申出来深克隆和浅克隆的名词。”
“我们先聊聊堆(Heap)和栈(Stack)吧。”
“好啊,老沉,听你说说!”

2、堆和栈

  • 堆:在.net 中准确的说是托管堆,它由 CLR 管理,当堆满了后,会自动清理垃圾,所以做.net开发,基本不需要关心内存的释放,原理还是需要了解的

另外,根据引用类型实例的大小,“堆"分为"GC堆"和"LOH(Large Object Heap)堆”,当引用类型实例大小小于85000个字节的时候,实例被分配在"GC堆"上;当实例大小大于或等于于85000个字节的时候,实例被分配在"LOH(Large Object Heap)堆"。

  • 栈: 翻译起来应该是堆栈,因为老和堆放一块,感觉容易混,因此现在一般都简称为栈。

堆和栈是程序运行时,数据主要存放的两个存储区。
堆区存放引用类型的对象,主要由CLR/GC释放;
栈区存放函数的参数、局部变量、返回数据,其内存无需我们管理,也不接受GC管理,当栈元素被弹出后,立马释放。

栈区大小在32位应用下有1MB大小,64位应用是4MB。 为啥是这样的呢?看看下面这位David Cutler,就是他制定的规则,这是windwosNT系统的统一标配,和.net关系不大。

当程序的EXE 栈大小 或CreateThread()调用未明确指定堆栈大小时,它将选择一MB字节,几乎所有程序员都将其留给操作系统来选择大小。

当然1MB字节是很多的,一个真正的线程很少消耗超过几千字节的内存。因此,兆字节实际上是相当浪费的。并且由于Windows的内存机制,在按需分页的虚拟内存操作系统上您可以承受这种浪费,兆字节只是虚拟内存,只是处理器的编号,在实际寻址之前,您永远不会真正使用物理内存(机器中的RAM)。

在.net世界, 我们的程序一般不接受程序员自己分配栈空间,除非在不安全的模式使用stackalloc关键字。

也许你会觉得1MB有点小,其实真相恰恰是相反的,1MB已经太多了,这使得操作系统创建线程的能力大大降低,实际上 asp.net 可能只有 256KB~512KB之间,甚至于实际的使用可能只在 4KB左右,并且操作系统会倾向于进行优化,只提交你需要的堆栈大小。

windows在管理线程栈时的自动增加示意图。

通过ILDASM,查看PE头,可以看到实际已经分配的栈空间大小。


.net 运行时的栈提交方式也可以进行修改。

公共语言运行时的默认行为是在启动线程时提交完整的线程堆栈。如果必须在内存有限的服务器上创建大量线程,并且其中大多数线程将使用很少的堆栈空间,如果公共语言运行时未在线程执行完后立即提交完整的线程堆栈,则服务器的性能可能会更好。

<configuration>  <runtime>  <disableCommitThreadStack enabled="1" />  </runtime>
</configuration>

栈资源代表着什么呢? 简言之,每个线程都需要1MB的栈大小,先不管其他的消耗,在windows 32位系统下, 因为只有2GB的内存地址空间,因此最多只能创建 2048 个线程,当然实际上会比这小。这样反推,windwos 64位操作系统也仅仅能创建 4096个线程。当然这个没考虑windows对栈提交的优化,如果它按需提交,那同样内存下,会支撑更多的线程创建。

因此如果要提升windwos创建线程的能力,需要降低默认的1MB的设定,当然有工具去修改这个设置,有兴趣的朋友可以去搜搜Testlimit

为什么要从内存转移到栈或“加载”?另一方面,为什么要从栈转移到内存或“存储”呢?为什么不将它们全部都放在内存中呢?

因为简单!

因为从概念上讲,栈对于语言编译器编写者来说非常简单。栈是一种用于描述计算的简单易懂的机制。对于JIT编译器作者来说,栈在概念上也非常容易。使用栈是一种简化的抽象,因此,它又降低了我们的成本。

您问:“为什么要栈呢?” 为什么不直接将所有内存都耗尽?

好吧,让我们考虑一下。假设您要生成以下内容的CIL代码:
int x = A() + B() + C() + 10;

只有栈,则假设我们有一个约定,即“ add”,“ call”,“ store”等始终将其参数移出栈,并将其结果(如果有的话)放在栈上。要为此C#生成CIL代码,我们只需要说些类似的话:

load the address of x // The stack now contains address of x
call A()              // The stack contains address of x and result of A()
call B()              // Address of x, result of A(), result of B()
add                   // Address of x, result of A() + B()
call C()              // Address of x, result of A() + B(), result of C()
add                   // Address of x, result of A() + B() + C()
load 10               // Address of x, result of A() + B() + C(), 10
add                   // Address of x, result of A() + B() + C() + 10
store in address      // The result is now stored in x, and the stack is empty.

现在,我们将按照您的方式进行操作,其中每个操作码都将获取其操作数的地址以及将其结果存储到的地址:

Allocate temporary store T1 for result of A()
Call A() with the address of T1
Allocate temporary store T2 for result of B()
Call B() with the address of T2
Allocate temporary store T3 for the result of the first addition
Add contents of T1 to T2, then store the result into the address of T3
Allocate temporary store T4 for the result of C()
Call C() with the address of T4
Allocate temporary store T5 for result of the second addition
...

这是怎么回事吗?我们的代码越来越庞大,因为我们必须显式分配通常按照约定会放在栈上的所有临时存储。更糟糕的是,我们的操作码本身变得越来越庞大,因为它们现在都必须将要写入结果的地址以及每个操作数的地址作为参数。一条“ add”指令知道它将要从堆栈中取出两件事并放在一件事上,这可以是一个字节。一个带有两个操作数地址和一个结果地址的加法指令将非常庞大。

我们使用基于栈的操作码,因为栈可以解决常见的问题。即:我想分配一些临时存储,请尽快使用它,然后在完成后迅速删除它。假设我们有可用的栈,我们可以使操作码非常小,并使代码非常简洁。

3、值类型、引用类型

切入整体,bool 、byte 、char 、decimal 、double 、enum 、float 、int 、long 、sbyte 、short 、struct 、uint 、ulong 、ushort这些值类型都存储在栈内,而class 、interface 、delegate 、object 、string这些类型均存储在堆中。

对于引用类型,都会定义一个指针指向堆内存,因此在我们成为浅拷贝的时候,拷贝的实际是引用类型的指针。而值类型是直接拷贝的。
一个例子:

class Person
{public int Age { get; set; }public Person Father { get; set; }public Person Mother { get; set; }
}

如果我对该对象进行了浅克隆并更改了使用期限,则原始对象的使用期限将不会更改。
但是,如果我随后更改了克隆对象的父亲的属性,那么我也会影响原始对象的父亲,因为未克隆引用。

换一种方式思考,在C#中,当您对对象进行浅克隆时,您相比深克隆“浅一层”,因为变浅了,您要克隆的对象内的任何对象本身也不会递归地克隆。

深度克隆显然是相反的。它一直尝试克隆对象的所有属性,然后克隆该属性的属性。

4、成员克隆

如果您对C#中的克隆进行了研究,则可能遇到了“成员方式”克隆方法。它对每个类均可用,但“仅在该类内部”可用,因为它是Object的受保护方法。您不能在另一个类的对象上调用它。

class Person
{public string Name { get; set; }public Person Father { get; set; }public Person Mother { get; set; }public Person Clone(){return (Person)this.MemberwiseClone();}
}

然而,快速浏览一下智能感知就可以告诉我们一些…

创建当前对象的浅表副本。

因此,在此对象上调用clone只会对其进行浅克隆,而不会进行深克隆。
如果您的对象是纯粹的值对象,那么这实际上可以为您工作,您可以在这里停下来。但是在大多数情况下,我们正在寻找更深的克隆。

5、手动克隆

反正,你最了解你的类,不是吗?

class Person
{public string Name { get; set; }public Person Father { get; set; }public Person Mother { get; set; }public Person Clone(){return new Person{Name = this.Name,Father = this.Father == null ? null : new Person { Name = this.Father.Name },Mother = this.Mother == null ? null : new Person { Name = this.Mother.Name }};}
}

如果属性不多,不失为一个好办法。

6、二进制序列化器克隆

[Serializable]
class Person
{public string Name { get; set; }public Person Father { get; set; }public Person Mother { get; set; }public Person Clone(){IFormatter formatter = new BinaryFormatter();using (stream = new MemoryStream()){formatter.Serialize(stream, this);stream.Seek(0, SeekOrigin.Begin);return (Person)formatter.Deserialize(stream);}}
}

我们必须用[Serializable]属性来修饰我们的类,否则,我们会得到异常错误,这看起来不Nice!
当然你可以改良为一个静态方法

public static class CloningService
{public static T Clone<T>(this T source){// Don't serialize a null object, simply return the default for that objectif (Object.ReferenceEquals(source, null)){return default(T);}IFormatter formatter = new BinaryFormatter();using (stream = new MemoryStream()){formatter.Serialize(stream, source);stream.Seek(0, SeekOrigin.Begin);return (T)formatter.Deserialize(stream);}}
}

当然 [Serializable]标记别忘了。

7、json序列化克隆

public static class CloningService
{public static T Clone<T>(this T source){// Don't serialize a null object, simply return the default for that objectif (Object.ReferenceEquals(source, null)){return default(T);}var deserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace };var serializeSettings = new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore };return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source, serializeSettings), deserializeSettings);}
}

我只能说,每次必须这样做时,我都认为“将其转换为JSON并再次返回确实不是很好……”。
但这是可行的。它可以处理您扔给它的所有东西,而且几乎没有错。

我认为,如果您正在寻找可靠的,可重用的方法来在代码中克隆对象,就是这个。

8、json克隆的循环

眼尖的读者会注意到,在上述JSON克隆服务中,我们有一行处理引用循环。

这是非常非常普遍的情况,尤其是在用作DataModel的一部分的模型中,两个类将相互引用。无论您如何决定克隆对象,始终会遇到对象之间相互引用的问题,并且任何克隆尝试都将陷入无休止的循环

因此,即使在上面的代码中,我们使用一个设置来解决它,也值得指出的是,无论您决定克隆对象的方式如何,这总是一个问题。

9、小结

亲爱的读者,你有什么高招?

.net core 浅克隆和深克隆/浅拷贝和深拷贝相关推荐

  1. C# 浅拷贝与深拷贝

    拷贝(copy): 什么是拷贝:通常意义来说也就是复制,对象的拷贝也就是将对象复制出来一个一样的新对象出来.虽然都是复制对象,但是不同的复制方法,复制出来的对象确不是一样的,存在一些差异. 有哪些拷贝 ...

  2. 创建型设计模式-----原型模式(浅克隆、深克隆)

    目录 简介 为什么用克隆 浅克隆 深克隆 序列化实现深拷贝 应用场景 今天讲创建型设计模式中的原型模式,大家可以从标题中略微猜测一下,这个原型模式是个什么东东. 简介 定义:用原型实例创建对象的种类, ...

  3. java 浅克隆_(Java)浅克隆与深克隆的区别

    克隆,就是复制一个对象的副本,而克隆又分浅克隆和深克隆.浅克隆是指克隆得到的对象基本类型的值改变了,而源对象的值不会变.但如果被克隆对象引用类型的值改变了,那么源对象的值同样会改变,因为引用类型在栈内 ...

  4. javascript---对象和函数的引用、浅拷贝、深拷贝、递归

    1.javascript 对象和函数的引用 <!doctype html> <html lang="en"> <head><meta ch ...

  5. Java中的浅克隆与深克隆

    Java中的浅克隆与深克隆 一:前言 二:浅克隆与深克隆的区别 一:前言 克隆,即复制一个对象,该对象的属性与被复制的对象一致,如果不使用Object类中的clone方法实现克隆,可以自己new出一个 ...

  6. Java浅克隆与深克隆-clone

    文章目录 clone 浅克隆和深克隆的区别 浅克隆和深克隆的特点 另一种参考 浅克隆 深克隆 实现 扩展 解决多层克隆问题 总结 clone 浅克隆和深克隆的区别 浅克隆: 对当前对象进行克隆,并克隆 ...

  7. 基于java实现浅拷贝和深拷贝

    目录 1.概念 2.浅拷贝 2.1.浅拷贝实战 3.深拷贝 3.1.嵌套 clone 方法 3.2.使用序列化流 3.3.使用开源工具类 1.概念 浅拷贝:在拷贝一个对象时,复制基本数据类型的成员变量 ...

  8. Java设计模式之原型模式(浅克隆,深克隆)

    一.原型模式: 概述: 创建型模式之一,它通过复制一个已有对象来获取更多相同或相似的对象,可提高对象创建效率,简化创建过程. 原理: 将一个原型对象传给要发动创建的对象(如客户端对象),这个客户端对象 ...

  9. Java中的浅拷贝与深拷贝

    一.引用拷贝与对象拷贝 class Person implements Cloneable{private String name;private int age;...省略get和set方法 pro ...

最新文章

  1. 判断图有无环_链表:环找到了,那入口呢?
  2. 运维不得不知道的事 数据中心断电如何做
  3. opencv 运动追踪_足球运动员追踪-使用OpenCV根据运动员的球衣颜色识别运动员的球队
  4. python3 array为什么不能放不同类型的数据_来自俄罗斯的凶猛彪悍的分析数据库ClickHouse...
  5. ADO.Net 事务操作
  6. Membership Inference Attacks Against Recommender Systems论文解读
  7. java强制关闭远程桌面_elasticsearch中的java.io.IOException: 远程主机强迫关闭了一个现有的连接...
  8. html5游戏制作入门系列教程(三)
  9. 小程序php支付,前后端分离
  10. linux下简单的邮件配置
  11. dolphin.php 视频,DolphinPHP V1.0.4发布
  12. 5.1 tensorflow2实现简单线性回归分析——python实战
  13. coolfire的八篇入门文章(.txt)
  14. Jenkins linux 操作系统一键部署多节点
  15. ios:苹果手机直接安装ipa文件
  16. 先睹为快,Go2 Error 的挣扎之路
  17. 小猫爪:动手笔记01-FreeRTOS移植
  18. reducer在react使用
  19. c语言程序书写时,C语言程序书写规范
  20. log4j2配置详情

热门文章

  1. 配电室的管理制度及综合监控系统的介绍
  2. java下载文件总结
  3. python安装了包在pycharm上看不到_解决Pycharm 包已经下载,但是运行代码提示找不到模块的问题...
  4. 劲舞团app修改服务器文件,“劲舞团服务器端”配置文件分析
  5. 【camera-tuning】杂记---镜子的颜色
  6. python与数据处理课后答案_python数据处理与科学计算_课程2020最新章节测试网课课后答案...
  7. 一种用剪刀型构件和弹簧拉紧的天然气罐对接机构
  8. NOIP2011普及组 瑞士轮
  9. 微信小程序-从无到有
  10. QtWebassembly遇到的一些报错问题及解决方案