[C#.NET] 使用 using 或 try/finally 清理資源

  • 41396
  • 0
  • 2013-07-05

[C#.NET] 使用 using 或 try/finally 清理資源

在.NET世界裡若有用了托管資源,可以將類別繼承IDisposable來釋放資源或是實作解構子,若類別有繼承IDisposable,除了使用try/finally之外還可以使用using關鍵字;解構子的生命週期太長,可能要等到所有應用程式關閉才會觸發,所以本篇討論的是繼承IDisposable介面的方法。

在很多時候很多人習慣會這樣寫,者只是一個簡單的讀檔,然後回傳讀到的結果

public string ReaderLine(string FileName)
{
    if (!File.Exists(FileName))
        return null;
    Stream stream = null;
    StreamReader reader = null;
    string line="";
    string result="";
    try
    {
        stream = File.Open(FileName, FileMode.Open);
        reader = new StreamReader(stream);
        while ((line = reader.ReadLine()) != null)
        {
            result += line + Environment.NewLine;
        }
        reader.Close();
        stream.Close();
        return result;
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        return null;
    }
}

 

 

這樣看起來很好,有判斷檔案存在與否,也補捉例外了,但資源的釋放呢??假若try區段發生exception,reader.Close跟stream.Close永遠不會執行到了,資源也沒有釋放,等到下次其它應用程式或是自己的程式要用的時候,檔案一直跳出被鎖定,你可能會為了找出被誰鎖定而苦惱,甚至花了不少時間還不知道為什麼。

下列程式碼,我故意製造轉型失敗

 

public string ReaderLine(string FileName)
{
    if (!File.Exists(FileName))
        return null;
    Stream stream = null;
    StreamReader reader = null;
    string line="";
    string result="";
    try
    {
        stream = File.Open(FileName, FileMode.Open);
        reader = new StreamReader(stream);
        while ((line = reader.ReadLine()) != null)
        {
            result += line + Environment.NewLine;
        }
        string a = "999";
        byte b = byte.Parse(a);
        reader.Close();
        stream.Close();
        return result;
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        return null;
    }
}

 


 

當第一次呼叫這個方法時會出現轉型失敗的例外,第二次以後就會出現資源正在被其他程序使用當中

image

 

為了確保資源一定會被釋放我們可以改成以下,將釋放的程式碼寫在finally區段裡面。

public string ReaderLine(string FileName)
{
    if (!File.Exists(FileName))
        return null;
    Stream stream = null;
    StreamReader reader = null;
    string line="";
    string result="";
    try
    {
        stream = File.Open(FileName, FileMode.Open);
        reader = new StreamReader(stream);
        while ((line = reader.ReadLine()) != null)
        {
            result += line + Environment.NewLine;
        }
        string a = "999";
        byte b = byte.Parse(a);
        
        return result;
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        return null;
    }
    finally
    {
        reader.Close();
        stream.Close();
    }
}

 

 

或是用using包起來

public string ReaderLine(string FileName)
{
    if (!File.Exists(FileName))
        return null;
    string line = "";
    string result = "";

    try
    {
        using (Stream stream = File.Open(FileName, FileMode.Open))
        {
            using (StreamReader reader = new StreamReader(stream))
            {
                while ((line = reader.ReadLine()) != null)
                {
                    result += line + Environment.NewLine;
                }
                string a = "999";
                byte b = byte.Parse(a);

                return result;
            }
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        return null;
    }
}

 

就算例外不幸發生了,這樣也能確保Close方法一定會被執行到,不管程式碼被呼叫器了幾次,在有例外發生的情況下,所使用的資源被釋放了,也不會有檔案被鎖定的例外發生了

image

 

除了資源的釋放,我也常用在執行緒的釋放

Monitor.Enter(_thisLock);
try
{
    //TODO
}
finally
{
    Monitor.Exit(_thisLock);
}

 

 


後記:

不只資源的釋放,只要是一定要做的動作,我們都可以用把它寫在finally裡,用來保証一定會執行。using若是要跟finnally選一個,個人還是比較習慣用finally來釋放資源,若用了太多層的using除了不好閱讀之外,也不容易維護。

若有謬誤,煩請告知,新手發帖請多包涵


Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET

Image result for microsoft+mvp+logo