• [你必须知道的.NET]第十六回:深入浅出关键字using全接触


    博客园CLR基础研究团队|CLR团队精品系列|Anytao技术博客

    [你必须知道的.NET]

    第十六回:深入浅出关键字---using全接触

    发布日期:2007.10.1 作者:Anytao
    © 2007 Anytao.com ,原创作品,转贴请注明作者和出处。

    本文将介绍以下内容:

    • 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

    原创作品,转贴请注明作者和出处,留此信息。

    本文以“现状”提供且没有任何担保,同时也没有授予任何权利。
    This posting is provided "AS IS" with no warranties, and confers no rights.

  • 相关阅读:
    企业应用的互联网化
    企业应用开发平台GAP平台
    技术有什么用?
    Samba服务器配置
    IT从业人员需要知道的10个小秘密
    在雪豹10.6.2(Mac OS X)上安装Oracle10g
    项目经理的职责
    通过堡垒机远程连接Windows Server
    MySQL:Data truncated for column 'last_apply_time' at row 1
    当有莫名其妙的错误里, 可以
  • 原文地址:https://www.cnblogs.com/anytao/p/must_net_16.html
Copyright © 2020-2023  润新知