新书推介:《语义网技术体系》
作者:瞿裕忠,胡伟,程龚
   XML论坛     W3CHINA.ORG讨论区     计算机科学论坛     SOAChina论坛     Blog     开放翻译计划     新浪微博  
 
  • 首页
  • 登录
  • 注册
  • 软件下载
  • 资料下载
  • 核心成员
  • 帮助
  •   Add to Google

    >> 本版讨论.NET,C#,ASP,VB技术
    [返回] 计算机科学论坛计算机技术与应用『 Dot NET,C#,ASP,VB 』 → <展现 C#> (rainbow 翻译)- 第五章 类 查看新帖用户列表

      发表一个新主题  发表一个新投票  回复主题  (订阅本版) 您是本帖的第 11019 个阅读者浏览上一篇主题  刷新本主题   树形显示贴子 浏览下一篇主题
     * 贴子主题: <展现 C#> (rainbow 翻译)- 第五章 类 举报  打印  推荐  IE收藏夹 
       本主题类别:     
     admin 帅哥哟,离线,有人找我吗?
      
      
      
      威望:9
      头衔:W3China站长
      等级:计算机硕士学位(管理员)
      文章:5255
      积分:18406
      门派:W3CHINA.ORG
      注册:2003/10/5

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给admin发送一个短消息 把admin加入好友 查看admin的个人资料 搜索admin在『 Dot NET,C#,ASP,VB 』的所有贴子 点击这里发送电邮给admin  访问admin的主页 引用回复这个贴子 回复这个贴子 查看admin的博客楼主
    发贴心情 <展现 C#> (rainbow 翻译)- 第五章 类


    发信人: wuxq (很想好好休息), 信区: DotNET        
    标  题: <展现 C#> (rainbow 翻译)- 第五章 类
    发信站: BBS 水木清华站 (Mon Apr 30 13:48:25 2001)

    第五章  类
        前一章讨论了数据类型和它们的用法。现在我们转移到C#中至关重要的结构——类
    。没有了类,就连简单的C#程序都不能编译。这一章假定你知道了一个类的基本组成部
    分:方法、属性、构造函数和析构函数。 C#在其中增加了索引和事件。
        在这一章中,你学到下列有关类的话题。
          。 使用构造函数和析构函数
          。给类写方法
          。给一个类增加属性存取标志
          。实现索引
          。创建事件并通过代表元为事件关联客户
          。应用类、成员和存取修饰符。
    5.1  构造函数和析构函数
        在你可以访问一个类的方法、属性或任何其它东西之前, 第一条执行的语句是包含
    有相应类的构造函数。甚至你自己不写一个构造函数,也会有一个缺省的构造函数提供
    给你。
    class TestClass
    {
    public TestClass(): base() {} // 由编译器提供
    }
         一个构造函数总是和它的类名相同,但是,它没有声明返回类型。总之,构造函数
    总是public的,你可以用它们来初始化变量。
    public TestClass()
    {
    // 在这给变量
    // 初始化代码等等。
    }
        如果类仅包含静态成员(能以类型调用,而不是以实例调用的成员),你可以创建一
    个private的构造函数。
    private TestClass() {}
        尽管存取修饰符在这一章的后面将要大篇幅地讨论,但是private意味着从类的外面
    不可能访问该构造函数。所以,它不能被调用,且没有对象可以自该类定义被实例化。

           并不仅限于无参数构造函数——你可以传递初始参数来初始化成员。
           public TestClass(string strName, int nAge) { ... }
        作为一个C/C++程序员,你可能习惯于给初始化写一个附加的方法,因为在构造函数
    中没有返回值。当然,尽管在C#中也没有返回值,但你可以引发一个自制的异常,以从
    构造函数获得返回值。更多有关异常处理的知识在第七章 "异常处理"中有讨论。
        但是,当你保留引用给宝贵的资源,应该想到写一个方法来解决:一个可以被显式
    地调用来释放这些资源。问题是当你可以在析构函数(以类名的前面加"~"的方式命名)中
    做同样的事情时,为何还要写一个附加的方法.
    public ~TestClass()
    {
    // 清除
    }
        你应该写一个附加方法的原因是垃圾收集器,它在变量超出范围后并不会立即被调
    用,而仅当间歇期间或内存条件满足时才被触发。当你锁住资源的时间长于你所计划的
    时间时,它就会发生。因此,提供一个显式的释放方式是一个好主意,它同样能从析构
    函数中调用。
    public void Release()
    {
    // 释放所有宝贵的资源
    }
    public ~TestClass()
    {
    Release();
    }
        调用析构函数中的释放方法并不是必要的——总之,垃圾收集会留意释放对象。但
    没有忘记清除是一种良好的习惯。
    5.2  方法
         既然对象能正确地初始化和结束,所剩下来的就是往类中增加功能。在大多数情况
    下,功能的主要部分在方法中能得到实现。你早已见过静态方法的使用,但是,这些是
    类型(类)的部分,不是实例(对象)。
        为了让你迅速入门,我把这些方法的烦琐问题安排为三节:
          。方法参数
          。改写方法
          。方法屏蔽
    5.2.1  方法参数
        因方法要处理更改数值,你多多少少要传递值给方法,并从方法获得返回值。以下
    三个部分涉及到由传递值和为调用者获取返回结果所引起的问题。
          。输入参数
          。引用参数
          。输出参数
    5.2.1.1  输入参数
        你早已在例子中见过的一个参数就是输入参数。你用一个输入参数通过值传递一个
    变量给一个方法——方法的变量被调用者传递进来的值的一个拷贝初始化。清单5.1 示
    范输入参数的使用。
    清单  5.1 通过值传递参数
    1: using System;
    2:
    3: public class SquareSample
    4: {
    5:  public int CalcSquare(int nSideLength)
    6:  {
    7:   return nSideLength*nSideLength;
    8:  }
    9: }
    10:
    11: class SquareApp
    12: {
    13:  public static void Main()
    14:  {
    15:   SquareSample sq = new SquareSample();
    16:   Console.WriteLine(sq.CalcSquare(25).ToString());
    17:  }
    18: }
         因为我传递值而不是引用给一个变量,所以当调用方法时(见第16行),可以使用一
    个常量表达式(25)。整型结果被传回给调用者作为返回值,它没有存到中间变量就被立
    即显示到屏幕上 。
         输入参数按C/C++程序员早已习惯的工作方式工作。如果你来自VB,请注意没有能
    被编译器处理的隐式ByVal或ByRef——如果没有设定,参数总是用值传递。
         这点似乎与我前面所陈述的有冲突:对于一些变量类型,用值传递实际上意味着用
    引用传递。迷惑吗? 一点背景知识也不需要:COM中的东西就是接口,每一个类可以拥有
    一个或多个接口。一个接口只不过是一组函数指针,它不包含数据。重复该数组会浪费
    很多内存资源;所以,仅开始地址被拷贝给方法,它作为调用者,仍然指向接口的相同
    指针。那就是为什么对象用值传递一个引用。
    5.2.1.2  引用参数
        尽管可以利用输入参数和返回值建立很多方法,但你一想到要传递值并原地修改它
    (也就是在相同的内存位置),就没有那么好运了。这里用引用参数就很方便。
    void myMethod(ref int nInOut)
        因为你传递了一个变量给该方法(不仅仅是它的值),变量必须被初始化。否则,编
    译器会报警。清单 5.2 显示如何用一个引用参数建立一个方法。
    清单 5.2  通过引用传递参数
    1: // class SquareSample
    2: using System;
    3:
    4: public class SquareSample
    5: {
    6:  public void CalcSquare(ref int nOne4All)
    7:  {
    8:   nOne4All *= nOne4All;
    9:  }
    10: }
    11:
    12: class SquareApp
    13: {
    14:  public static void Main()
    15:  {
    16:   SquareSample sq = new SquareSample();
    17:
    18:   int nSquaredRef = 20; // 一定要初始化
    19:   sq.CalcSquare(ref nSquaredRef);
    20:   Console.WriteLine(nSquaredRef.ToString());
    21:  }
    22: }
        正如所看到的,所有你要做的就是给定义和调用都加上ref限定符。因为变量通过引
    用传递,你可以用它来计算出结果并传回该结果。但是,在现实的应用程序中,我强烈
    建议要用两个变量,一个输入参数和一个引用参数。
    5.2.1.3  输出参数
        传递参数的第三种选择就是把它设作一个输出参数。正如该名字所暗示,一个输出
    参数仅用于从方法传递回一个结果。它和引用参数的另一个区别在于:调用者不必先初
    始化变量才调用方法。这显示在清单5.3中。
    清单  5.3  定义一个输出参数
    1: using System;
    2:
    3: public class SquareSample
    4: {
    5:  public void CalcSquare(int nSideLength, out int nSquared)
    6:  {
    7:   nSquared = nSideLength * nSideLength;
    8:  }
    9: }
    10:
    11: class SquareApp
    12: {
    13:  public static void Main()
    14:  {
    15:   SquareSample sq = new SquareSample();
    16:
    17:   int nSquared; // 不必初始化
    18:   sq.CalcSquare(15, out nSquared);
    19:   Console.WriteLine(nSquared.ToString());
    20:  }
    21: }
    5.2.2  改写方法
        面向对象设计的重要原则就是多态性。不要理会高深的理论,多态性意味着:当基
    类程序员已设计好用于改写的方法时,在派生类中,你就可以重定义(改写)基类的方法
    。基类程序员可以用 virtual 关键字设计方法:
    virtual void CanBOverridden()
        当从基类派生时,所有你要做的就是在新方法中加入override关键字:
    override void CanBOverridden()
        当改写一个基类的方法时,你必须明白,不能改变方法的访问属性——在这章的后
    面,你会学到更多关于访问修饰符的知识。
        除了改写基类方法的事实外,还有另一个甚至更重要的改写特性。当把派生类强制
    转换成基类类型并接着调用虚拟方法时,被调用的是派生类的方法而不是基类的方法。

    ((BaseClass)DerivedClassInstance).CanBOverridden();
        为了演示虚拟方法的概念,清单 5.4 显示如何创建一个三角形基类,它拥有一个可
    以被改写的成员方法(ComputeArea)。
    清单 5.4   改写一个基类的方法
    1: using System;
    2:
    3: class Triangle
    4: {
    5:  public virtual double ComputeArea(int a, int b, int c)
    6:  {
    7:   // Heronian formula
    8:   double s = (a + b + c) / 2.0;
    9:   double dArea = Math.Sqrt(s*(s-a)*(s-b)*(s-c));
    10:   return dArea;
    11:  }
    12: }
    13:
    14: class RightAngledTriangle:Triangle
    15: {
    16:  public override double ComputeArea(int a, int b, int c)
    17:  {
    18:   double dArea = a*b/2.0;
    19:   return dArea;
    20:  }
    21: }
    22:
    23: class TriangleTestApp
    24: {
    25:  public static void Main()
    26:  {
    27:   Triangle tri = new Triangle();
    28:   Console.WriteLine(tri.ComputeArea(2, 5, 6));
    29:
    30:   RightAngledTriangle rat = new RightAngledTriangle();
    31:   Console.WriteLine(rat.ComputeArea(3, 4, 5));
    32:  }
    33: }
        基类Triangle定义了方法ComputeArea。它采用三个参数,返回一个double结果,且
    具有公共访问性。从Triangle类派生出的是RightAngledTriangle,它改写了ComputeAr
    ea 方法,并实现了自己的面积计算公式。两个类都被实例化,且在命名为TriangleTes
    tApp的应用类的Main() 方法中得到验证。
    我漏了解释第14行:
    class RightAngledTriangle : Triangle
         在类语句中冒号(:)表示RightAngledTriangle从类 Triangle派生。那就是你所
    必须要做的,以让C#知道你想把 Triangle当作RightAngledTriangle的基类。
         当仔细观察直角三角形的ComputeArea方法时,你会发现第3个参数并没有用于计算
    。但是,利用该参数就可以验证是否是“直角”。如清单5.5所示。
    清单 5.5   调用基类实现
    1: class RightAngledTriangle:Triangle
    2: {
    3:  public override double ComputeArea(int a, int b, int c)
    4:  {
    5:   const double dEpsilon = 0.0001;
    6:   double dArea = 0;
    7:   if (Math.Abs((a*a + b*b - c*c)) > dEpsilon)
    8:   {
    9:    dArea = base.ComputeArea(a,b,c);
    10:   }
    11:   else
    12:   {
    13:    dArea = a*b/2.0;
    14:   }
    15:
    16:   return dArea;
    17:  }
    18: }
        该检测简单地利用了毕达哥拉斯公式,对于直角三角形,检测结果必须为0。如果结
    果不为0,类就调用它基类的 ComputeArea来实现。
    dArea = base.ComputeArea(a,b,c);
        例子的要点为:通过显式地利用基类的资格检查,你就能轻而易举地调用基类实现
    改写方法。
    当你需要实现其在基类中的功能,而不愿意在改写方法中重复它时,这就非常有帮助。

    5.2.3 方法屏蔽
         重定义方法的一个不同手段就是要屏蔽基类的方法。当从别人提供的类派生类时,
    这个功能特别有价值。看清单 5.6,假设BaseClass由其他人所写,而你从它派生出 De
    rivedClass 。
    清单 5.6   Derived Class 实现一个没有包含于 Base Class中的方法
    1: using System;
    2:
    3: class BaseClass
    4: {
    5: }
    6:
    7: class DerivedClass:BaseClass
    8: {
    9:  public void TestMethod()
    10:  {
    11:   Console.WriteLine("DerivedClass::TestMethod");
    12:  }
    13: }
    14:
    15: class TestApp
    16: {
    17:  public static void Main()
    18:  {
    19:   DerivedClass test = new DerivedClass();
    20:   test.TestMethod();
    21:  }
    22: }
        在这个例子中, DerivedClass 通过TestMethod()实现了一个额外的功能。但是,
    如果基类的开发者认为把TestMethod()放在基类中是个好主意,并使用相同的名字实现
    它时,会出现什么问题呢?(见清单5.7)
    清单 5.7    Base Class 实现和 Derived Class相同的方法
    1: class BaseClass
    2: {
    3:  public void TestMethod()
    4:  {
    5:   Console.WriteLine("BaseClass::TestMethod");
    6:  }
    7: }
    8:
    9: class DerivedClass:BaseClass
    10: {
    11:  public void TestMethod()
    12:  {
    13:   Console.WriteLine("DerivedClass::TestMethod");
    14:  }
    15: }
    在优秀的编程语言中,你现在会遇到一个真正的大麻烦。但是,C#会给你提出警告:
    hiding2.cs(13,14): warning CS0114: 'DerivedClass.TestMethod()' hides inherit
    edmember 'BaseClass.TestMethod()'. To make the current method override
    that implementation, add the override keyword. Otherwise add
    the new keyword.
    (hiding2.cs(13,14):警告  CS0114:'DerivedClass.TestMethod()'
    屏蔽了所继承的成员'BaseClass.TestMethod()'。
    要想使当前方法改写原来的实现,加上 override关键字。否则加上新的关键字。)
    具有了修饰符new,你就可以告诉编译器,不必重写派生类或改变使用到派生类的代码,
    你的方法就能屏蔽新加入的基类方法。清单5.8  显示如何在例子中运用new修饰符。
    清单  5.8   屏蔽基类方法
    1: class BaseClass
    2: {
    3:  public void TestMethod()
    4:  {
    5:   Console.WriteLine("BaseClass::TestMethod");
    6:  }
    7: }
    8:
    9: class DerivedClass:BaseClass
    10: {
    11:  new public void TestMethod()
    12:  {
    13:   Console.WriteLine("DerivedClass::TestMethod");
    14:  }
    15: }
    使用了附加的new修饰符,编译器就知道你重定义了基类的方法,它应该屏蔽基类方法。
    但是,如果你按以下方式编写:
    DerivedClass test = new DerivedClass();
    ((BaseClass)test).TestMethod();
      基类方法的实现就被调用了。这种行为不同于改写方法,后者保证大部分派生方法获
    得调用。
    5.3  类属性
        有两种途径揭示类的命名属性——通过域成员或者通过属性。前者是作为具有公共
    访问性的成员变量而被实现的;后者并不直接回应存储位置,只是通过 存取标志(acce
    ssors)被访问。
        当你想读出或写入属性的值时,存取标志限定了被实现的语句。用于读出属性的值
    的存取标志记为关键字get,而要修改属性的值的读写符标志记为set。
    在你对该理论一知半解以前,请看一下清单5.9中的例子,属性SquareFeet被标上了get
    和set的存取标志。
    清单 5.9  实现属性存取标志
    1: using System;
    2:
    3: public class House
    4: {
    5:  private int m_nSqFeet;
    6:
    7:  public int SquareFeet
    8:  {
    9:   get { return m_nSqFeet; }
    10:   set { m_nSqFeet = value; }
    11:  }
    12: }
    13:
    14: class TestApp
    15: {
    16:  public static void Main()
    17:  {
    18:   House myHouse = new House();
    19:   myHouse.SquareFeet = 250;
    20:   Console.WriteLine(myHouse.SquareFeet);
    21:  }
    22: }
        House类有一个命名为SquareFeet的属性,它可以被读和写。实际的值存储在一个可
    以从类内部访问的变量中——如果你想当作一个域成员重写它,你所要做的就是忽略存
    取标志而把变量重新定义为:
    public int SquareFeet;
    对于一个如此简单的变量,这样不错。但是,如果你想要隐藏类内部存储结构的细节时
    ,就应该采用存取标志。在这种情况下,set 存取标志给值参数中的属性传递新值。(
    可以改名,见第10行。)
    除了能够隐藏实现细节外,你还可自由地限定各种操作:
    get和set:允许对属性进行读写访问。
    get only:只允许读属性的值。
    set only:只允许写属性的值。
    除此之外,你可以获得实现在set标志中有效代码的机会。例如,由于种种原因(或根本
    没有原因),你就能够拒绝一个新值。最好是没有人告诉你它是一个动态属性——当你
    第一次请求它后,它会保存下来,故要尽可能地推迟资源分配。
    5.4   索引
        你想过象访问数组那样使用索引访问类吗 ?使用C#的索引功能,对它的期待便可了
    结。
    语法基本上象这样:
    属性   修饰符  声明 { 声明内容}
    具体的例子为
    public string this[int nIndex]
    {
    get { ... }
    set { ... }
    }
    索引返回或按给出的index设置字符串。它没有属性,但使用了public修饰符。声明部分
    由类型string和this 组成用于表示类的索引。
        get和set的执行规则和属性的规则相同。(你不能取消其中一个。) 只存在一个差
    别,那就是:你几乎可以任意定义大括弧中的参数。限制为,必须至少规定一个参数,
    允许ref  和out  修饰符。
    this关键字确保一个解释。索引没有用户定义的名字,this 表示默认接口的索引。如果
    类实现了多个接口,你可以增加更多个由InterfaceName.this说明的索引。
        为了演示一个索引的使用,我创建了一个小型的类,它能够解析一个主机名为IP地
    址——或一个IP地址列表(以http://www.microsoft.com为例)。这个列表通过索引可以
    访问,你可以看一下清单5.10 的具体实现。
    清单  5.10  通过一个索引获取一个IP地址
    1: using System;
    2: using System.Net;
    3:
    4: class ResolveDNS
    5: {
    6:  IPAddress[] m_arrIPs;
    7:
    8:  public void Resolve(string strHost)
    9:  {
    10:   IPHostEntry iphe = DNS.GetHostByName(strHost);
    11:   m_arrIPs = iphe.AddressList;
    12:  }
    13:
    14:  public IPAddress this[int nIndex]
    15:  {
    16:   get
    17:   {
    18:    return m_arrIPs[nIndex];
    19:   }
    20:  }
    21:
    22:  public int Count
    23:  {
    24:   get { return m_arrIPs.Length; }
    25:  }
    26: }
    27:
    28: class DNSResolverApp
    29: {
    30:  public static void Main()
    31:  {
    32:   ResolveDNS myDNSResolver = new ResolveDNS();
    33:   myDNSResolver.Resolve("http://www.microsoft.com");
    34:
    35:   int nCount = myDNSResolver.Count;
    36:   Console.WriteLine("Found {0} IP's for hostname", nCount);
    37:   for (int i=0; i < nCount; i++)
    38:    Console.WriteLine(myDNSResolver[i]);
    39:  }
    40: }
         为了解析主机名,我用到了DNS类,它是System .Net 名字空间的一部分。但是,
    由于这个名字空间并不包含在核心库中,所以必须在编译命令行中引用该库:
    csc /r:System.Net.dll /out:resolver.exe dnsresolve.cs
         解析代码是向前解析的。在该  Resolve方法中,代码调用DNS类的静态方法GetHo
    stByName,它返回一个IPHostEntry对象。结果,该对象包含有我要找的数组——Addre
    ssList数组。在退出Resolve 方法之前,在局部的对象实例成员m_arrIPs中,存储了一
    个AddressList array的拷贝(类型IPAddress 的对象存储在其中)。
         具有现在生成的数组 ,通过使用在类ResolveDNS中求得的索引,应用程序代码就
    可以在第37至38行列举出IP地址。(在第6章 "控制语句",有更多有关语句的信息。)   
    因为没有办法更改IP地址,所以仅给索引使用了get存取标志。为了简单其见,我忽略
    了数组的边界溢出检查。
    5.4  事件
        当你写一个类时,有时有必要让类的客户知道一些已经发生的事件。如果你是一个
    具有多年编程经验的程序员,似乎有很多的解决办法,包括用于回调的函数指针和用于
    ActiveX控件的事件接收(event sinks)。现在你将要学到另外一种把客户代码关联到类
    通知的办法——使用事件。
        事件既可以被声明为类域成员(成员变量),也可以被声明为属性。两者的共性为
    ,事件的类型必定是代表元,而函数指针原形和C#的代表元具有相同的含义。
        每一个事件都可以被0或更多的客户占用,且客户可以随时关联或取消事件。你可以
    以静态或者以实例方法定义代表元,而后者很受C++程序员的欢迎。
        既然我已经提到了事件的所有功能及相应的代表元,请看清单5.11中的例子。它生
    动地体现了该理论。
    清单5.11  在类中实现事件处理
    1: using System;
    2:
    3: // 向前声明
    4: public delegate void EventHandler(string strText);
    5:
    6: class EventSource
    7: {
    8:  public event EventHandler TextOut;
    9:
    10:  public void TriggerEvent()
    11:  {
    12:   if (null != TextOut) TextOut("Event triggered");
    13:  }
    14: }
    15:
    16: class TestApp
    17: {
    18:  public static void Main()
    19:  {
    20:   EventSource evsrc = new EventSource();
    21:
    22:   evsrc.TextOut += new EventHandler(CatchEvent);
    23:   evsrc.TriggerEvent();
    24:
    25:   evsrc.TextOut -= new EventHandler(CatchEvent);
    26:   evsrc.TriggerEvent();
    27:
    28:   TestApp theApp = new TestApp();
    29:   evsrc.TextOut += new EventHandler(theApp.InstanceCatch);
    30:   evsrc.TriggerEvent();
    31:  }
    32:
    33:  public static void CatchEvent(string strText)
    34:  {
    35:   Console.WriteLine(strText);
    36:  }
    37:
    38:  public void InstanceCatch(string strText)
    39:  {
    40:   Console.WriteLine("Instance " + strText);
    41:  }
    42: }
        第4行声明了代表元(事件方法原形),它用来给第8行中的EventSource类声明Tex
    tOut事件域成员。你可以观察到代表元作为一种新的类型声明,当声明事件时可以使用
    代表元。
        该类仅有一个方法,它允许我们触发事件。请注意,你必须进行事件域成员不为nu
    ll的检测,因为可能会出现没有客户对事件感兴趣这种情况。
        TestApp类包含了Main 方法,也包含了另外两个方法,它们都具备事件所必需的信
    号。其中一个方法是静态的,而另一个是实例方法。
        EventSource 被实例化,而静态方法CatchEvent被预关联上了 TextOut事件:
    evsrc.TextOut += new EventHandler(CatchEvent);
        从现在起,当事件被触发时,该方法被调用。如果你对事件不再感兴趣,简单地取
    消关联:
    evsrc.TextOut -= new EventHandler(CatchEvent);
        注意,你不能随意取消关联的处理函数——在类代码中仅创建了这些处理函数。为
    了证明事件处理函数也和实例方法一起工作,余下的代码建立了TestApp 的实例,并钩
    住事件处理方法。
        事件在哪方面对你特别有用?你将经常在ASP+中或使用到WFC (Windows Foundatio
    n Classes)时,涉及到事件和代表元。
    5.5   应用修饰符
        在这一章的学习过程中,你已经见过了象public、virtual等修饰符。欲以一种易于
    理解的方法概括它们,我把它们划分为三节:
    。类修饰符
    。成员修饰符
    。存取修饰符
    5.5.1 类修饰符
        到目前为止,我还没有涉及到类修饰符,而只涉及到了应用于类的存取修饰符。但
    是,有两个修饰符你可以用于类:
        abstract——关于抽象类的重要一点就是它不能被实例化。只有不是抽象的派生类
    才能被实例化。派生类必须实现抽象基类的所有抽象成员。你不能给抽象类使用sealed
    修饰符。
         sealed——密封 类不能被继承。使用该修饰符防止意外的继承,在.NET框架中的
    类用到这个修饰符。
        要见到两个修饰符的运用,看看清单5.12 ,它创建了一个基于一个抽象类的密封类
    (肯定是一个十分极端的例子)。
    清单  5.12  抽象类和密封类
    1: using System;
    2:
    3: abstract class AbstractClass
    4: {
    5:  abstract public void MyMethod();
    6: }
    7:
    8: sealed class DerivedClass:AbstractClass
    9: {
    10:  public override void MyMethod()
    11:  {
    12:   Console.WriteLine("sealed class");
    13:  }
    14: }
    15:
    16: public class TestApp
    17: {
    18:  public static void Main()
    19:  {
    20:   DerivedClass dc = new DerivedClass();
    21:   dc.MyMethod();
    22:  }
    23: }
    5.5.2  成员修饰符
        与有用的成员修饰符的数量相比,类修饰符的数量很少。我已经提到了一些,这本
    书即将出现的例子描述了其它的成员修饰符。
        以下是有用的成员修饰符:
         abstract——说明一个方法或存取标志不能含有一个实现。它们都是隐式虚拟,且
    在继承类中,你必须提供 override关键字。
         const——这个修饰符应用于域成员或局部变量。在编译时常量表达式被求值,所
    以,它不能包含变量的引用。
        event ——定义一个域成员或属性作为类型事件。用于捆绑客户代码到类的事件。

        extern——告诉编译器方法实际上由外部实现。第10章 “和非受管代码互相操作”
    将全面地涉及到外部代码。
        override——用于改写任何基类中被定义为virtual的方法和存取标志。要改写的名
    字和基类的方法必须一致。
        readonly——一个使用  readonly修饰符的域成员只能在它的声明或者在包含它的
    类的构造函数中被更改。
        static——被声明为static的成员属于类,而不属于类的实例。你可以用static 于
    域成员、方法、属性、操作符甚至构造函数。
        virtual——说明方法或存取标志可以被继承类改写。
    5.5.3  存取修饰符
        存取修饰符定义了某些代码对类成员(如方法和属性)的存取等级。你必须给每个
    成员加上所希望的存取修饰符,否则,默认的存取类型是隐含的。
        你可以应用4个 存取修饰符之一:
        public——任何地方都可以访问该成员,这是具有最少限制的存取修饰符。
        protected——在类及所有的派生类中可以访问该成员,不允许外部访问。
        private——仅仅在同一个类的内部才能访问该成员。甚至派生类都不能访问它。
        internal——允许相同组件(应用程序或库)的所有代码访问。在.NET组件级别,
    你可以把它视为public,而在外部则为private。
        为了演示存取修饰符的用法,我稍微修改了Triangle例子,使它包含了新增的域成
    员和一个新的派生类(见清单 5.13)。
    清单 5.13  在类中使用存取修饰符
    1: using System;
    2:
    3: internal class Triangle
    4: {
    5:  protected int m_a, m_b, m_c;
    6:  public Triangle(int a, int b, int c)
    7:  {
    8:   m_a = a;
    9:   m_b = b;
    10:   m_c = c;
    11:  }
    12:
    13:  public virtual double Area()
    14:  {
    15:   // Heronian formula
    16:   double s = (m_a + m_b + m_c) / 2.0;
    17:   double dArea = Math.Sqrt(s*(s-m_a)*(s-m_b)*(s-m_c));
    18:   return dArea;
    19:  }
    20: }
    21:
    22: internal class Prism:Triangle
    23: {
    24:  private int m_h;
    25:  public Prism(int a, int b, int c, int h):base(a,b,c)
    26:  {
    27:   m_h = h;
    28:  }
    29:
    30:  public override double Area()
    31:  {
    32:   double dArea = base.Area() * 2.0;
    33:   dArea += m_a*m_h + m_b*m_h + m_c*m_h;
    34:   return dArea;
    35:  }
    36: }
    37:
    38: class PrismApp
    39: {
    40:  public static void Main()
    41:  {
    42:   Prism prism = new Prism(2,5,6,1);
    43:   Console.WriteLine(prism.Area());
    44:  }
    45: }
        Triangle 类和 Prism 类现在被标为 internal。这意味着它们只能在当前组件中被
    访问。请记住“.NET组件”这个术语指的是包装( packaging,),而不是你可能在COM
    +中用到的组件。Triangle 类有三个 protected成员,它们在构造函数中被初始化,并
    用于面积计算的方法中。由于这些成员是protected 成员,所以我可以在派生类Prism中
    访问它们,在那里执行不同的面积计算。Prism自己新增了一个成员m_h,它是私有的—
    —甚至派生类也不能访问它。
        花些时间为每个类成员甚至每个类计划一种保护层次,通常是个好主意。当需要引
    入修改时,全面的计划最终会帮助你,因为没有程序员会愿意使用“没有文档”的类功
    能。
    5.6  小结
        这章显示了类的各种要素,它是运行实例(对象)的模板。在一个对象的生命期,
    首先被执行的代码是个构造函数。构造函数用来初始化变量,这些变量后来在方法中用
    于计算结果。
        方法允许你传递值、引用给变量,或者只传送一个输出值。方法可以被改写以实现
    新的功能,或者你可以屏蔽基类成员,如果它实现了一个具有和派生类成员相同名字的
    方法。
        命名属性可以被当作域成员(成员变量)或属性存取标志实现。后者是get和set存
    取标志,忽略一个或另外一个,你可以创建仅写或仅读属性。存取标志非常适合于确认
    赋给属性的值。
        C#类的另外一个功能是索引,它使象数组语法一样访问类中值成为可能。还有,如
    果当类中的某些事情发生时,你想客户得到通知,要让它们与事件关联。
        当垃圾收集器调用析构函数时,对象的生命就结束了。由于你不能准确地预测这种
    情况什么时候会发生,所以应该创建一个方法以释放这些宝贵的资源,当你停止使用它
    们时。

    --

    ※ 来源:·BBS 水木清华站 smth.org·[FROM: 166.111.215.13]
    上一篇
    返回上一页
    回到目录
    回到页首
    下一篇


       收藏   分享  
    顶(0)
      




    ----------------------------------------------

    -----------------------------------------------

    第十二章第一节《用ROR创建面向资源的服务》
    第十二章第二节《用Restlet创建面向资源的服务》
    第三章《REST式服务有什么不同》
    InfoQ SOA首席编辑胡键评《RESTful Web Services中文版》
    [InfoQ文章]解答有关REST的十点疑惑

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2004/11/9 2:25:00
     
     GoogleAdSense
      
      
      等级:大一新生
      文章:1
      积分:50
      门派:无门无派
      院校:未填写
      注册:2007-01-01
    给Google AdSense发送一个短消息 把Google AdSense加入好友 查看Google AdSense的个人资料 搜索Google AdSense在『 Dot NET,C#,ASP,VB 』的所有贴子 点击这里发送电邮给Google AdSense  访问Google AdSense的主页 引用回复这个贴子 回复这个贴子 查看Google AdSense的博客广告
    2024/5/8 21:55:02

    本主题贴数1,分页: [1]

    管理选项修改tag | 锁定 | 解锁 | 提升 | 删除 | 移动 | 固顶 | 总固顶 | 奖励 | 惩罚 | 发布公告
    W3C Contributing Supporter! W 3 C h i n a ( since 2003 ) 旗 下 站 点
    苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
    3,914.063ms