某項目需要輸出一個資料檔案,該檔案由2部分組成,即檔案頭資訊和資料。
項目是使用C#語言在.NET Framework 4上建立的。
拿到這個需求,首先想到的是定義一個Writer類,在寫入方法中建立一個檔案流,使用BinaryWriter封裝,寫入所需要的各種資料。看起來就像這樣:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<code>public</code> <code>void</code> <code>Write()</code>
<code>{</code>
<code> </code><code>using</code> <code>(Stream stream = OpenFileStream())</code>
<code> </code><code>using</code> <code>(BinaryWriter writer = </code><code>new</code> <code>BinaryWriter(stream))</code>
<code> </code><code>{</code>
<code> </code><code>WriteHeader(writer);</code>
<code> </code><code>WriteData(writer);</code>
<code> </code><code>}</code>
<code>}</code>
<code>private</code> <code>void</code> <code>WriteHeader(BinaryWriter writer)</code>
<code> </code><code>// ......</code>
<code>private</code> <code>void</code> <code>WriteData(BinaryWriter writer)</code>
不過随後需求就發生了變化,因為資料敏感,需要加密。于是想到對檔案頭部分使用BinaryWriter寫入,而後面的資料部分,先使用CryptoStream包裝流,再使用BinaryWriter寫入。于是改成這樣:
<code> </code><code>using</code> <code>(BinaryWriter headerWriter = </code><code>new</code> <code>BinaryWriter(stream))</code>
<code> </code><code>{</code>
<code> </code><code>WriteHeader(headerWriter);</code>
<code> </code><code>}</code>
<code> </code><code>using</code> <code>(CryptoStream cryptoStream</code>
<code> </code><code>= </code><code>new</code> <code>CryptoStream(stream, GetCryptoTransform(),</code>
<code> </code><code>CryptoStreamMode.Write))</code>
<code> </code><code>using</code> <code>(BinaryWriter dataWriter = </code><code>new</code> <code>BinaryWriter(cryptoStream))</code>
<code> </code><code>WriteData(dataWriter);</code>
改過之後,問題産生了——在使用headerWriter的using語句結束時,會自動調用headerWriter.Dispose(),而這個方法會調用BaseStream.Close(),也就是說,檔案流被關閉了,那麼後面嘗試寫入資料時就會抛出異常。
雖然BinaryWriter有一個構造方法可以申明不關閉流:
<code>public</code> <code>BinaryWriter(</code>
<code> </code><code>Stream output,</code>
<code> </code><code>Encoding encoding,</code>
<code> </code><code>bool</code> <code>leaveOpen</code>
<code>)</code>
但這個構造方法是在.NET 4.5才加入的,項目是用的4.0的Framework,是以必須另外想辦法。而且後面的CryptoStream也存在同樣的問題,而它可沒有提供不關閉流的構造。
這裡可以想到兩個辦法來處理:
1. 在所有内容都寫完之後再統一Dispose各種操作對象和流對象。
2. 定義一個從Stream繼承的StreamWrapper,将Close和Dispose都重載并實作為空方法,再定義一個ReallyClose方法來真正關閉封裝的流。
使用第1種方法,就像這樣:
<code> </code><code>using</code> <code>(Stream s = OpenFileStream())</code>
<code> </code><code>using</code> <code>(BinaryWriter headWriter = </code><code>new</code> <code>BinaryWriter(s))</code>
<code> </code><code>using</code> <code>(CryptoStream cs = </code><code>new</code> <code>CryptoStream(s, GetCryptoTransform(),</code>
<code> </code><code>CryptoStreamMode.Write))</code>
<code> </code><code>using</code> <code>(BinaryWriter dataWriter = </code><code>new</code> <code>BinaryWriter(cs))</code>
<code> </code><code>WriterHeader(headWriter);</code>
<code> </code><code>WriteData(dataWriter);</code>
而且如果一個檔案分成了很多很多段的話,這個using清單就太長了。但這不是問題,問題是如果WriteHeader中抛出異常,那麼cs和dataWriter這兩個對象就浪費了。是以,可以考慮使用try {...} finally {...} 來實作using,
18
19
20
21
22
23
<code> </code><code>Stream stream = </code><code>null</code><code>;</code>
<code> </code><code>BinaryWriter headWriter = </code><code>null</code><code>;</code>
<code> </code><code>CryptoStream cs = </code><code>null</code><code>;</code>
<code> </code><code>BinaryWriter dataWriter = </code><code>null</code><code>;</code>
<code> </code><code>try</code>
<code> </code><code>stream = OpenFileStream();</code>
<code> </code><code>headWriter = </code><code>new</code> <code>BinaryWriter(stream);</code>
<code> </code><code>cs = </code><code>new</code> <code>CryptoStream(stream, GetCryptoTransform(), CryptoStreamMode.Write);</code>
<code> </code><code>dataWriter = </code><code>new</code> <code>BinaryWriter(cs);</code>
<code> </code><code>finally</code>
<code> </code><code>if</code> <code>(dataWriter != </code><code>null</code><code>) { dataWriter.Dispose(); }</code>
<code> </code><code>if</code> <code>(cs != </code><code>null</code><code>) { cs.Dispose(); }</code>
<code> </code><code>if</code> <code>(headWriter != </code><code>null</code><code>) { headWriter.Dispose(); }</code>
<code> </code><code>if</code> <code>(stream != </code><code>null</code><code>) { stream.Dispose(); }</code>
解決問題,但代碼量大,而且容易出錯。比如,要記得在WriterHeader裡面Flush,這個原本會在Dispose()自動執行的操作(對于CryptoStream,需要執行FlushFinalBlock())。
相對來說,寫一個StreamWrapper靠譜多了。不過使用RealClose就失去了IDisposable的意義,是以稍稍改變一下,定義一個變量來允許Dispose時關閉流。
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<code>class</code> <code>StreamWrapper : Stream</code>
<code> </code><code>private</code> <code>readonly</code> <code>Stream stream;</code>
<code> </code><code>public</code> <code>Stream Stream { </code><code>get</code> <code>{ </code><code>return</code> <code>stream; } }</code>
<code> </code><code>public</code> <code>StreamWrapper(Stream stream)</code>
<code> </code><code>this</code><code>.stream = stream;</code>
<code> </code><code>IsLeavingOpen = </code><code>true</code><code>;</code>
<code> </code><code>public</code> <code>override</code> <code>void</code> <code>Flush()</code>
<code> </code><code>stream.Flush();</code>
<code> </code><code>public</code> <code>override</code> <code>long</code> <code>Seek(</code><code>long</code> <code>offset, SeekOrigin origin)</code>
<code> </code><code>return</code> <code>stream.Seek(offset, origin);</code>
<code> </code><code>public</code> <code>override</code> <code>void</code> <code>SetLength(</code><code>long</code> <code>value)</code>
<code> </code><code>stream.SetLength(value);</code>
<code> </code><code>public</code> <code>override</code> <code>int</code> <code>Read(</code><code>byte</code><code>[] buffer, </code><code>int</code> <code>offset, </code><code>int</code> <code>count)</code>
<code> </code><code>return</code> <code>stream.Read(buffer, offset, count);</code>
<code> </code><code>public</code> <code>override</code> <code>void</code> <code>Write(</code><code>byte</code><code>[] buffer, </code><code>int</code> <code>offset, </code><code>int</code> <code>count)</code>
<code> </code><code>stream.Write(buffer, offset, count);</code>
<code> </code><code>public</code> <code>override</code> <code>bool</code> <code>CanRead { </code><code>get</code> <code>{ </code><code>return</code> <code>stream.CanRead; } }</code>
<code> </code><code>public</code> <code>override</code> <code>bool</code> <code>CanSeek { </code><code>get</code> <code>{ </code><code>return</code> <code>stream.CanSeek; } }</code>
<code> </code><code>public</code> <code>override</code> <code>bool</code> <code>CanWrite { </code><code>get</code> <code>{ </code><code>return</code> <code>stream.CanWrite; } }</code>
<code> </code><code>public</code> <code>override</code> <code>long</code> <code>Length { </code><code>get</code> <code>{ </code><code>return</code> <code>stream.Length; } }</code>
<code> </code><code>public</code> <code>override</code> <code>long</code> <code>Position</code>
<code> </code><code>get</code> <code>{ </code><code>return</code> <code>stream.Position; }</code>
<code> </code><code>set</code> <code>{ stream.Position = value; }</code>
<code> </code><code>public</code> <code>override</code> <code>void</code> <code>Close()</code>
<code> </code><code>if</code> <code>(IsLeavingOpen)</code>
<code> </code><code>return</code><code>;</code>
<code> </code><code>stream.Close();</code>
<code> </code><code>base</code><code>.Close();</code>
<code> </code><code>protected</code> <code>override</code> <code>void</code> <code>Dispose(</code><code>bool</code> <code>disposing)</code>
<code> </code><code>if</code> <code>(disposing)</code>
<code> </code><code>stream.Dispose();</code>
<code> </code><code>base</code><code>.Dispose(disposing);</code>
<code> </code><code>public</code> <code>bool</code> <code>IsLeavingOpen { </code><code>get</code><code>; </code><code>set</code><code>; }</code>
如果稍稍改變一下寫入資料的接口,使用起來也非常友善
<code> </code><code>using (Stream fileStream = OpenFileStream())</code>
<code> </code><code>using (StreamWrapper stream = </code><code>new</code> <code>StreamWrapper(fileStream))</code>
<code> </code><code>WriteHeader(stream);</code>
<code> </code><code>WriteData(stream);</code>
<code> </code><code>stream.IsLeavingOpen = </code><code>false</code><code>;</code>
<code>private</code> <code>void</code> <code>WriteHeader(Stream stream)</code>
<code> </code><code>using (BinaryWriter writer = </code><code>new</code> <code>BinaryWriter(stream))</code>
<code> </code><code>// ......</code>
<code>private</code> <code>void</code> <code>WriteData(Stream stream)</code>
<code> </code><code>using (CryptoStream cryptoStream = </code><code>new</code> <code>CryptoStream(stream,</code>
<code> </code><code>GetCryptoTransform(), CryptoStreamMode.Write))</code>
<code> </code><code>using (BinaryWriter writer = </code><code>new</code> <code>BinaryWriter(cryptoStream))</code>
<code></code>
本文轉自邊城__ 51CTO部落格,原文連結:http://blog.51cto.com/jamesfancy/1359515,如需轉載請自行聯系原作者