手把手wordpress仿站,网站开发工作分解结构的树形图,网站建设的费用估算,网页制作教程第二版源管理#xff08;尤其是内存回收#xff09;曾经是程序员的噩梦#xff0c;不过在.NET平台上这个噩梦似乎已经不复存在。CLR在后台为垃圾回收做了很多事情#xff0c;使得我们现在谈起在.NET上进行开发时#xff0c;都会说还是new一个对象吧#xff01;回收#xff1f;… 源管理尤其是内存回收曾经是程序员的噩梦不过在.NET平台上这个噩梦似乎已经不复存在。CLR在后台为垃圾回收做了很多事情使得我们现在谈起在.NET上进行开发时都会说还是new一个对象吧回收有垃圾回收器呢。其实并没有这么简单。 对象序列化是现代软件开发中的一项重要技术无论是本地存储还是远程传输都会使用序列化技术来保持资源管理1.显式释放资源需继承接口IDisposableC#中的每一个类型都代表一种资源而资源又分为两类托管资源 由CLR管理分配和释放的资源即从CLR里new出来的对象。非托管资源 不受CLR管理的对象如Windows内核对象或者文件、数据库连接、套接字、COM对象等。如果我们的类型使用到了非托管资源或者需要显式地释放托管资源那么就需要让类型继承接口IDisposable这毫无例外。这相当于告诉调用者类型对象是需要显式释放资源的你需要调用类型的Dispose方法。一个标准的继承了IDisposable接口的类型应该像下面这样去实现。这种实现我们称为Dispose模式承IDispose接口也为实现语法糖using带来了便利。在C#编码中如果像下面这样使用using编译器会自动为我们生成调用Dispose方法的IL代码2.即使提供了显式释放方法也应该在终结器中提供隐式清理在标准的Dispose模式中我们注意到一个以开头的方法如下所示Copy这个方法叫做类型的终结器。提供终结器的意义在于我们不能奢望类型的调用者肯定会主动调用Dispose方法基于终结器会被垃圾回收器调用这个特点它被用作资源释放的补救措。对于没有继承IDisposable接口的类型对象垃圾回收器则会直接释放对象所占用的内存而对于实现了Dispose模式的类型在每次创建对象的时候CLR都会将该对象的一个指针放到终结列表中垃圾回收器在回收该对象的内存前会首先将终结列表中的指针放到一个freachable队列中。同时CLR还会分配专门的线程读取freachable队列并调用对象的终结器只有到这个时候对象才会真正被标识为垃圾并且在下一次进行垃圾回收时释放对象占用的内存。可以看到实现了Dispose模式的类型对象起码要经过两次垃圾回收才能真正地被回收掉因为垃圾回收机制会首先安排CLR调用终结器。基于这个特点如果我们的类型提供了显式释放的方法来减少一次垃圾回收同时也可以在终结器中提供隐式清理以避免调用者忘记调用该方法而带来的资源泄漏。注意1 在有的文档中终结器也称做析构器。注意2 如果调用者已经调用Dispose方法进行了显式地资源释放那么隐式释放资源也就是终结器就没有必要再运行了。FCL中的类型GC提供了静态方法SuppressFinalize来通知垃圾回收器这一点。注意查看Dispose方法Copypublic void Dispose(){ Dispose(true); GC.SuppressFinalize(this);}3.Dispose方法应允许被多次调用一个类型的Dispose方法应该允许被多次调用而不抛异常。鉴于这个原因类型内部维护了一个私有的布尔型变量disposed如下所示在实际清理代码的方法中加入了如下的判断语句ifdisposed{ return}在//省略部分的代码方法的最后为disposed赋值为truedisposedtrue这意味着如果类型已经被清理过一次那么清理工作将不再进行。对象被调用过Dispose方法并不表示该对象已经被置为null且被垃圾回收机制回收过内存已经彻底不存在了。事实上对象的引用可能还在。但是对象被Dispose过说明对象的正常状态已经不存在了此时如果调用对象公开的方法应该会为调用者抛出一个ObjectDisposedException。4.在Dispose模式中应提取一个受保护的虚方法真正实现IDisposable接口的Dispose方法并没有做实际的清理工作它其实是调用了下面这个带布尔参数且受保护的虚方法Copy之所以提供这样一个受保护的虚方法是因为考虑了这个类型会被其他类继承的情况。如果类型存在一个子类子类也许会实现自己的Dispose模式。受保护的虚方法用来提醒子类必须在实现自己的清理方法时注意到父类的清理工作即子类需要在自己的释放方法中调用base.Dispose方法。如果不为类型提供这个受保护的虚方法很有可能让开发者设计子类的时候忽略掉父类的清理工作。所以基于继承体系的原因要为类型的Dispose模式提供一个受保护的虚方法。5.在Dispose模式中应区别对待托管资源和非托管资源Dispose模式设计的思路基于如果调用者显式调用了Dispose方法那么类型就该按部就班地将自己的资源全部释放。如果调用者忘记调用Dispose方法了那么类型就假定自己的所有托管资源会全部交给垃圾回收器回收所以不进行手工清理。理解了这一点我们就理解了为什么在Dispose方法中虚方法传入的参数是true而在终结器中虚方法传入的参数是false。6.具有可释放字段的类型或拥有本机资源的类型应该是可释放的我们将C#中的类型分为普通类型和继承了IDisposable接口的非普通类型。非普通类型除了那些包含托管资源的类型外还包括类型本身也包含一个非普通类型的字段的类型。在标准的Dispose模式中我们对非普通类型举了一个例子一个非普通类型AnotherResource。由于AnotherResource是一个非普通类型所以如果现在有这么一个类型它组合了AnotherResource那么它就应该继承IDisposable接口代码如下所示类型AnotherSampleClass虽然没有包含任何显式的非托管资源但是由于它本身包含了一个非普通类型所以我们仍旧必须为它实现一个标准的Dispose模式。除此以外类型拥有本机资源即非托管类型资源它也应该继承IDisposable接口。7.及时释放资源很多人会注意到垃圾回收机制自动为我们隐式地回收了资源垃圾回收器会自动调用终结器于是不禁会问为什么还要主动释放资源呢我们来看以下这个例子如果连续两次单击打开文件按钮系统就会报错如下所示IOException:文件c:\test.txt 正由另一进程使用,因此该进程无法访问此文件。现在来分析在打开文件的方法中方法执行完毕后由于局部变量fileStream在程序中已经没有任何地方引用了所以它会在下一次垃圾回收时被运行时标记为垃圾。那么什么时候会进行下一次垃圾回收呢或者说垃圾回收器什么时候才开始真正进行回收工作呢微软官方的解释是当满足以下条件之一时将发生垃圾回收系统具有低的物理内存。由托管堆上已分配的对象使用的内存超出了可接受的范围。调用GC.Collect方法。几乎在所有情况下我们都不必调用此方法因为垃圾回收器会负责调用它。但在本实例中为了体会一下不及时回收资源的危害所以进行了一次GC.Collect方法的调用大家可以仔细体会运行这个方法所带来的不同。垃圾回收机制中还有一个“代”的概念。一共分为3代0代、1代、2代。第0代包含一些短期生存的对象如示例代码中的局部变量fileStream就是一个短期生存对象。当buttonOpen_Click退出时fileStream就被丢到了第0代但此刻并不进行垃圾回收当第0代满了的时候运行时会认为现在低内存的条件已满足那时才会进行垃圾回收。所以我们永远不知道fileStream这个对象或者说资源什么时候才会被回收。在回收之前它实际已经没有用处却始终占据着内存或者说资源不放这对应用系统来说是一种极大的浪费并且这种浪费还会干扰程序的正常运行如在本实例中由于它始终占着文件资源导致我们不能再次使用这个文件资源了。不及时释放资源还带来另外一个问题。在上面中我们已经了解到如果类型本身继承了IDisposable接口垃圾回收机制虽然会自动帮我们释放资源但是这个过程却延长了因为它不是在一次回收中完成所有的清理工作。本实例中的代码因为fileStream继承了IDisposable接口故第一次进行垃圾回收的时候垃圾回收器会调用fileStream的终结器然后等待下一次的垃圾回收这时fileStream对象才有可能被真正的回收掉。了解了不及时释放资源的危害后现在来改进这个程序如下所示Copy这确实是一种改进但是我们没考虑到方法中的第一行代码可能会抛出异常。如果它抛出异常那么fileStream.Dispose将永远不会执行。于是再一次改进如下所示为了更进一步简化语句还可以使用语法糖“using”关键字。8.必要时应将不再使用的对象引用赋值为null在CLR托管的应用程序中存在一个“根”的概念类型的静态字段、方法参数以及局部变量都可以作为“根”存在值类型不能作为“根”只有引用类型的指针才能作为“根”。当检查到方法内的“根”时如果发现没有任何一个地方引用了局部变量则不管是否已经显式将其赋值为null都意味着该“根”已经被停止。然后垃圾回收器会发现该根的引用为空同时标记该根可被释放。需要注意一下几点局部变量赋值为null无意义因为编译器在编译时就会过滤。类型的静态字段赋值为null是有意义的。是因为类型的静态字段一旦被创建该“根”就一直存在。所以垃圾回收器始终不会认为它是一个垃圾。非静态字段则不存在这个问题。在实际工作中一旦我们感觉到自己的静态引用类型参数占用的内存空间比较大并且用完后不会再使用便可以立刻将其赋值为null。这也许并不必要但这绝对是一个好习惯。试想在一个系统中那些时不时在类型中出现的静态变量吧它们就那样静静地待在内存里一旦被创建就永远不会离开。或许我们可以专门为此写一个小建议那就是尽量少用静态变量。序列化1.为无用字段标注不可序列化序列化是指这样一种技术把对象转变成流。相反的过程我们称为反序列化。在很多的场合都需要用到这项技术例如把对象保存到本地在下次运行程序的时候恢复这个对象。把对象传到网络中的另外一台终端上然后在此终端还原这个对象。其他的场合如把对象复制到系统的粘贴板中然后用快捷键CtrlV恢复这个对象。有以下几方面的原因决定了要为无用字段标注不可序列化节省空间。类型在序列化后往往会存储到某个地方如数据库、硬盘或内存中如果一个字段在反序列化后不需要保持状态那它就不应该被序列化这会占用宝贵的空间资源。反序列化后字段信息已经没有意义了。如Windows内核句柄在反序列化后往往已经失去了意义所以它就不应该被序列化。字段因为业务上的原因不允许被序列化。例如明文密码不应该被序列化后一同保存在文件中。如果字段本身所对应的类型在代码中未被设定为可序列化那它就该被标注不可序列化否则运行时会抛出异常SerializationException。注意1.由于属性本质上是方法所以不能将NonSerialized特性应用于属性上在标识某个属性不能被序列化时自动实现的属性显然已经不能使用。2.要让事件不能被序列化需使用改进的特性语法fieldNonSerialized。2.利用定制特性减少可序列化的字段特性attribute可以声明式地为代码中的目标元素添加注解。运行时可以通过查询这些托管模块中的元数据信息达到改变目标元素运行时行为的目的。在System.Runtime.Serialization命名空间下有4个这样的特性下面是MSDN上对它们的解释OnDeserializedAttribute当它应用于某方法时会指定在对象反序列化后立即调用此方法。OnDeserializingAttribute当它应用于某方法时会指定在反序列化对象时调用此方法。OnSerializedAttribute如果将对象图应用于某方法则应指定在序列化该对象图后是否调用该方法。OnSerializingAttribute当它应用于某个方法时会指定在对象序列化前调用此方法。示例3.使用继承ISerializable接口更灵活地控制序列化过程除了利用特性Serializable之外我们还可以注意到在序列化的应用中常常会出现一个接口ISerializable。接口ISerializable的意义在于如果特性Serializable以及与其相配套的OnDeserializedAttribute、OnDeserializingAttribute、OnSerializedAttribute、OnSerializingAttribute、NonSerialized等特性不能完全满足自定义序列化的要求那就需要继承ISerializable了。例如我们要将一个对象反序列化成为另外一个对象就要都实现ISerializable接口原理其实很简单那就是在一个对象的GetObjectData方法中处理序列化在另一个对象的受保护构造方法中反序列化。4.实现ISerializable的子类型应负责父类的序列化#我们将要实现的继承自ISerializable的类型Employee有一个父类Person假设Person没有实现序列化而现在子类Employee却要求能够满足序列化的场景。不过很遗憾序列化器没有默认去处理Person类型对象需要我们在子类中受保护的构造方法和GetObjectData方法为它们加入父类字段的处理总结如有需要 上一篇的《C#规范整理·泛型委托事件》也可以看看原文地址https://www.cnblogs.com/zhan520g/p/11061237.html.NET社区新闻深度好文欢迎访问公众号文章汇总 http://www.csharpkit.com