100165824.htm
来自「C#高级编程(第三版),顶死你们。。 。up」· HTM 代码 · 共 251 行 · 第 1/5 页
HTM
251 行
<p class="2" style="MARGIN-LEFT: 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US"> }</span></p>
<p class="2" style="MARGIN: 0cm 0cm 8.15pt 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US"> // etc.</span></p>
<h4 style="TEXT-INDENT: 21.45pt"><span lang="EN-US">3. </span><span style="FONT-FAMILY: 黑体">装箱和取消装箱数据类型转换</span></h4>
<p class="MsoNormal"><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><span lang="EN-US">(</span><span style="FONT-FAMILY: 宋体">理论上可以在结构和</span><span lang="EN-US">System.ValueType</span><span style="FONT-FAMILY: 宋体">之间进行转换,但一般很少这么做</span><span lang="EN-US">)</span><span style="FONT-FAMILY: 宋体">。</span></p>
<p class="MsoNormal"><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">object</span><span style="FONT-FAMILY: 宋体">的转换总是一种隐式转换,因为这种转换是从派生类型到基本类型的转换,即第</span><span lang="EN-US">2</span><span style="FONT-FAMILY: 宋体">章中简要介绍的装箱过程。例如</span><span style="FONT-FAMILY: 宋体">,</span><span lang="EN-US">Currency</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">Currency balance = new Currency(40,0);</span></p>
<p class="2" style="MARGIN: 0cm 0cm 8.15pt 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">object baseCopy = balance;</span></p>
<p class="MsoNormal"><span style="FONT-FAMILY: 宋体">在执行上述隐式转换时,</span><span lang="EN-US">balance</span><span style="FONT-FAMILY: 宋体">的内容被复制到堆上,放在一个装箱的对象上,</span><span lang="EN-US">BaseCopy</span><span style="FONT-FAMILY: 宋体">对象引用设置为该对象。在后台发生的情况是:在最初定义</span><span lang="EN-US">Currency</span><span style="FONT-FAMILY: 宋体">结构时,</span><span lang="EN-US">.NET Framework</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">Currency</span><span style="FONT-FAMILY: 宋体">类,它包含与</span><span lang="EN-US">Currency</span><span style="FONT-FAMILY: 宋体">结构相同的所有字段,但却是一个引用类型,存储在堆上。无论这个值类型是一个结构,还是一个枚举,定义它时都存在类似的装箱引用类型,对应于所有的基本值类型,如</span><span lang="EN-US">int</span><span style="FONT-FAMILY: 宋体">、</span><span lang="EN-US">double</span><span style="FONT-FAMILY: 宋体">和</span><span lang="EN-US"> uint</span><span style="FONT-FAMILY: 宋体">。不能也不必在源代码中直接编程访问这些装箱类型,但在把一个值类型转换为</span><span lang="EN-US">object</span><span style="FONT-FAMILY: 宋体">时,它们是在后台工作的对象。在隐式地把</span><span lang="EN-US">Currency </span><span style="FONT-FAMILY: 宋体">转换为</span><span lang="EN-US"> object</span><span style="FONT-FAMILY: 宋体">时,会实例化一个装箱</span> <span lang="EN-US">Currency</span><span style="FONT-FAMILY: 宋体">实例,并用</span><span lang="EN-US">Currency</span><span style="FONT-FAMILY: 宋体">结构中的所有数据进行初始化。在上面的代码中,</span><span lang="EN-US">BaseCopy</span><span style="FONT-FAMILY: 宋体">对象引用的就是这个已装箱的</span><span lang="EN-US">Currency</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">object derivedObject = new Currency(40,0);</span></p>
<p class="2" style="MARGIN-LEFT: 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">object baseObject = new object();</span></p>
<p class="2" style="MARGIN-LEFT: 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">Currency derivedCopy1 = (Currency)derivedObject; // OK</span></p>
<p class="2" style="MARGIN: 0cm 0cm 8.15pt 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">Currency derivedCopy2 = (Currency)baseObject; // Exception thrown</span></p>
<p class="MsoNormal"><span style="FONT-FAMILY: 宋体">上述代码的工作方式与前面的引用类型一样。把</span><span lang="EN-US">DerivedObject</span><span style="FONT-FAMILY: 宋体">转换为</span><span lang="EN-US"> Currency</span><span style="FONT-FAMILY: 宋体">会成功进行,因为</span><span lang="EN-US">DerivedObject</span><span style="FONT-FAMILY: 宋体">实际上引用的是装箱</span> <span lang="EN-US">Currency</span><span style="FONT-FAMILY: 宋体">实例<span style="LETTER-SPACING: -1pt">——</span></span><span style="LETTER-SPACING: -1pt"> </span><span style="FONT-FAMILY: 宋体">转换的过程是把已装箱的</span> <span lang="EN-US">Currency</span><span style="FONT-FAMILY: 宋体">对象的字段复制到一个新的</span><span lang="EN-US">Currency</span><span style="FONT-FAMILY: 宋体">结构中。第二个转换会失败,因为</span><span lang="EN-US">BaseObject</span><span style="FONT-FAMILY: 宋体">没有引用已装箱的</span> <span lang="EN-US">Currency</span><span style="FONT-FAMILY: 宋体">对象。</span></p>
<p class="MsoNormal"><span style="FONT-FAMILY: 宋体">在使用装箱和取消装箱时,这两个过程都把数据复制到新装箱和取消装箱的对象上,理解这一点是非常重要的。这样,对装箱对象的操作就不会影响原来值类型的内容。</span></p>
<h3 style="MARGIN: 8.15pt 0cm"><span><span lang="EN-US">5.5.2 </span></span><span style="FONT-FAMILY: 黑体">多重数据类型转换</span></h3>
<p class="MsoNormal"><span><span style="FONT-FAMILY: 宋体">在定义数据类型转换时必须考虑的一个问题是,如果在进行要求的数据类型转换时,</span><span lang="EN-US"> C#</span></span><span style="FONT-FAMILY: 宋体">编译器没有可用的直接转换方式,就会寻找一种方式,把几种转换合并起来。例如,在</span><span lang="EN-US">Currency</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">Currency balance = new Currency(10,50);</span></p>
<p class="2" style="MARGIN-LEFT: 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">long amount = (long)balance;</span></p>
<p class="2" style="MARGIN: 0cm 0cm 8.15pt 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">double amountD = balance;</span></p>
<p class="MsoNormal"><span style="FONT-FAMILY: 宋体">首先初始化一个</span><span lang="EN-US">Currency</span><span style="FONT-FAMILY: 宋体">实例,再把它转换为一个</span><span lang="EN-US">long</span><span style="FONT-FAMILY: 宋体">。问题是不能定义这样的转换。但是,这段代码仍可以编译成功。因为编译器知道我们要定义一个从</span><span lang="EN-US">Currency</span><span style="FONT-FAMILY: 宋体">到</span><span lang="EN-US">float</span><span style="FONT-FAMILY: 宋体">的隐式转换,而且它知道如何显式地从</span><span lang="EN-US">float </span><span style="FONT-FAMILY: 宋体">转换为</span><span lang="EN-US">long</span><span style="FONT-FAMILY: 宋体">。所以它会把这行代码编译为中间语言代码,首先把</span><span lang="EN-US">balance</span><span style="FONT-FAMILY: 宋体">转换为</span><span lang="EN-US">float</span><span style="FONT-FAMILY: 宋体">,再把结果转换为</span><span lang="EN-US">long</span><span style="FONT-FAMILY: 宋体">。上述代码的最后一行也是这样,把</span><span lang="EN-US">balance</span><span style="FONT-FAMILY: 宋体">转换为</span><span lang="EN-US">double</span><span style="FONT-FAMILY: 宋体">型时,因为从</span><span lang="EN-US">Currency</span><span style="FONT-FAMILY: 宋体">到</span><span lang="EN-US"> float</span><span style="FONT-FAMILY: 宋体">的转换和</span><span lang="EN-US">float </span><span style="FONT-FAMILY: 宋体">到</span><span lang="EN-US">double</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">Currency balance = new Currency(10,50);</span></p>
<p class="2" style="MARGIN-LEFT: 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">long amount = (long)(float)balance;</span></p>
<p class="2" style="MARGIN: 0cm 0cm 8.15pt 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">double amountD = (double)(float)balance;</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">Currency balance = new Currency(10,50);</span></p>
<p class="2" style="MARGIN: 0cm 0cm 8.15pt 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">long amount = balance;</span></p>
<p class="MsoNormal"><span style="FONT-FAMILY: 宋体">原因是编译器可以找到的最佳匹配的转换仍是首先转换为</span><span lang="EN-US">flost</span><span style="FONT-FAMILY: 宋体">,再转换为</span><span lang="EN-US">long</span><span style="FONT-FAMILY: 宋体">,但从</span><span lang="EN-US">float</span><span style="FONT-FAMILY: 宋体">到</span><span lang="EN-US">long</span><span style="FONT-FAMILY: 宋体">的转换需要显式指定。</span></p>
<p class="MsoNormal"><span style="FONT-FAMILY: 宋体">所有这些都不会带来太多的麻烦。转换的规则是非常直观的,主要是为了防止在开发人员不知情的情况下丢失数据。但是,在定义数据类型转换时如果不小心,编译器就有可能指定一条导致不期望的结果的路径。例如,假定编写</span><span lang="EN-US">Currency</span><span style="FONT-FAMILY: 宋体">类的其他小组成员要把一个</span><span lang="EN-US">uint</span><span style="FONT-FAMILY: 宋体">转换为</span><span lang="EN-US">Currency</span><span style="FONT-FAMILY: 宋体">,而该</span><span lang="EN-US">uint</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></p>
<p class="2" style="MARGIN: 8.15pt 0cm 0pt 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">public static implicit operator Currency (uint value)</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"> return new Currency(value/100u, (ushort)(value%100));</span></p>
<p class="2" style="MARGIN: 0cm 0cm 8.15pt 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">} // Don't do this!</span></p>
<p class="MsoNormal"><span style="FONT-FAMILY: 宋体">注意,在这段代码中,第一个</span><span lang="EN-US">100</span><span style="FONT-FAMILY: 宋体">后面的</span><span lang="EN-US">u</span><span style="FONT-FAMILY: 宋体">可以确保把</span><span lang="EN-US">value/100u</span><span style="FONT-FAMILY: 宋体">解释为</span><span lang="EN-US">uint</span><span style="FONT-FAMILY: 宋体">。如果写成</span><span lang="EN-US">value/100</span><span style="FONT-FAMILY: 宋体">,编译器就会把它解释为一个</span><span lang="EN-US">int</span><span style="FONT-FAMILY: 宋体">型的值,而不是</span><span lang="EN-US">uint</span><span style="FONT-FAMILY: 宋体">型的值。</span></p>
<p class="MsoNormal"><span style="FONT-FAMILY: 宋体">我们已经很清楚地在这段代码中注释了“不要这么做”。看看下面的代码段,它把包含</span><span lang="EN-US">350</span><span style="FONT-FAMILY: 宋体">的</span><span lang="EN-US">uint</span><span style="FONT-FAMILY: 宋体">转换为一个</span><span lang="EN-US">Currency</span><span style="FONT-FAMILY: 宋体">,再转换回</span><span lang="EN-US">uint</span><span style="FONT-FAMILY: 宋体">。那么在执行完这段代码后,</span><span lang="EN-US">bal2</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">uint bal = 350;</span></p>
<p class="2" style="MARGIN-LEFT: 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">Currency balance = bal;</span></p>
<p class="2" style="MARGIN: 0cm 0cm 8.15pt 21.45pt; TEXT-INDENT: 18.45pt"><span lang="EN-US">uint bal2 = (uint)balance;</span></p>
<p class="MsoNormal"><span style="FONT-FAMILY: 宋体">答案不是</span><span lang="EN-US">350</span><span style="FONT-FAMILY: 宋体">,而是</span><span lang="EN-US">3</span><span style="FONT-FAMILY: 宋体">!这是符合逻辑的。我们把</span><span lang="EN-US">350</span><span style="FONT-FAMILY: 宋体">隐式地转换为</span><span lang="EN-US">Currency</span><span style="FONT-FAMILY: 宋体">,得到</span><span lang="EN-US">balance.Dollars=3</span><span style="FONT-FAMILY: 宋体">,</span><span lang="EN-US">balance.Cents=50</span><span style="FONT-FAMILY: 宋体">。然后编译器进行通常的操作,为转换回</span><span lang="EN-US">uint</span><span style="FONT-FAMILY: 宋体">指定最佳路径。</span><span lang="EN-US">balance</span><span style="FONT-FAMILY: 宋体">最终会被隐式地转换为</span><span lang="EN-US">float</span><span style="FONT-FAMILY: 宋体">型</span><span lang="EN-US">(</span><span style="FONT-FAMILY: 宋体">其值为</span><span lang="EN-US">3.5)</span><span style="FONT-FAMILY: 宋体">,然后显式地转换为</span><span lang="EN-US">uint</span><span style="FONT-FAMILY: 宋体">型,其值为</span><span lang="EN-US">3</span><span style="FONT-FAMILY: 宋体">。</span></p>
<p class="MsoNormal"><span style="FONT-FAMILY: 宋体">当然,转换为另一个数据类型后,再转换回来有时会丢失数据。例如,把包含</span><span lang="EN-US">5.8</span><span style="FONT-FAMILY: 宋体">的</span><span lang="EN-US">float</span><span style="FONT-FAMILY: 宋体">转换为</span><span lang="EN-US">int</span><span style="FONT-FAMILY: 宋体">,再转换回</span><span lang="EN-US">float</span><span style="FONT-FAMILY: 宋体">,会丢失数字中的小数部分,得到</span><span lang="EN-US">5</span><span style="FONT-FAMILY: 宋体">,但这和一个整数被</span><span lang="EN-US">100</span><span style="FONT-FAMILY: 宋体">整除的情况略有区别。</span><span lang="EN-US">Currency</span><span style="FONT-FAMILY: 宋体">现在成了一种相当危险的类,它会对整数进行一些奇怪的操作。</span></p>
<p class="MsoNormal"><span style="FONT-FAMILY: 宋体">问题是,在转换过程中如何解释整数是有矛盾的。从</span><span lang="EN-US">Currency</span><span style="FONT-FAMILY: 宋体">到</span><span lang="EN-US">float</span><span style="FONT-FAMILY: 宋体">的转换会把整数</span><span lang="EN-US">1</span><span style="FONT-FAMILY: 宋体">解释为</span><span lang="EN-US">1</span><span style="FONT-FAMILY: 宋体">美元,但</span><span lang="EN-US">uint</span><span style="FONT-FAMILY: 宋体">到</span><span lang="EN-US">Currency</span><span style="FONT-FAMILY: 宋体">的转换会把这个整数解释为</span><span lang="EN-US">1</span><span style="FONT-FAMILY: 宋体">美分,这是很糟糕的。如果希望类易于使用,就应确保所有的转换都按一种手工兼容的方式执行,即这些转换应得到相同的结果。在本例中,显然要重新编写</span><span lang="EN-US">uint</span><span style="FONT-FAMILY: 宋体">到</span><span lang="EN-US">Currency</span><span style="FONT-FAMILY: 宋体">的转换,把整数值</span><span lang="EN-US">1</span><span style="FONT-FAMILY: 宋体">解释为</span><span lang="EN-US">1</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">public static implicit operator Currency (uint value)</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"> return new Currency(value, 0);</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">uint</span><span style="FONT-FAMILY: 宋体">到</span><span lang="EN-US">Currency</span><span style="FONT-FAMILY: 宋体">的转换时,就只能通过</span><span lang="EN-US">float</span><span style="FONT-FAMILY: 宋体">来进行。此时直接转换的效率要高得多,所以进行这种额外转换会提高性能,但需要确保它
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?