第一章(四) - 委托和事件、拆箱和装箱
提示:个人学习总结,如有错误,敬请指正。
文章目录
一、委托(delegate)
delegate的实例,从功能上来讲,类似于C++的函数指针。
可以把委托理解为一个函数容器,由于委托可以被直接赋值,所以会有函数丢失的风险,
二、event
event 很简单,它在委托delegate上,又做了一次封装,这次封装的意义是,限制用户直接操作delegate委托实例中变量的权限。
其不在可以进行 =操作,但仍然保留+= 和 -=;
三、Action和Func
- Action:C#封装好的委托写法,也会有函数丢失的风险。
- Func: 带返回值
四、装箱和拆箱
- 装箱:值类型实例转换为引用类型实例
- 拆箱:值类型实例转换为引用类型实例
值类型是在声明时就初始化了,因为它一旦声明就有了自己的空间因此它不可能为null,也不能为null。
引用类型在分配内存后,它其实只是一个空壳子,可以认为是指针,初始化后不指向任何空间,因此默认为null。
栈是本着先进后出的数据结构(LIFO)原则的存储机制,
它是一段连续的内存,所以对栈数据的定位比较快速,
而堆则是随机分配的空间, 处理的数据比较多, 无论如何, 至少要两次定位。
堆内存的创建和删除节点的时间复杂度是O(logn)。栈创建和删除的时间复杂度则是O(1),栈速度更快。
那么既然栈速度这么快,全部用栈不就好了。
这又涉及到生命周期问题,由于栈中的生命周期是必须确定的,销毁时必须按次序销毁,
从最后分配的块部分开始销毁,创建后什么时候销毁必须是一个定量,所以在分配和销毁时不灵活,
基本都用于函数调用和递归调用中,这些生命周期比较确定的地方。
相反堆内存可以存放生命周期不确定的内存块,满足当需要删除时再删除的需求,
所以堆内存相对于全局类型的内存块更适合,分配和销毁更灵活。
值类型的数据存储在stack上,直接持有。
引用类型的真实数据存储在heap,持有该地址的指针。
装箱的内部操作:
装箱:
根据相应的值类型在堆中分配一个值类型内存块,再将数据拷贝给它。按三步进行。
- 第一步:在堆内存中新分配一个内存块(大小为值类型实例大小加上一个方法表指针和一个SyncBlockIndex)。
- 第二步:将值类型的实例字段拷贝到新分配的内存块中。
- 第三步:返回内存堆中新分配对象的地址。这个地址就是一个指向对象的引用了。
拆箱:
则更为简单点,先检查对象实例,确保它是给定值类型的一个装箱值,再将该值从实例复制到值类型变量的内存块中。
五、装箱、拆箱对执行效率有哪些影响,如何优化。
影响:由于装箱、拆箱时生成的是全新的对象,不断得分配和销毁内存会不但大量消耗CPU,也同时增加了内存碎片,降低了性能。
避免方法:
- Struct 通过重载函数来避免拆箱、装箱。
- 使用泛型
- 通过统一实现的接口提前拆装箱