讨论清楚这一点之后,我们还可以得到一个很重要的结论:Dispose不等于Cleanup。Dispose只完成了Cleanup当中的一个步骤:释放非托管资源。一个对象的Cleanup,常常还需要很多其它的操作,将数组清零就是一个例子。.Net也好Java也好,对Cleanup都没有一个很好的处理办法。当初大家以为有了GC什么问题都解决了,结果发现没了destructor好多地方搞不定,只能用Dispose凑数,前面有提到说Using是一个不好的设计,是因为人人常常会误以为有了Using就完成了Cleanup。但Dispose模式用来做Cleanup,天然就有缺陷,所以想限制它的使用场景,但架不住大家的滥用。我们在C里已经受够了这套,C++好不容易解决了,结果C#又回到原点,这真有点讽刺。
明白了Dispose不等Cleanup,Dispose也不应该用作一般意义上的Cleanup之后,我们可以来看一下基类该不该有Dispose方法的问题。
像Stream这样的基类,他表达的是数据访问的接口这样一个概念。基于SRP(Single Responsibility Principle),它不应该承担”是否需要释放资源”这样的职责(Stream实际上应该是一个接口而不是一个基类,用基类来重用代码已经被实践证明是有问题的做法,但考虑到Stream出现得太早这也没办法)。当有一种具体的实现既实现数据访问又需要释放资源的时候,基于ISP(Intece Segregation Principle)它应该同时实现这两个接口。这样就不会出现MemoryStream这种明明没有unmanaged resource,但也要被迫实现Dispose的尴尬。当一个父类或者接口的具体实现是No Operation,或者是Not Implemented的时候,这常常是一个bad smell,说明有设计缺陷。
所以正确的作法是Stream不实现Dispose,FileStream实现Stream和IDisposable,而MemoryStream只实现Stream。大部分情况下,这种实现方法和现在的实现比没有太大的差。假设我们有一个void ProcessStream(Stream input)来基于Stream处理数据,现在常见的写法是:
using(Stream s = CreateFileStream(...))
{ ProcessStream(s); ]
换成新的作法要改成
using (FileStream s = CreateFileStream(...))
{ ProcessStream(s); }
或者
MemoryStream s = CreateMemoryStream(...);
ProcessStream(s);
但有一种情况,用新的做比较尴尬:
Stream s = CreateStream();
ProcessStream(s);
当有些Stream可以Dispose,而有些不可以的时候,需要把s变成一个成员变量,并引入一个新的变量来保存可以dispose的stream,CreateStream会根据是否dispose来相应的设置,然后Dispose只调用在需要调用的人上,在CreateStream里要这么写:
//if FileStream
FileStream fs = CreateFileStream();
this.stream = fs;
this.disposableObject = fs;
// if MemoryStream
本文来自电脑杂谈,转载请注明本文网址:
https://www.pc-fly.com/a/ruanjian/article-46198-2.html
应立即声明