1. 程式人生 > >.net中using指令用法

.net中using指令用法

using指令的多種用法

  • using語句在Dispose模式中的應用

  1. 引言

  在.NET大家庭中,有不少的關鍵字承擔了多種角色,例如new關鍵字就身兼數職,除了能夠建立物件,在繼承體系中隱藏基類成員,還在泛型宣告中約束可能用作型別引數的引數,在[第五回:深入淺出關鍵字---把new說透]我們對此都有詳細的論述。本文,將把目光轉移到另外一個身兼數職的明星關鍵字,這就是using關鍵字,在詳細討論using的多重身份的基礎上來了解.NET在語言機制上的簡便與深邃。

  那麼,using的多重身份都體現在哪些方面呢,我們先一睹為快吧:

  • 引入名稱空間

  • 建立別名

  • 強制資源清理

  下面,本文將從這幾個角度來闡述using的多彩應用。

  2. 引入名稱空間

  using作為引入名稱空間指令的用法規則為:

  using Namespace;

  在.NET程式中,最常見的程式碼莫過於在程式檔案的開頭引入System名稱空間,其原因在於System名稱空間中封裝了很多最基本最常用的操作,下面的程式碼對我們來說最為熟悉不過:

  using System;

  這樣,我們在程式中就可以直接使用名稱空間中的型別,而不必指定詳細的型別名稱。using指令可以訪問巢狀名稱空間。

  關於:名稱空間

  名稱空間是.NET程式在邏輯上的組織結構,而並非實際的物理結構,是一種避免類名衝突的方法,用於將不同的資料型別組合劃分的方式。例如,在.NET中很多的基本型別都位於System名稱空間,資料操作型別位於System.Data名稱空間,

  誤區:
  • using類似於Java語言的import指令,都是引入名稱空間(Java中稱作包)這種邏輯結構;而不同於C語言中的#include指令,用於引入實際的類庫,

  • using引入名稱空間,並不等於編譯器編譯時載入該名稱空間所在的程式集,程式集的載入決定於程式中對該程式集是否存在呼叫操作,如果程式碼中不存在任何呼叫操作則編譯器將不會載入using引入名稱空間所在程式集。因此,在原始檔開頭,引入多個名稱空間,並非載入多個程式集,不會造成“過度引用”的弊端。

  3. 建立別名

  using為名稱空間建立別名的用法規則為:

  using alias = namespace | type;

  其中namespace表示建立名稱空間的別名;而type表示建立類型別名。例如,在.NET Office應用中,常常會引入Microsoft.Office.Interop.Word.dll程式集,在引入名稱空間時為了避免繁瑣的型別輸入,我們通常為其建立別名如下:

  using MSWord = Microsoft.Office.Interop.Word;

  這樣,就可以在程式中以MSWord來代替Microsoft.Office.Interop.Word字首,如果要建立Application物件,則可以是這樣,

  private static MSWord.Application ooo = new MSWord.Application();

  同樣,也可以建立型別的別名,用法為:

 
using MyConsole = System.Console;

class UsingEx

{
    public static void Main()

    {
        MyConsole.WriteLine("應用了類的別名。");
    }
}


  而建立別名的另一個重要的原因在於同一cs檔案中引入的不同名稱空間中包括了相同名稱的型別,為了避免出現名稱衝突可以通過設定別名來解決,例如:


namespace Anytao.Essential.Demos
{
    using BoyPlayer = Boyspace.Player;
    using GirlPlayer = GirlSpace.Player;

    class UsingEx
    {
        public static void Main()
        {
            BoyPlayer.Play();
            GirlPlayer.Play();
        }
    }
}

namespace Boyspace
{
    public class Player
    {
        public static void Play()
        {
            System.Console.WriteLine("Boys play football.");
        }
    }
}

namespace Girlspace
{
    public class Player
    {
        public static void Play()
        {
            System.Console.WriteLine("Girls play violin.");
        }
    }
}

以using建立別名,有效的解決了這種可能的命名衝突,儘管我們可以通過型別全名稱來加以區分,但是這顯然不是最佳的解決方案,using使得這一問題迎刃而解,不費絲毫功夫,同時在編碼規範上看來也更加的符合編碼要求。

  4. 強制資源清理

  4.1 由來

  要理解清楚使用using語句強制清理資源,就首先從瞭解Dispose模式說起,而要了解Dispose模式,則應首先了解.NET的垃圾回收機制。這些顯然不是本文所能完成的巨集論,我們只需要首先明確的是.NET提供了Dispose模式來實現顯式釋放和關閉物件的能力。

  Dispose模式

  Dispose模式是.NET提供的一種顯式清理物件資源的約定方式,用於在.NET 中釋放物件封裝的非託管資源。因為非託管資源不受GC控制,物件必須呼叫自己的Dispose()方法來釋放,這就是所謂的Dispose模式。從概念角度來看,Dispose模式就是一種強制資源清理所要遵守的約定;從實現角度來看,Dispose模式就是讓要一個型別實現IDisposable介面,從而使得該型別提供一個公有的Dispose方法。

  本文不再討論如何讓一個型別實現Dispose模式來提供顯示清理非託管資源的方式,而將注意集中在如何以using語句來簡便的應用這種實現了Dispose模式的型別的資源清理方式。我們在記憶體管理與垃圾回收章節將有詳細的討論。

  using語句提供了強制清理物件資源的便捷操作方式,允許指定何時釋放物件的資源,其典型應用為:


