關於 C# 的 using 陳述式在實務應用上的基本觀念

分享到噗浪!

之前偶有機會看到 MSDN 論壇上有人提到關於使用 using 陳述式的錯誤觀念 (看到不止一次),由於自己也經常在用,不知道原來有人會誤以為使用 using 會將所有例外狀況 (Exception) 給吃掉,但事實上並非如此,請讓我娓娓道來。

using 有兩種,相信許多人經常會用到:

1. 一種是作為「指示詞」,可用來建立命名空間的別名(Alias),或用來匯入在其他命名空間中定義的型別
2. 另一種是「陳述式」,可用來定義一個範圍,該範圍內的物件會在此範圍結尾處執行 Dispose 方法。

第一種:using 指示詞

標準用法:

using System.Text;

別名用法:

using Models = MvcApplication.Models;

第二種:using 陳述式 ( 也是我今天想強調的部分 )

using 陳述式是一個非常基本的 C# 語法,相信在實務開發上經常會使用到,如果沒使用過的人可能代表你對 .NET 的記憶體管理的 Sense 不太夠,很有可能寫出資源耗盡的 .NET 程式碼。

雖然 .NET 有內建強大的記憶體管理機制(GC),但開發人員還是不能完全依賴 .NET 來處理一些無法釋放的資源,例如:Handles, Unmanaged Resources, …。

而使用 using 最主要的目的是為了讓物件建立的同時能確保該物件所佔用的資源一定會被完整釋放,如果沒有釋放這些無法自動釋放的資源,就很有可能讓 .NET 應用程式發生 資源耗盡 (Resource Exhausted) 的狀況。

從 MSDN 上節錄一段範例程式如下:

using (System.IO.StreamReader sr = 
         new System.IO.StreamReader(@"C:\test.txt"))
{
    string s = null;
    while((s = sr.ReadLine()) != null)
    {
        Console.WriteLine(s);
    }
}

使用 using 陳述式有一個最基本的條件,就是該物件必須有實做 IDisposable 介面,才能確保在 using 的結尾數時自動執行 Dispose() 方法

使用 using 陳述式的另一個強烈建議的條件,就是該物件被建立的語法必須寫在 using 子句中,否則物件很有可能在被釋放後還有其他物件存取的情況,進而引發例外狀況!

