嵌套使用Using Statement造成对象被dispose多次 CA2202
前言:
Using语句的用途有好几种,这几种用法都很简单。
我们这里说的C# Using语句是用来释放资源的那个,不是引用的那个。
这里不是教大家如何使用using,只是ColdJokeLife在使用的过程中出现了一点问题,在这里分享一下。
ColdJokeLife在使用FxCop过程中,遇到的CA2202问题,下面分享一下解决过程和分析结果。
没有什么高深的技术,园中的老牛、大虾们可以绕行了。
一、Using简介
使用Using语句来释放资源的原理很简单,使用try/finally包裹代码段,并在代码块结尾,调用对象的Dispose方法。
例如:
/*代码取自MSDN官网*/ // 使用using using (Font font1 = new Font("Arial", 10.0f)) { byte charset = font1.GdiCharSet; } // 等效代码 { Font font1 = new Font("Arial", 10.0f); try { byte charset = font1.GdiCharSet; } finally { if (font1 != null) ((IDisposable)font1).Dispose(); } }
二、出现的问题
前面已经简要介绍了using 的一些用法,我的问题是更复杂一点点的:嵌套使用using。
示例代码如下:
// nested using statements using (Stream stream = new FileStream("file.txt", FileMode.OpenOrCreate)) { using (StreamWriter writer = new StreamWriter(stream)) { // Use the writer object... } }
上例,嵌套使用了using语句,看上去貌似没有什么问题,但是这里会有bug。
bug原因:当内层using代码段执行完毕后,会释放writer,但此时stream也被释放了。
所以,当外层using代码段执行完毕后,会造成stream被释放两次。这就出现问题了。
补充一点:按照正常Dispose的实现要求,实现Dispose方法使其被多次调用时,不会出现错误。
所以,如果正确实现Dispose方法,那么这里的问题就不是错误。
所以,这里这里只是一个隐患,不一定会出现问题。
三、解决办法
这里的解决办法非常简单就是修改代码,不使用嵌套using的方式,自己写try/finally代码段。
// 解决办法:自己写try/finally Stream stream = null; try { stream = new FileStream("file.txt", FileMode.OpenOrCreate); using (StreamWriter writer = new StreamWriter(stream)) { stream = null; // 必须在最开始的位置,原因后面讲
//Use the writer object... } } finally { if(stream != null) stream.Dispose(); }
四、原因分析
最开始,我猜想是using在代码结束时,将用到的所有对象都释放了,包括外层using中定义的对象。
但是这种猜想不太可能,因为前面对using的分析看来,编译器不可能去做这些多余的事情,它只会帮你释放using创建的那些对象。
去度娘、谷哥、StackOverflow都没有找到想要的答案,就去看内层Using中创建的StreamWriter的Dispose方法,
果然找到了答案!
StreamWriter的Dispose方法中一段这样的代码:
// 如果LeaveOpen为false,那么就释放掉steam对象 // 这里的stream是通过构造函数传入的,即我们例子中的FileStream if (!this.LeaveOpen) { if (this.stream != null) { try { if (disposing) this.stream.Close(); } // ... }
从这段代码中,就可以看出来是内层的对象在Dispose方法时,帮我们把外层的对象也释放了。
这是StreamWriter的机制,不是using嵌套的问题。
所以,我们再来看看刚才写的解决办法:
1、将外层using换成我们写的try/finally
这么做是为了防止嵌套的时候,外层using再次去dispose造成问题。
如果没有出现异常,内层using正常dispose streamWriter时,stream也被dispose,而且stream也被设置为null。
所以不会再被dispose。如果出现异常,streamWriter没有被正常dispose,那么保证stream可以在finally中被释放。
2、stream = null; 语句必须放在最前面
因为假如内层using中出现异常,因为using会包裹一层try/finally,所以streamWriter肯定会被dispose,所以stream不用被dispose。
所以,只要正常运行到using代码块内,说明不用再去释放stream了,那么就必须把它设置为null,防止多次dispose。
五、总结
1、嵌套的using并不是不能使用
产生多次Dispose的原因,是内层对象(例如:StreamWriter)的Dispose机制(LeaveOpen)。
它与using的嵌套无关,所以如果内层对象与外层对象间无关系的话,应该还是可以正常使用的。
2、使用嵌套using要小心
当我们不太清除内层对象是否与外层对象有关系时,需要小心,因为一旦不注意,就会造成外层对象被多次释放。
而且在多层次嵌套时,更加要小心。
最后,贴上一个4层嵌套的例子:
// 4层嵌套代码 using (var cryptoProvider = new DESCryptoServiceProvider()) { using (var ms = new MemoryStream(byEnc)) { using (var cst = new CryptoStream(ms, cryptoProvider.CreateDecryptor(byKey, byIv), CryptoStreamMode.Read)) { using (var sr = new StreamReader(cst)) { // 业务逻辑 } } } } // 修改后的代码 DESCryptoServiceProvider cryptoProvider = null; MemoryStream memoryStream = null; CryptoStream cryptoStream = null; try { cryptoProvider = new DESCryptoServiceProvider(); memoryStream = new MemoryStream(encryptData); cryptoStream = new CryptoStream(memoryStream, cryptoProvider.CreateDecryptor(rgbKey, rgbIv), CryptoStreamMode.Read); using (var streamReader = new StreamReader(cryptoStream)) { memoryStream = null; cryptoStream = null;
// 业务逻辑 } } finally { if (cryptoProvider != null) { cryptoProvider.Dispose(); } if (cryptoStream != null) { cryptoStream.Dispose(); memoryStream = null; } if (memoryStream != null) { memoryStream.Dispose(); } }
自己查资料、分析,尝试后的一些东西,希望与大家分享一下。
希望对大家有帮助。
出处:http://www.cnblogs.com/ColdJokeLife/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,如有问题,请联系我,非常感谢。