《C#高级编程》第6版154页。
基类和派生类之间的数据类型转换
MyDerived直接或间接派生于MyBase,从MyDerived到MyBase的转换:
MyDerived derivedObject = new MyDerived();
MyBase baseCopy = derivedObject;
MyDerived隐式转换为MyBase,因为对类MyBase的任何引用都可以引用类MyBase的对象或派生于MyBase的对象。在OO编程中,派生类的实例实际上是基类的实例,但加上了一些额外的信息。在基类上定义的所有函数和字段也都在派生类上定义了。
下面看看另一种方式:
MyBase derivedObject = new MyDerived();
MyBase baseObject = new MyBase();
MyDerived derivedCopy1 = (MyDerived) derivedObject; // OK
MyDerived derivedCopy2 = (MyDerived) baseObject; // Throws exception
最后一个语句在执行时会抛出一个异常。在进行数据类型转换时,会检查被引用的对象。因为基类引用实际上可以引用一个派生类实例,所以这个对象可能是要转换的派生类的一个实例。如果是这样,转换就会成功,派生的引用被设置为引用这个对象。但如果该对象不是派生类(或者派生于这个类的其他类)的一个实例,转换就会失败,抛出一个异常。
注意,编译器已经提供了基类和派生类之间的转换,这种转换实际上并没有对对象进行任何数据转换。如果要进行的转换是合法的,它们也仅是把新引用设置为对对象的引用。这些转换在本质上与用户定义的转换不同。例如,在前面的SimpleCurrency示例中(150页),我们定义了Currency结构和float之间的转换。在float到Currency的转换中,则实例化了一个新Currency结构,并用要求的值进行初始化。如果要把MyBase实例转换为MyDerived对象,其值根据MyBase实例的内容来确定,就不能使用数据类型转换语法。最合适的选项通常是定义一个派生类的构造函数,它的参数是一个基类实例,让这个构造函数执行相关的初始化:
class DerivedClass : BaseClass
{
public DerivedClass(BaseClass rhs)
{
// initialize object from the Base instance
}
// etc.
装箱和拆箱数据类型转换
从结构(或基本类型)到object的转换总是一种隐式转换,因为这种转换是从派生类型到基本类型的转换。例如,Currency结构:
Currency balance = new Currency(40, 0);
object baseCopy = balance;
在执行上述隐式转换时,balance的内容被复制到堆上,放在一个装箱的对象上,BaseCopy对象引用设置为该对象。在后台发生的情况是:在最初定义Currency结构时,.NET Framework隐式地提供另一个(隐式)类,即装箱的Currency类,它包含与Currency结构相同的所有字段,但却是一个引用类型,存储在堆上。无论这个值类型是一个结构,还是一个枚举,定义它时都存在类似的装箱引用类型,对应于所有的基本值类型,如int、double和uint。不能也不必在源代码中直接编程访问这些装箱类型,但在把一个值类型转换为object时,它们是在后台工作的对象。在隐式地把Currency转换为object时,会实例化一个装箱的Currency实例,并用Currency结构中的所有数据进行初始化。在上面的代码中,BaseCopy对象引用的就是这个已装箱的Currency实例。通过这种方式,就可以实现从派生类到基类的转换,并且,值类型的语法与引用类型的语法一样。
转换的另一个方式称为拆箱。与在基本引用类型和派生引用类型之间的转换一样,这是一种显式转换,因为如果要转换的对象不是正确的类型,会抛出一个异常:
object derivedObject = new Currency(40, 0);
object baseObject = new object();
Currency derivedCopy1 = (Currency)derivedObject; // OK
Currency derivedCopy2 = (Currency)baseObject; // Exception thrown
上述代码的工作方式与前面的引用类型一样。把derivedObject转换为Currency成功进行,因为derivedObject实际上引用的是装箱Currency实例——转换的过程是把已装箱的Currency对象的字段复制到一个新的Currency结构中。第二个转换会失败,因为baseObject没有引用已装箱的Currency对象。
在使用装箱和拆箱时,这两个过程都把数据复制到新装箱和拆箱的对象上,理解这一点是非常重要的。这样,对装箱对象的操作就不会影响原来值类型的内容。