using (Font f = new Font("Verdana", 12, FontStyle.Regular))
{
    //執行文字繪製操作

    Graphics g = e.Graphics;

    Rectangle rect = new Rectangle(10, 10, 200, 200);

    g.DrawString("Try finally dispose font.", f, Brushes.Black, rect);

}//執行結束,釋放f物件資源

  上述典型應用中,using語句在結束時會自動呼叫欲被清除物件的Dispose()方法。因此,該Font物件必須實現IDispose介面,才能使用using語句強制物件清理資源。我們檢視其型別定義可知:

  public sealed class Font : MarshalByRefObject, ICloneable, ISerializable, IDisposable

  Font型別的確實現了IDisposeable介面,也就具有了顯示回收資源的能力。然而,我們並未從上述程式碼中,看出任何使用Dispose方法的蛛絲馬跡,這正式using語句帶來的簡便之處,其實質究竟怎樣呢?

  4.2 實質

  要想了解using語句的執行本質,瞭解編譯器在背後做了哪些手腳,就必須迴歸到IL程式碼中來揭密才行:


.method public hidebysig static void Main() cil managed

{

 .entrypoint

 // 程式碼大小       40 (0x28)

 .maxstack 4

 .locals init ([0] class [System.Drawing]System.Drawing.Font f,

           [1] bool CS$4$0000)

 IL_0000: nop

 IL_0001: ldstr      "Verdana"

 IL_0006: ldc.r4     12.

 IL_000b: ldc.i4.0

 IL_000c: newobj     instance void [System.Drawing]System.Drawing.Font::.ctor(string,float32,

     valuetype [System.Drawing]System.Drawing.FontStyle)

 IL_0011: stloc.0

 .try

 {

……部分省略……

 } // end .try

 finally

 {

……部分省略……

    IL_001f: callvirt   instance void [mscorlib]System.IDisposable::Dispose()

    IL_0024: nop

    IL_0025: endfinally

 } // end handler

 IL_0026: nop

 IL_0027: ret

} // end of method UsingDispose::Main

  顯然,編譯器在自動將using生成為try-finally語句,並在finally塊中呼叫物件的Dispose方法,來清理資源。

  在.NET規範中,微軟建議開放人員在呼叫一個型別的Dispose()或者Close()方法時,將其放在異常處理的finally塊中。根據上面的分析我們可知,using語句正是隱式的呼叫了型別的Dispose方法,因此以下的程式碼和上面的示例是完全等效的:

Code
Font f2 = new Font("Arial", 10, FontStyle.Bold);

try

{

//執行文字繪製操作

    Graphics g = new Graphics();

    Rectangle rect = new Rectangle(10, 10, 200, 200);

    g.DrawString("Try finally dispose font.", f2, Brushes.Black, rect);  

}

finally

{

    if (f2 != null)

        ((IDisposable)f2).Dispose();

}

  4.3 規則

  • using只能用於實現了IDisposable介面的型別,禁止為不支援IDisposable介面的型別使用using語句,否則會出現編譯時錯誤;
  • using語句適用於清理單個非託管資源的情況,而多個非託管物件的清理最好以try-finnaly來實現,因為巢狀的using語句可能存在隱藏的Bug。內層using塊引發異常時,將不能釋放外層using塊的物件資源。
  • using語句支援初始化多個變數,但前提是這些變數的型別必須相同,例如:

using(Pen p1 =new Pen(Brushes.Black), p2 =new Pen(Brushes.Blue))
{
}

  否則,編譯將不可通過。不過,還是有變通的辦法來解決這一問題,原因就是應用using語句的型別必然實現了IDisposable介面,那麼就可以以下面的方式來完成初始化操作,


using (IDisposable font = new Font("Verdana", 12, FontStyle.Regular), pen = new Pen(Brushes.Black))
{
    float size = (font as Font).Size;

    Brush brush = (pen as Pen).Brush;
}

  另一種辦法就是以使用try-finally來完成,不管初始化的物件型別是否一致。
  • Dispose方法用於清理物件封裝的非託管資源,而不是釋放物件的記憶體,物件的記憶體依然由垃圾回收器控制。

  • 程式在達到using語句末尾時退出using塊,而如果到達語句末尾之前引入異常則有可能提前退出。

  • using中初始化的物件,可以在using語句之前宣告,例如:

Font f3 =new Font("Verdana", 9, FontStyle.Regular);
using (f3)
{
//執行文字繪製操作}

  5. 結論

  一個簡單的關鍵字,多種不同的應用場合。本文從比較全面的角度,詮釋了using關鍵字在.NET中的多種用法,值得指出的是這種用法並非實現於.NET的所有高階語言,本文的情況主要侷限在C#中。