100165817.htm

来自「C#高级编程(第三版),顶死你们。。 。up」· HTM 代码 · 共 292 行 · 第 1/5 页

HTM
292
字号
<p class="2" style="MARGIN: 0cm 0cm 8.15pt 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">}</span></p>
<p class="a3" style="MARGIN-TOP: 8.15pt; TEXT-INDENT: 21.45pt"><span style="FONT-FAMILY: 黑体">注意:</span></p>
<p class="a1" style="MARGIN-BOTTOM: 8.15pt; TEXT-INDENT: 21.45pt"><span lang="EN-US">Java</span><span style="FONT-FAMILY: 楷体_GB2312">开发人员可以把</span><span lang="EN-US">C#</span><span style="FONT-FAMILY: 楷体_GB2312">中的</span><span lang="EN-US">sealed</span><span style="FONT-FAMILY: 楷体_GB2312">当作</span><span lang="EN-US">Java</span><span style="FONT-FAMILY: 楷体_GB2312">中的</span><span lang="EN-US">final</span><span style="FONT-FAMILY: 楷体_GB2312">。</span></p>
<p class="MsoNormal"><span style="FONT-FAMILY: 宋体">在把类或方法标记为</span><span lang="EN-US">sealed</span><span style="FONT-FAMILY: 宋体">时,最可能的情形是:如果要对库、类或自己编写的其他类进行操作,则重写某些功能会导致错误。也可以因商业原因把类或方法标记为</span><span lang="EN-US">sealed</span><span style="FONT-FAMILY: 宋体">,以防第三方以违反注册协议的方式扩展该类。但一般情况下,在把类或方法标记为</span><span lang="EN-US">sealed</span><span style="FONT-FAMILY: 宋体">时要小心,因为这么做会严重限制它的使用。即使不希望能继承一个类或重写类的某个成员,仍有可能在将来的某个时刻,有人会遇到我们没有预料到的情形。</span><span lang="EN-US">.NET</span><span style="FONT-FAMILY: 宋体">基类库大量使用了密封类,使希望从这些类中派生出自己的类的第三方开发人员无法访问这些类。例如</span><span lang="EN-US">string</span><span style="FONT-FAMILY: 宋体">就是一个密封类。</span></p>
<p class="MsoNormal"><span style="FONT-FAMILY: 宋体">把方法声明为</span><span lang="EN-US">sealed</span><span style="FONT-FAMILY: 宋体">也可以实现类似的目的,但很少这么做。</span></p>
<p class="2" style="MARGIN: 8.15pt 0cm 0pt 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">class MyClass</span></p>
<p class="2" style="MARGIN-LEFT: 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">{</span></p>
<p class="2" style="MARGIN-LEFT: 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">&nbsp;&nbsp; public sealed override void FinalMethod()</span></p>
<p class="2" style="MARGIN-LEFT: 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">&nbsp;&nbsp; {</span></p>
<p class="2" style="MARGIN-LEFT: 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // etc.</span></p>
<p class="2" style="MARGIN-LEFT: 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">&nbsp;&nbsp; }</span></p>
<p class="2" style="MARGIN-LEFT: 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">}</span></p>
<p class="2" style="MARGIN-LEFT: 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">class DerivedClass : MyClass</span></p>
<p class="2" style="MARGIN-LEFT: 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">{</span></p>
<p class="2" style="MARGIN-LEFT: 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">&nbsp;&nbsp; public override void FinalMethod()&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // wrong. Will give compilation error</span></p>
<p class="2" style="MARGIN-LEFT: 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">&nbsp;&nbsp; {</span></p>
<p class="2" style="MARGIN-LEFT: 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">&nbsp;&nbsp; }</span></p>
<p class="2" style="MARGIN: 0cm 0cm 8.15pt 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">}</span></p>
<p class="MsoNormal"><span style="FONT-FAMILY: 宋体">在方法上使用</span><span lang="EN-US">sealed</span><span style="FONT-FAMILY: 宋体">关键字是没有意义的,除非该方法本身是某个基类上另一个方法的重写形式。如果定义一个新方法,但不想让别人重写它,首先就不要把它声明为</span><span lang="EN-US">virtual</span><span style="FONT-FAMILY: 宋体">。但如果要重写某个基类方法,</span><span lang="EN-US">sealed</span><span style="FONT-FAMILY: 宋体">关键字就提供了一种方式,可以确保为方法提供的重写代码是最终的代码,其他人不能再重写它。</span></p>
<h3 style="MARGIN: 8.15pt 0cm"><span lang="EN-US">4.2.6&nbsp; </span><span style="FONT-FAMILY: 黑体">派生类的构造函数</span></h3>
<p class="MsoNormal"><span><span style="FONT-FAMILY: 宋体">第</span><span lang="EN-US">3</span></span><span style="FONT-FAMILY: 宋体">章介绍了单个类的构造函数是如何工作的。这样,就产生了一个有趣的问题,在开始为层次结构中的类</span><span lang="EN-US">(</span><span style="FONT-FAMILY: 宋体">这个类继承了其他类,也可能有定制的构造函数</span><span lang="EN-US">)</span><span style="FONT-FAMILY: 宋体">定义自己的构造函数时,会发生什么情况?</span><span lang="EN-US"> </span></p>
<p class="MsoNormal"><span style="FONT-FAMILY: 宋体">假定没有为类定义任何显式的构造函数,这样编译器就会为所有的类提供默认的构造函数,在后台会进行许多操作,编译器可以很好地解决层次结构中的所有问题,每个类中的每个字段都会初始化为默认值。但在添加了一个我们自己的构造函数后,就要通过派生类的层次结构高效地控制构造过程,因此必须确保构造过程顺利进行,不要出现不能按照层次结构进行构造的问题。</span></p>
<p class="MsoNormal"><span style="FONT-FAMILY: 宋体">为什么派生类会有某些特殊的问题?原因是在创建派生类的实例时,实际上会有多个构造函数在起作用。实例化类的构造函数本身不能初始化类,还必须调用基类中的构造函数。这就是为什么要通过层次结构进行构造的原因。</span></p>
<p class="MsoNormal"><span style="FONT-FAMILY: 宋体">为了说明为什么必须调用基类的构造函数,下面是手机公司</span><span lang="EN-US">MortimerPhones</span><span style="FONT-FAMILY: 宋体">开发的一个例子。这个例子包含一个抽象类</span><span lang="EN-US">GenericCustomer</span><span style="FONT-FAMILY: 宋体">,它表示顾客。还有一个</span><span lang="EN-US">(</span><span style="FONT-FAMILY: 宋体">非抽象</span><span lang="EN-US">)</span><span style="FONT-FAMILY: 宋体">类</span><span lang="EN-US">Nevermore60 Customer</span><span style="FONT-FAMILY: 宋体">,它表示采用特定付费方式</span><span lang="EN-US">(</span><span style="FONT-FAMILY: 宋体">称为</span><span lang="EN-US">Nevermore60</span><span style="FONT-FAMILY: 宋体">付费方式</span><span lang="EN-US">)</span><span style="FONT-FAMILY: 宋体">的顾客。所有的顾客都有一个名字,由一个私有字段表示。在</span><span lang="EN-US">Nevermore60</span><span style="FONT-FAMILY: 宋体">付费方式中,顾客前几分钟的电话费比较高,需要一个字段</span><span lang="EN-US">highCostMinutesUsed</span><span style="FONT-FAMILY: 宋体">,它详细说明了每个顾客该如何支付这些较高的电话费。抽象类</span><span lang="EN-US">GenericCustomer</span><span style="FONT-FAMILY: 宋体">的定义如下所示:</span></p>
<p class="2" style="MARGIN: 8.15pt 0cm 0pt 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">abstract class GenericCustomer</span></p>
<p class="2" style="MARGIN-LEFT: 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">{</span></p>
<p class="2" style="MARGIN-LEFT: 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">&nbsp;&nbsp; private string name;</span></p>
<p class="2" style="MARGIN-LEFT: 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">&nbsp;&nbsp; // lots of other methods etc.</span></p>
<p class="2" style="MARGIN-LEFT: 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">}</span></p>
<p class="2" style="MARGIN-LEFT: 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">class Nevermore60Customer : GenericCustomer</span></p>
<p class="2" style="MARGIN-LEFT: 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">{</span></p>
<p class="2" style="MARGIN-LEFT: 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">&nbsp;&nbsp; private uint highCostMinutesUsed;</span></p>
<p class="2" style="MARGIN-LEFT: 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">&nbsp;&nbsp; // other methods etc.</span></p>
<p class="2" style="MARGIN: 0cm 0cm 8.15pt 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">}</span></p>
<p class="MsoNormal"><span style="FONT-FAMILY: 宋体">不要担心在这些类中执行的其他方法,因为这里仅考虑构造过程。如果下载了本章的示例代码,就会发现类的定义仅包含构造函数。</span></p>
<p class="MsoNormal"><span style="FONT-FAMILY: 宋体">下面看看使用</span><span lang="EN-US">new</span><span style="FONT-FAMILY: 宋体">运算符实例化</span><span lang="EN-US">Nevermore60Customer</span><span style="FONT-FAMILY: 宋体">时,会发生什么情况:</span></p>
<p class="2" style="MARGIN: 8.15pt 0cm 8.15pt 21.45pt; TEXT-INDENT: 18.45pt"><span><span lang="EN-US">&nbsp;&nbsp; GenericCustomer customer = new Nevermore60Customer();</span></span></p>
<p class="MsoNormal"><span style="FONT-FAMILY: 宋体">显然,成员字段</span><span lang="EN-US">name</span><span style="FONT-FAMILY: 宋体">和</span><span lang="EN-US">highCostMinutesUsed</span><span style="FONT-FAMILY: 宋体">都必须在实例化</span><span lang="EN-US">customer</span><span style="FONT-FAMILY: 宋体">时进行初始化。如果没有提供自己的构造函数,而是仅依赖于默认的构造函数,</span><span lang="EN-US">name</span><span style="FONT-FAMILY: 宋体">就会初始化为</span><span lang="EN-US">null</span><span style="FONT-FAMILY: 宋体">引用,</span><span lang="EN-US">highCostMinutesUsed</span><span style="FONT-FAMILY: 宋体">初始化为</span><span lang="EN-US">0</span><span style="FONT-FAMILY: 宋体">。下面详细讨论其过程。</span></p>
<p class="MsoNormal"><span lang="EN-US">HighCostMinutesUsed</span><span style="FONT-FAMILY: 宋体">字段没有问题:编译器提供的默认</span><span lang="EN-US">Nevermore60Customer</span><span style="FONT-FAMILY: 宋体">构造函数会把它初始化为</span><span lang="EN-US">0</span><span style="FONT-FAMILY: 宋体">。</span></p>
<p class="MsoNormal"><span style="FONT-FAMILY: 宋体">那么</span><span lang="EN-US">name</span><span style="FONT-FAMILY: 宋体">呢?看看类定义,显然,</span><span lang="EN-US">Nevermore60Customer</span><span style="FONT-FAMILY: 宋体">构造函数不能初始化这个值。字段</span><span lang="EN-US">name</span><span style="FONT-FAMILY: 宋体">声明为</span><span lang="EN-US">private</span><span style="FONT-FAMILY: 宋体">,这意味着派生的类不能访问它。默认的</span><span lang="EN-US">Nevermore60Customer</span><span style="FONT-FAMILY: 宋体">构造函数甚至不知道存在这个字段。惟一知道这个字段的是</span><span lang="EN-US">GenericCustomer</span><span style="FONT-FAMILY: 宋体">的其他成员,即如果对</span><span lang="EN-US">name</span><span style="FONT-FAMILY: 宋体">进行初始化,就必须在</span><span lang="EN-US">GenericCustomer</span><span style="FONT-FAMILY: 宋体">的某个构造函数中进行。无论类层次结构有多大,这种情况都会一直延续到最终的基类</span><span lang="EN-US">System.Object</span><span style="FONT-FAMILY: 宋体">上。</span></p>
<p class="MsoNormal"><span style="FONT-FAMILY: 宋体">理解了上面的问题后,就可以明白实例化派生类时会发生什么样的情况了。假定默认的构造函数在整个层次结构中使用:编译器首先找到它试图实例化的类的构造函数,在本例中是</span><span lang="EN-US">Nevermore60Customer</span><span style="FONT-FAMILY: 宋体">,这个默认</span><span lang="EN-US">Nevermore60Customer</span><span style="FONT-FAMILY: 宋体">构造函数首先要做的是为其直接基类</span><span lang="EN-US">GenericCustomer</span><span style="FONT-FAMILY: 宋体">运行默认构造函数,然后</span><span lang="EN-US">GenericCustomer</span><span style="FONT-FAMILY: 宋体">构造函数为其直接基类</span><span lang="EN-US">System.Object</span><span style="FONT-FAMILY: 宋体">运行默认构造函数,</span><span lang="EN-US">System. Object</span><span style="FONT-FAMILY: 宋体">没有任何基类,所以它的构造函数就执行,并把控制返回给</span><span lang="EN-US">GenericCustomer</span><span style="FONT-FAMILY: 宋体">构造函数。现在执行</span><span lang="EN-US">GenericCustomer</span><span style="FONT-FAMILY: 宋体">构造函数,把</span><span lang="EN-US">name</span><span style="FONT-FAMILY: 宋体">初始化为</span><span lang="EN-US">null</span><span style="FONT-FAMILY: 宋体">,再把控制权返回给</span><span lang="EN-US">Nevermore60Customer</span><span style="FONT-FAMILY: 宋体">构造函数,接着执行这个构造函数,把</span><span lang="EN-US">highCostMinutesUsed</span><span style="FONT-FAMILY: 宋体">初始化为</span><span lang="EN-US">0</span><span style="FONT-FAMILY: 宋体">,并退出。此时,</span><span lang="EN-US">Nevermore60Customer</span><span style="FONT-FAMILY: 宋体">实例就已经成功地构造和初始化了。</span></p>
<p class="MsoNormal"><span style="FONT-FAMILY: 宋体">构造函数的调用顺序是先调用</span><span lang="EN-US">System.Object</span><span style="FONT-FAMILY: 宋体">,再按照层次结构由上向下进行,直到到达编译器要实例化的类为止。还要注意在这个过程中,每个构造函数都初始化它自己的类中的字段。这是它的一般工作方式,在开始添加自己的构造函数时,也应尽可能遵循这个规则。</span></p>
<p class="MsoNormal"><span style="FONT-FAMILY: 宋体">注意构造函数的执行顺序。基类的构造函数总是最先调用。也就是说,派生类的构造函数可以在执行过程中调用基类方法、属性和其他成员,因为基类已经构造出来的,其字段也初始化了。如果派生类不喜欢初始化基类的方式,但要访问数据,就可以改变数据的初始值,但是,好的编程方式应尽可能避免这种情况,让基类构造函数来处理其字段。</span></p>
<p class="MsoNormal"><span style="FONT-FAMILY: 宋体">理解了构造过程后,就可以开始添加自己的构造函数了。</span></p>
<h4 style="TEXT-INDENT: 21.45pt"><span lang="EN-US">1. </span><span style="FONT-FAMILY: 黑体">在层次结构中添加无参数的构造函数</span></h4>
<p class="MsoNormal"><span style="FONT-FAMILY: 宋体">首先讨论最简单的情况,在层次结构中用一个无参数的构造函数来替换默认的构造函数后,看看会发生什么情况。假定要把每个人的名字初始化为</span><span lang="EN-US">&lt;no name&gt;</span><span style="FONT-FAMILY: 宋体">,而不是</span><span lang="EN-US">null</span><span style="FONT-FAMILY: 宋体">引用,修改</span><span lang="EN-US">GenericCustomer</span><span style="FONT-FAMILY: 宋体">中的代码,如下所示:</span></p>
<p class="a6" style="BACKGROUND: #f2f2f2; MARGIN: 8.15pt 0cm 0pt 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">&nbsp;&nbsp; public abstract class GenericCustomer</span></p>
<p class="a6" style="BACKGROUND: #f2f2f2; MARGIN-LEFT: 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">&nbsp;&nbsp; {</span></p>
<p class="a6" style="BACKGROUND: #f2f2f2; MARGIN-LEFT: 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; private string name;</span></p>
<p class="2" style="MARGIN-LEFT: 21.45pt; TEXT-INDENT: 18.45pt"><span><span lang="EN-US">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public GenericCustomer()</span></span></p>
<p class="2" style="MARGIN-LEFT: 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; : base()&nbsp; // we could omit this line without affecting the compiled code</span></p>
<p class="2" style="MARGIN-LEFT: 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {</span></p>
<p class="2" style="MARGIN-LEFT: 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;name = &quot;&lt;no name&gt;&quot;;</span></p>
<p class="2" style="MARGIN: 0cm 0cm 8.15pt 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</span></p>
<p class="MsoNormal"><span style="FONT-FAMILY: 宋体">添加这段代码后,代码运行正常。</span><span lang="EN-US">Nevermore60Customer</span><span style="FONT-FAMILY: 宋体">仍有自己的默认构造函数,所以上面描述的事件顺序仍不变,但编译器会使用定制的</span><span lang="EN-US">GenericCustomer</span><span style="FONT-FAMILY: 宋体">构造函数,不是默认的构造函数,所以</span><span lang="EN-US">name</span><span style="FONT-FAMILY: 宋体">字段按照需要应初始化为</span><span lang="EN-US">&lt;no name&gt;</span><span style="FONT-FAMILY: 宋体">。</span></p>
<p class="MsoNormal"><span style="FONT-FAMILY: 宋体">注意,在定制的构造函数中,在执行</span><span lang="EN-US">GenericCustomer</span><span style="FONT-FAMILY: 宋体">构造函数前,添加了一个对基类构造函数的显式调用,使用的语法与前面解释如何获得不同的重载构造函数以互相调用时使用的语法相同。惟一的区别是,这次使用的关键字是</span><span lang="EN-US">base</span><span style="FONT-FAMILY: 宋体">,而不是</span><span lang="EN-US">this</span><span style="FONT-FAMILY: 宋体">,表示这是一个基类的构造函数,而不是要调用的类的构造函数。在</span><span lang="EN-US">base</span><span style="FONT-FAMILY: 宋体">关键字后面的圆括号中没有参数,这是非常重要的,因为没有给基类构造函数传送参数,所以编译器会调用无参数的构造函数。其结果是编译器会插入调用</span><span lang="EN-US">System.Object</span><span style="FONT-FAMILY: 宋体">构造函数的代码,这正好与默认情况相同。</span></p>
<p class="MsoNormal"><span style="FONT-FAMILY: 宋体">实际上,可以把这行代码删除,只加上为本章中大多数构造函数编写的代码:</span></p>
<p class="2" style="MARGIN: 8.15pt 0cm 0pt 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span>public GenericCustomer()</span></span></p>
<p class="2" style="MARGIN-LEFT: 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {</span></p>

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?