如果你跟我一樣,習慣性的優先使用 “using declaration” 來撰寫 using
語句,並且翻寫範例時會自動將 “using statement” 轉換成 “using declaration”,那麼不久的將來你可能也會遇到這個問題。
using declaration
在 C# 8 更新中推出了 using declaration,它是對 using
語句的簡化和擴展。使用 “using declaration” 有以下幾個好處:
程式碼簡潔性:讓程式碼更簡潔,省去了
using
語句的需求,使程式碼看起來更整潔,特別是當有多個using
語句時。例如,對比傳統的using
語句 “using statement”:1
2
3
4using (var stream = new MemoryStream())
{
// 使用 stream
}與 “using declaration”:
1
2using var stream = new MemoryStream();
// 使用 stream範圍性:只在當前的範圍中有效。在範圍結束時,相應的資源會被自動關閉和釋放,不需要額外的
Dispose()
調用。關閉的順序如下,從最後一個到第一個:1
2
3
4
5
6
7
8{
using var f1 = new FileStream("...");
using var f2 = new FileStream("..."), f3 = new FileStream("...");
...
// Dispose f3
// Dispose f2
// Dispose f1
}可讀性:提高了程式碼的可讀性,使開發者更容易識別出哪些變數是具有限定範圍的。
1
using var stream = new MemoryStream();
與
1
var stream = new MemoryStream();
總而言之,”using declaration” 提供了一種更簡潔、可讀性更高的方式來處理需要釋放的資源,同時還具有範圍性,有助於減少資源泄漏的可能性,使程式碼更加安全可靠。
BUT!!! 會寫這篇文章就是因為我在使用 “using declaration” 時遇到了一個坑,讓我不得不重新思考使用 “using declaration” 的時機。如果你跟我一樣,習慣性的優先使用 “using declaration” 來撰寫 using
語句,並且翻寫範例時會自動將 “using statement” 轉換成 “using declaration”,那麼你可能也會遇到這個問題。
錯誤案例
這次我碰到的就是 Zip 操作時常用的類別 ZipArchive,以下是常見的 ZipArchive 使用方式:
1 | private static byte[] CompressFilesToZipWithUsingStatement(Dictionary<string, byte[]> filesToCompress) |
但如果我們不經過大腦與測試將所有 “using statement” 改成 “using declaration”,就會發生一點問題:
1 | private static byte[] CompressFilesToZipWithUsingDeclaration(Dictionary<string, byte[]> filesToCompress) |
以下是使用範例:
1 | var filesToCompress = new Dictionary<string, byte[]>() |
如果執行上面的程式碼,你會發現 “using declaration” 後產生的壓縮檔案是壞的,而 “using statement” 產生的壓縮檔案是正確的。
釋放資源的時機
不是所有類別在釋放資源時,只是單純地釋放資源,也可能會進行額外的操作。
讓我們來看看 ZipArchive 在 Dispose
時會發生什麼事情:
1 | protected virtual void Dispose(bool disposing) |
可以看到當 ZipArchiveMode 為 Create 或 Update 時,會呼叫 WriteFile
方法:
1 | private void WriteFile() |
這邊我們只需要關注在最後一個方法 WriteArchiveEpilogue
:
1 | // writes eocd, and if needed, zip 64 eocd, zip64 eocd locator |
看方法說明可以知道是用於寫入壓縮檔案的結尾,那這個與壓縮檔錯誤有什麼關係呢?回頭看原先的語法:
1 | private static byte[] CompressFilesToZipWithUsingDeclaration(Dictionary<string, byte[]> filesToCompress) |
還記得 “using declaration” 的資源釋放時機是區塊的結束,也就是說在 return zipMemoryStream.ToArray();
結束離開區塊後。所以在 zipArchive
釋放資源之前,也就是寫入壓縮檔的結尾之前,就已經將 zipMemoryStream
轉換成 byte[] 並回傳了,所以壓縮檔案當然會壞掉!
心法
除非明確了解類別的 Dispose
行為,否則 IDE 提示你簡化時你在簡化,可以看到圖片中只有 MemoryStream
與 zipEntry.Open()
(Stream) 兩個類型時會有修正通知,這是由於 Visual Studio 分析器(Overview of source code analysis)判斷出這兩個類型在 Dispose
時只是單純地釋放資源,不會有額外的操作,所以可以安全地轉換成 “using declaration”。