天天看點

分段輸出到同一個Stream(.NET)

 某項目需要輸出一個資料檔案,該檔案由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,如需轉載請自行聯系原作者

繼續閱讀