[C#.NET] using區段是否等於try/finally

  • 22981
  • 0
  • 2013-07-05

[C#.NET] using區段是否等於try/finally

續上篇[.NET] 使用 using 或 try/finally 清理資源,我們都知道using可以釋放實作IDisposable介面的類別,主要是釋放非托管的物件,例如:在操作檔案的時候就必須要釋放才不會造成檔案鎖定,為了確保資源能正確的被釋放,我們可以用using或是try/finally在finally區段裡釋放資源以及該做的事,思考一下using內部幫我們做了什麼呢?

使用

try/finally的寫法如下

public string ReaderLine(string FileName)
{
    if (!File.Exists(FileName))
        return null;
    Stream stream = null;
    StreamReader reader = null;
    string line = "";
    StringBuilder sb = new StringBuilder();
    try
    {
        stream = File.Open(FileName, FileMode.Open);
        reader = new StreamReader(stream);
        while ((line = reader.ReadLine()) != null)
        {
            sb.AppendLine(line);
        }

        return sb.ToString();
    }
    finally
    {
        if (stream != null)
            stream.Close();
        if (reader != null)
            reader.Close();
    }
}

 

使用using的寫法如下

public string ReaderLine(string FileName)
{
    if (!File.Exists(FileName))
        return null;

    string line = "";
    StringBuilder sb = new StringBuilder();
    using (Stream stream = File.Open(FileName, FileMode.Open))
    {
        using (StreamReader reader = new StreamReader(stream))
        {
            while ((line = reader.ReadLine()) != null)
            {
                sb.AppendLine(line);
            }
            return sb.ToString();
        }
    }
}

 


我用using或try/finally的寫法例外發生時會被吃掉嗎?

我相信很多人對上述的寫法會有疑問,我們來做個小實驗,我在try區塊以及using區塊裡面故意寫轉型失敗的程式碼,我們可以發現VS會拋出例外訊息。

image_thumb_thumb

image_thumb1_thumb

所以不管是using還是try/finally都不會吃掉例外訊息,並且能確保資源被釋放,釋放演練請看[.NET] 使用 using 或 try/finally 清理資源

 

 


using和try/finally有什麼不同? using幫我們做了什麼?

基本上它們兩個的功能是一樣的,主要都是用來釋放資源用,先來用Reflector看看using所產生的IL,我們可以觀察到using區段裡面是用try/finlly寫的

image_thumb3_thumb

.method public hidebysig instance string ReaderLine(string FileName) cil managed
{
    .maxstack 2
    .locals init (
        [0] string line,
        [1] class [mscorlib]System.Text.StringBuilder sb,
        [2] class [mscorlib]System.IO.Stream stream,
        [3] class [mscorlib]System.IO.StreamReader reader,
        [4] string a,
        [5] uint8 b,
        [6] string CS$1$0000,
        [7] bool CS$4$0001)
    L_0000: nop 
    L_0001: ldarg.1 
    L_0002: call bool [mscorlib]System.IO.File::Exists(string)
    L_0007: stloc.s CS$4$0001
    L_0009: ldloc.s CS$4$0001
    L_000b: brtrue.s L_0012
    L_000d: ldnull 
    L_000e: stloc.s CS$1$0000
    L_0010: br.s L_008d
    L_0012: ldstr ""
    L_0017: stloc.0 
    L_0018: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor()
    L_001d: stloc.1 
    L_001e: ldarg.1 
    L_001f: ldc.i4.3 
    L_0020: call class [mscorlib]System.IO.FileStream [mscorlib]System.IO.File::Open(string, valuetype [mscorlib]System.IO.FileMode)
    L_0025: stloc.2 
    L_0026: nop 
    L_0027: ldloc.2 
    L_0028: newobj instance void [mscorlib]System.IO.StreamReader::.ctor(class [mscorlib]System.IO.Stream)
    L_002d: stloc.3 
    L_002e: nop 
    L_002f: br.s L_003b
    L_0031: nop 
    L_0032: ldloc.1 
    L_0033: ldloc.0 
    L_0034: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::AppendLine(string)
    L_0039: pop 
    L_003a: nop 
    L_003b: ldloc.3 
    L_003c: callvirt instance string [mscorlib]System.IO.TextReader::ReadLine()
    L_0041: dup 
    L_0042: stloc.0 
    L_0043: ldnull 
    L_0044: ceq 
    L_0046: ldc.i4.0 
    L_0047: ceq 
    L_0049: stloc.s CS$4$0001
    L_004b: ldloc.s CS$4$0001
    L_004d: brtrue.s L_0031
    L_004f: ldstr "999"
    L_0054: stloc.s a
    L_0056: ldloc.s a
    L_0058: call uint8 [mscorlib]System.Byte::Parse(string)
    L_005d: stloc.s b
    L_005f: ldloc.1 
    L_0060: callvirt instance string [mscorlib]System.Object::ToString()
    L_0065: stloc.s CS$1$0000
    L_0067: leave.s L_008d
    L_0069: ldloc.3 
    L_006a: ldnull 
    L_006b: ceq 
    L_006d: stloc.s CS$4$0001
    L_006f: ldloc.s CS$4$0001
    L_0071: brtrue.s L_007a
    L_0073: ldloc.3 
    L_0074: callvirt instance void [mscorlib]System.IDisposable::Dispose()
    L_0079: nop 
    L_007a: endfinally 
    L_007b: ldloc.2 
    L_007c: ldnull 
    L_007d: ceq 
    L_007f: stloc.s CS$4$0001
    L_0081: ldloc.s CS$4$0001
    L_0083: brtrue.s L_008c
    L_0085: ldloc.2 
    L_0086: callvirt instance void [mscorlib]System.IDisposable::Dispose()
    L_008b: nop 
    L_008c: endfinally 
    L_008d: nop 
    L_008e: ldloc.s CS$1$0000
    L_0090: ret 
    .try L_002e to L_0069 finally handler L_0069 to L_007b
    .try L_0026 to L_007b finally handler L_007b to L_008d
}


 

 

再看看Reflector不優化IL的結果,選C# → None,Reflector會幫我們直接翻譯IL成C#,當然你還可以翻成其它.NET語言

image_thumb4_thumb

public string ReaderLine(string FileName)
{
    string line;
    StringBuilder sb;
    Stream stream;
    StreamReader reader;
    string a;
    byte b;
    string CS$1$0000;
    bool CS$4$0001;
    if (File.Exists(FileName) != null)
    {
        goto Label_0012;
    }
    CS$1$0000 = null;
    goto Label_008D;
Label_0012:
    line = "";
    sb = new StringBuilder();
    stream = File.Open(FileName, 3);
Label_0026:
    try
    {
        reader = new StreamReader(stream);
    Label_002E:
        try
        {
            goto Label_003B;
        Label_0031:
            sb.AppendLine(line);
        Label_003B:
            if ((((line = reader.ReadLine()) == null) == 0) != null)
            {
                goto Label_0031;
            }
            a = "999";
            b = byte.Parse(a);
            CS$1$0000 = sb.ToString();
            goto Label_008D;
        }
        finally
        {
        Label_0069:
            if ((reader == null) != null)
            {
                goto Label_007A;
            }
            reader.Dispose();
        Label_007A:;
        }
    }
    finally
    {
    Label_007B:
        if ((stream == null) != null)
        {
            goto Label_008C;
        }
        stream.Dispose();
    Label_008C:;
    }
Label_008D:
    return CS$1$0000;
}

用Reflector幫我們翻譯IL的結果,using區段變成了try/finally

 


寫try一定要寫catch嗎?可以不寫catch嗎?

台灣很多的書籍都會教try/catch或是try/catch/finally的寫法,回顧一下它的用法,把程式邏輯擺在try區段,把例外處理擺在catch區段,一定要做的事則擺放在finally區段,以下try/catch/finally的用法,我相信很多人都看過以下的寫法也會這樣寫,但是眼尖的朋友一定會發現catch區段的寫法有很大的問題,以前發過的文章在這裡就不在多敘述,[.NET] 使用 try/catch 攔截例外應該注意事項,本篇重點是using內部的實作方式。

 

public string ReaderLine(string FileName)
{
    if (!File.Exists(FileName))
        return null;
    Stream stream = null;
    StreamReader reader = null;
    string line = "";
    StringBuilder sb = new StringBuilder();
    try
    {
        stream = File.Open(FileName, FileMode.Open);
        reader = new StreamReader(stream);
        while ((line = reader.ReadLine()) != null)
        {
            sb.AppendLine(line);
        }
        string a = "999";
        byte b = byte.Parse(a);
        return sb.ToString();
    }
    catch (Exception ex)
    {
        throw ex;
    }
    finally
    {
        if (stream != null)
            stream.Close();
        if (reader != null)
            reader.Close();
    }
}

 

在底層,catch沒寫好,直接拋Exception類別,會造成Stack的困擾,在UI層反而得到不完整的錯誤訊息,若你在開發底層原件,還在直接拋Exception類別,請把這壞習慣改掉,直接改拋throw,除非你能確保拋出正確的Exception類別;若你在開發UI層,可能會有記錄例外狀況的時候,可以用StackTrace記錄錯誤內容,請參考[.NET] 追蹤類別–StackTrace

try
{
    //TODO:要處理的事,程式邏輯
}
catch
{
    throw;
}
finally
{
    //DOTO:一定要做的事
}

或是連catch都不寫

 

try
{
    //TODO:要處理的事,程式邏輯
}
finally
{
    //DOTO:一定要做的事
}

 

 

try/finally這樣的寫法在CLR裡也是有跡可循,只針對已知的例外使用try/catch。
image


using不是比較好用嗎?為什麼要用try/finally?

如果瞭解using好處的人,會推崇使用using,因為它幫我們處理了變數的生命週期,並且呼叫Dispose(),這是好事,因為人都是會粗心大意的,但using的用意是要確保釋放資源;但並非所有的物件都有using可以用,若沒有using的物件要怎麼辦?比如執行緒鎖定,在這裡用Monitor類當範例,當呼叫Monitor.Enter鎖定時,就要在finally區段呼叫Monitor.Exit,已確保物件解鎖。

 

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



當類別沒有實作IDisposable介面,表示沒有辦法用using,我們可以在自己用try/finally或try/catch/finally處理,在finally區段裡一定要確定資源會被釋放或是該做的事有做。

當程式碼有一定要做的事,千萬不忘記做

 


後記:

由上面的演示我們可以知道

1.using跟try/finally,都不會吃掉例外訊息,因為沒有寫catch區段。

2.using是try/finally的簡易寫法,using裡面已經包含呼叫Dispose方法。

3.若要自己寫try/finally區段得自己呼叫Dispose方法。

 

PS.純個人看法,兩者的寫法結果都一樣,瞭解兩者之間的優點之後,就看自己喜歡什麼樣子的寫法,using的寫法看起來比較短,也不用擔心忘了寫釋放,若只有一層我會使用,但如果有多層using(兩層以上),我就會覺得大括號太多閱讀上有很大的不便,using之間可能有很多行程式碼,我就不會採用這樣的寫法,當然,若你的VS有裝外掛,可以解決閱讀上的困擾,下圖是VS採用telerik的結果。

image

 

我比較喜歡用try/finally同時處理多個實作IDisposable的物件

image


相關的文章參考:

  • using適用於只有少數物件需要釋放的情況,try/finally語法則適用於多個物件需要釋放的情況。這兩種語法是等價的,在編譯時編譯器會將using改為try/finally的寫法,故這兩種寫法皆可確保資源能有效的被釋放。[C#]Effective C# 條款十五:利用using和try/finally語句來清理資源.
  • finally 區塊對於清除 try 區塊中配置的任何資源,以及執行即使有例外狀況也必須執行的任何程式碼十分有用。不論 try 區塊如何結束,程式控制權最後都轉移到 finally 區塊。http://msdn.microsoft.com/zh-tw/library/zwc8s4fz.aspx
  • 由於會隱含執行Dispose,所以不需要再手動處理,如果自己在手動執行Close()、Dispose(),只會讓CLR再度浪費資源進而影響程式效能。[C#][Tips]再探using陳述式

 

 

 

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


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

Image result for microsoft+mvp+logo