[你必须知道的.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?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方法,因此以下的代碼和上面的示例是完全等效的:
????????????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#中。
??
?
參考文獻
(USA)Jeffrey Richter, Applied Microsoft .NET Framework Programming
(USA)Bill Wagner,?Effective C#
?
溫故知新
[開篇有益]
[第一回:恩怨情仇:is和as]
[第二回:對抽象編程:接口和抽象類]
[第三回:歷史糾葛:特性和屬性]
[第四回:后來居上:class和struct]
[第五回:深入淺出關鍵字---把new說透]
[第六回:深入淺出關鍵字---base和this]
[第七回:品味類型---從通用類型系統開始]
[第八回:品味類型---值類型與引用類型(上)-內存有理]
[第九回:品味類型---值類型與引用類型(中)-規則無邊]
[第十回:品味類型---值類型與引用類型(下)-應用征途]
[第十一回:參數之惑---傳遞的藝術(上)]
[第十二回:參數之惑---傳遞的藝術(下)]
[第十三回:從Hello, world開始認識IL]
[第十四回:認識IL代碼---從開始到現在]
[第十五回:繼承本質論]
? 2007?Anytao.com
原創作品,轉貼請注明作者和出處,留此信息。
?
評論列表 ?? ?? #5樓?2007-10-02 12:05?Lingxi 請問老大public int GetMaxId()
{
int maxId = 0;
using (conn) //conn--數據庫連接
{
string strSQL = "select max(xxId) from table";
try
{
conn.open();
OracleCommand cmd = new OracleCommand(strSQL,conn);
OracleDataReader odr = cmd.ExecuteReader();
if (odr.Read())
{
maxId = int.Parse(odr.GetOracleNumber(0).Value.ToString());
}
}
catch { }
}
return maxId;
}
/
OracleDataReader odr = cmd.ExecuteReader() 是否必須再用using包括起來?請您賜教。 支持(0)?反對(0) ?? #6樓?2007-10-02 12:18?idior using可能隱含的問題。
http://idior.cnblogs.com/articles/389949.html 支持(0)?反對(0) ?? #7樓?[樓主]?2007-10-02 16:22?Anytao @Lingxi?
賜教不敢,只是一點個人的想法。?
關于使用using進行資源清理,應該基于以下幾點:?
1 是否實現了IDispose接口,這點上OracleDataReader對象是沒有問題的;?
2 最好不要嵌套using塊,否則可能引起外層資源不能釋放的Bug,因此這里在conn內部嵌套using塊會導致可能的異常;?
3 對象本身是否需要進行顯式的資源釋放,常見的情況通常集中在例如:數據庫連接、文件、互斥體、嵌套字、位圖這樣的非托管資源上。?
在你的示例中using(conn)能夠保證關閉數據庫連接,datareader和Connection是緊密關聯的,因此沒有必要再次提供顯式的關閉操作。建議以OracleDataReader odr = cmd.ExecuteReader(CommandBehavior.CloseConnection)方式來控制關閉。?
推薦一篇參考文章:?
http://www.cnblogs.com/kentyshang/archive/2006/09/26/514901.html? 支持(0)?反對(0) ?? ?? #34樓?2008-04-18 21:17?文祥 很奇怪很多示例代碼都用了這樣的寫法:?
using(StreamWriter sw=File.CreateText(path)){?
...?
}?
以為用了using就萬事大吉了,用了using確實會自動釋放StreamWriter對象,但是在創建StreamWriter對象的時候一樣會發生異常,應該把整個using語句塊用try...catch括起來。?
其實沒有using之前的經典寫法應該是:?
StreamWriter sw;?
try {?
sw=File.CreateText(path);?
}?
catch(Exception e) {?
Console.WriteLine(e.Message);?
return;?
}?
try {?
sw.Write(...);...?
}?
catch(Exception e) {?
Console.WriteLine(e.Message);?
}?
finally {?
sw.Dispose();?
}?
對于你說的“內層using塊引發異常時,將不能釋放外層using塊的對象資源。”這句話,我深表懷疑,于是寫了一段代碼測試:?
public class Program {?
static void Main(string[] args) {?
try {?
using (MyDisposeClass mdc1 = new MyDisposeClass(1)) {?
using (MyDisposeClass mdc2 = new MyDisposeClass(2)) {?
throw new Exception();?
}?
}?
}?
catch { }?
}?
}?
class MyDisposeClass : IDisposable {?
private int n;?
public MyDisposeClass(int m) {?
this.n = m;?
}?
public void Dispose() {?
Console.WriteLine(n);?
}?
}?
輸出為:2 1。不管有沒有try...catch塊,位置放在哪里,都會調用兩個MyDisposeClass對象的Dispose方法,這證明了在using中的對象確實一定會調用Dispose方法,也即是在finally塊中的語句一定會執行。?
很奇怪很多示例代碼都用了這樣的寫法:?
using(StreamWriter sw=File.CreateText(path)){?
...?
}?
以為用了using就萬事大吉了,用了using確實會自動釋放StreamWriter對象,但是在創建StreamWriter對象的時候一樣會發生異常,應該把整個using語句塊用try...catch括起來。?
其實沒有using之前的經典寫法應該是:?
StreamWriter sw;?
try {?
sw=File.CreateText(path);?
}?
catch(Exception e) {?
Console.WriteLine(e.Message);?
return;?
}?
try {?
sw.Write(...);...?
}?
catch(Exception e) {?
Console.WriteLine(e.Message);?
}?
finally {?
sw.Dispose();?
}?
對于你說的“內層using塊引發異常時,將不能釋放外層using塊的對象資源。”這句話,我深表懷疑,于是寫了一段代碼測試:?
public class Program {?
static void Main(string[] args) {?
try {?
using (MyDisposeClass mdc1 = new MyDisposeClass(1)) {?
using (MyDisposeClass mdc2 = new MyDisposeClass(2)) {?
throw new Exception();?
}?
}?
}?
catch { }?
}?
}?
class MyDisposeClass : IDisposable {?
private int n;?
public MyDisposeClass(int m) {?
this.n = m;?
}?
public void Dispose() {?
Console.WriteLine(n);?
}?
}?
輸出為:2 1。不管有沒有try...catch塊,位置放在哪里,都會調用兩個MyDisposeClass對象的Dispose方法,這證明了在using中的對象確實一定會調用Dispose方法,也即是在finally塊中的語句一定會執行?
在using語句聲明多個變量確實挺有意思,而且還可以用一個共同的接口作類型,這我絕對想不到,這種寫法解決了我的一個問題,我原來這么寫:?
using(FileStream fs = new FileStream("", FileMode.Create, FileAccess.Write, FileShare.Write)) {?
using(StreamWriter sw=new StreamWriter(fs)) {?
...?
}?
}?
現在我可以這么寫了:?
using(IDisposable fs = new FileStream("", FileMode.Create, FileAccess.Write, FileShare.Write), sw=new StrreamWriter((FileStream)fs)) {...}?
哈哈。 支持(0)?反對(0) ?? #35樓?[樓主]?2008-04-18 23:43?Anytao @文祥?
謝謝文祥的分析,我所說的“內層using塊引發異常時,將不能釋放外層using塊的對象資源”,其實表達的是一種模式的推薦,例如有下面情況存在時,應用using模式就有可能帶來問題:?
public class FileEx: IDisposable?
{?
public void Dispose()?
{?
Console.WriteLine("FileEx資源清理。");?
Console.Read();?
}?
}?
public class DBEx : IDisposable?
{?
public DBEx()?
{?
//To throw exception in constructor.?
throw new Exception();?
}?
public void Dispose()?
{?
Console.WriteLine("DBEx資源清理。");?
Console.Read();?
}?
}?
class MoreUsing?
{?
public static void Main()?
{?
FileEx fe = new FileEx();?
DBEx de = new DBEx();?
using (fe)?
{?
using (de)?
{?
//Do some things here.?
}?
}?
}?
}? 支持(0)?反對(0) ?? #36樓?2008-10-31 20:58?syz 請問?
如何辨別托管資源與非托管資源,using里一般都是非托管資源嗎,具體包括哪些呢??
如果同等的條件下不使用using,也不顯示的釋放資源,那么.NET下?
對這些非托管資源是如何釋放的? 支持(0)?反對(0) ?? #37樓?[樓主]?2008-12-20 17:45?Anytao @syz?
.NET平臺下的開發,都是托管環境,你使用的任何類型都是托管代碼。而非.NET平臺下的資源則是非托管資源,其釋放過程因不同的情況而異。 支持(0)?反對(0) ?? #38樓?2009-02-20 16:35?feisky using類似于Java語言的import指令,都是引入命名空間(Java中稱作包)這種邏輯結構;而不同于C語言中的#include指令,用于引入實際的類庫,?
-----------------------------------------------------------------?
using引入的雖然是命名空間,可也可以看作是引入了類庫吧??
這和C中的include有什么區別呢? 支持(0)?反對(0) ?? #39樓?2010-08-04 17:00?風遙 @?Anytao
35樓你的舉例中DBEx de = new DBEx();?
這不還沒到using就已經拋出異常了嗎,我也不太明白
內層using塊引發異常時,將不能釋放外層using塊的對象資源 支持(0)?反對(0) ?? #40樓?2010-12-08 17:05?Efeng class MoreUsing?
{?
public static void Main()?
{?
FileEx fe = new FileEx();?
DBEx de = new DBEx();?
using (fe)?
{?
using (de)?
{?
//Do some things here.?
}?
}?
35樓的應該為下面這樣的吧
class MoreUsing?
{?
public static void Main()?
{?
using (FileEx fe = new FileEx())?
{?
using (DBEx de = new DBEx())?
{?
//Do some things here.?
}?
}?
總結
以上是生活随笔為你收集整理的[你必须知道的.NET]第十六回:深入浅出关键字---using全接触的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 日本公司开发新电容器!电动汽车最快1分钟
- 下一篇: 安卓性能天花板 黑鲨新机首曝:把SSD和