有些人認為 using 陳述式會自動被翻譯成以下程式碼:(錯誤範例

{
System.IO.StreamReader sr = new System.IO.StreamReader(@"C:\test.txt");
try
{
string s = null;
while((s = sr.ReadLine()) != null)
{
Console.WriteLine(s);
}
}
catch
{
}
finally
{
if (sr != null)
((IDisposable)sr).Dispose();
}
}

但事實上,應該不包含 catch 的區段才是!正確的語法應該如下:

{
	System.IO.StreamReader sr = new System.IO.StreamReader(@"C:\test.txt");
	try
	{
	  string s = null;
	  while((s = sr.ReadLine()) != null)
	  {
		  Console.WriteLine(s);
	  }
	}
	finally
	{
	  if (sr != null)
		((IDisposable)sr).Dispose();
	}
}

也就是在使用 using 陳述式時,雖然有 tryfinally,但並不包含 catch 的成分

換句話說,使用 using 陳述式並不會幫你捕捉例外狀況!!

所以就算你用 using 在程式中,若要處理 try-catch 的狀況還是要自行撰寫,否則就會遇到非預期的例外狀況而導致應用程式掛掉,這點必須特別注意!

相關連結

  

此文章由 will 發表於 2009/10/12 下午 10:27:25

永久連結 | 評論 (10) | 此文章的RSSRSS comment feed |

分類: C# | .Net | Tips

標籤: , , ,

評論

十月 13. 2009 00:03

Kalin

Hi 保哥,

請問一下,是否 using 加上 try catch 的正確用法為:
using (object a) {
  try {
  } catch {
  }
}

try {
  using (object b) {
  }
} catch {
}

那麼使用第二種寫法時,
當有發生 exception 時, 在 try 區塊內被建立的 object b, 是否也一定Dispose()了?

Kalin 台灣

十月 14. 2009 14:12

Will 保哥

Kalin:

是的,只要加上 using 就保證一定會執行 Dispose()

Will 保哥 台灣

十月 15. 2009 03:02

Typed ROBIN


如果沒有釋放這些無法自動釋放的資源,就很有可能讓 .NET 應用程式發生 記憶體洩漏 (Memory Leak) 的狀況。

這句不妥,應是說如果沒有釋放這些資源,會耗盡資源。
釋放資源跟記憶體洩漏沒有直接關係。

Typed ROBIN 台灣

十月 15. 2009 03:13

Will 保哥

Typed ROBIN:  

我瞭解你的看法與角度,如果針對 Managed 的程式碼來說的確有可能會遇到 OutOfMemoryException 的例外狀況 (耗盡資源)。

但如果 .NET 執行到 Unmanaged 的程式碼,的確有可能發生 Memory Leak 的問題。

Will 保哥 台灣

十月 15. 2009 03:23

Typed ROBIN

耗盡資源不等同於耗盡記憶體。
在耗盡記憶體之前,我相信你會先遭遇耗盡資源。

珍貴的資源要儘快放掉,因為數量有限,比如說database connection,比如說IO...

Typed ROBIN 台灣

十月 15. 2009 03:36

Will 保哥

我同意『耗盡資源不等同於耗盡記憶體』

但『在耗盡記憶體之前,我相信你會先遭遇耗盡資源』
=> 那可不一定,我之前有遇過先耗盡記憶體的狀況,尤其是 Web 應用程式在 32bit 作業系統下處理超大型檔案時最常發生,我之前遭遇過一、兩次。

Will 保哥 台灣

十月 15. 2009 03:55

Typed ROBIN

保哥...
先說我沒有不敬的意思。
不過我覺得你把事情混淆了,

你舉的例子不恰當,
處理超大型檔案時,想你是用FileStream之類的在存取,
Stream都有buffer,一次只會讀一點,不會整個檔案讀進來,所以記憶體不會爆,
會爆的是你將檔案內文全load到一個變數內,但這已跟我們在討論的using使用不當耗盡資源無關,強強要load足2G的檔案到記憶體,有沒有用using都一樣不是嗎?

Typed ROBIN 台灣

十月 15. 2009 14:06

Will 保哥

Typed ROBIN:

我覺得你說的有道理,是我的例子舉的不好,我已經修正文章的文字描述部分。

謝謝您的指正。 ^_^

Will 保哥 台灣

十月 16. 2009 11:29

Ammon

我有兩個疑問
using (System.IO.StreamReader sr = new System.IO.StreamReader(@"C:\test.txt"))  
{  
  // ...
  return sr;
}  

在函數外可以取得的 return 回來的 StreamReader
並且正常使用,
這樣使用完之後還需要 Close (或 Dispose) 嗎?


若是依照範例解譯成
{
System.IO.StreamReader sr = new System.IO.StreamReader(@"C:\test.txt");  
try  {
  // ...
  return sr;
}  
finally
{  
  if (sr != null)  
    ((IDisposable)sr).Dispose();  
}  
}
在函數外應該無法使用才對???

Ammon 台灣

十月 16. 2009 13:21

Will 保哥

Ammon:

1. 在函數外可以取得的 return 回來的 StreamReader 並且正常使用,這樣使用完之後還需要 Close (或 Dispose) 嗎?
=> 要 return 的物件不能用 using,否則 return 的參考會被 Dispose() 掉。

2. 沒錯,應該無法使用。如下程式碼範例,是會掛的:

        protected void Page_Load(object sender, EventArgs e)
        {
            System.IO.StreamReader sr = GetFileStreamReader(@"C:\test.txt");
            while (!sr.EndOfStream)
            {
                Response.Write(sr.ReadLine() + "<br/>");
            }
        }
        private System.IO.StreamReader GetFileStreamReader(string filename)
        {
            using (System.IO.StreamReader sr = new System.IO.StreamReader(filename))
            {
                return sr;
            }
        }

Will 保哥 台灣

新增評論


( 您輸入的Email不會顯示於網站上 )

  Country flag

biuquote
  • 評論
  • 線上預覽
Loading