【IT168 技术开发】学过编程的朋友都知道类型转换,并且也都清楚“隐式转换”、“显式转换”、“装箱”、“拆箱”等概念。但是,类型转换究竟有多少种?如何对其进行划分?何时应该使用何种类型转换?
类型转换的分类
学过编程的朋友都知道类型转换,并且也都清楚“隐式转换”、“显式转换”、“装箱”、“拆箱”等概念。但是,类型转换究竟有多少种?如何对其进行划分?何时应该使用何种类型转换?
本文将就这些问题进行一个简要解释。需要说明的是,本文中的几乎所有内容都来自我在讲课过程中的积累,在此强烈感谢我的学员,尤其是对我的看法提出疑问和建议的学员!另外,本文不一定对您的实际编程工作有太多帮助,不过可以让您对类型转换有一个新的理解。
1 相关概念
类型转换发生的时机 (Occasion)——当发生值的复制时,可能会发生类型转换。所谓“值的复制”,包括赋值运算和方法传参。如果被赋值的变量或方法的形式参数的类型与实际的对象类型不同,就需要进行类型转换。
下面给出两种发生类型转换的情况。
1// 例1 2int x = 10; 3long y = x; // 此处发生类型转换,因为x与y的类型不同。 4 5// 例2 6void F(long var) ...{ ... } 7 8void G() 9...{ 10 int i = 10; 11 F(i); // 此处发生类型转换,因为实参i与形参var的类型不同。 12} 13
源类型 (Source Type) 和目标类型 (Destination Type) ——当发生类型转换时,被赋值的变量或方法的形参的类型称为目标类型,而实际的对象的类型称为源类型。
例如前面例1中,变量x的类型int是源类型,而变量y的类型long是目标类型;例2中,变量i的类型int是源类型,而方法F的形参var的类型long是目标类型。
2 类型转换的分类
C#中的类型转换有两种分类方法,一种是根据转换方式的不同进行划分,可以分为显式 (Explicit) 转换和隐式 (Implicit) 转换两种;另外一种是根据源类型和目标类型之间的关系进行划分,可以分为变换 (Conversion)、投射 (Cast)和装箱/拆箱 (Boxing/Unboxing)。
下面这张图描绘了各种类型转化,以及它们发生的时机。
2.1 显式转换和隐式转换
从直观上看,显式和隐式转换只是语法上面的差别。当发生类型转换时,如果在代码中明确指定了目标类型,则称为显式转换,否则则称为隐式转换。
C#通过将形如“(目标类型)”这样的语法构造放在待转换对象的前面,来表示一个显式转换。
下面的代码介绍了在C#语言中进行显式和隐式转换时的语法。
需要注意的是,不是任意两种类型之间都能随意进行转换的。另外,无论显式转换还是隐式转换,都可能会失败。1int x = 10; 2long y = x; // 隐式转换 3x = (int) long; // 显示转换 4
如果显式转换失败,会在运行时抛出异常(这个异常可能是InvalidCastException,也可能是InvalidOperationException、OverflowException等具体异常);如果隐式转换失败,则会在编译时得到一个错误,指出不能进行隐式转换。
最后,隐式转换也可以用显式转换替代,但显式转换不能用隐式转换替代。换句话说,可以用显式转换的地方,用隐式转换也没什么问题;但需要显式转换的地方,就一定不能用隐式转换。
下面将从另外一种角度介绍各种不同的类型转换。
2.2 变换、投射、装箱/拆箱
根据参与类型转换的两种类型(源类型和目标类型)的关系不同,可以将类型转换分成三种;即:
如果源类型和目标类型一个是值类型一个是引用类型,则称为装箱/拆箱;
如果源类型和目标类型之间存在着直接或间接继承,则称为投射;
如果源类型和目标类型不具备上述两种关系,如两种简单值类型或兄弟/邻居类型(有着共同的祖先类)之间,则称为普通类型转换(或称“变换”)。
变换
变换是最普通的一种类型转换,通常发生在:
简单(内置)值类型之间;
重载了类型转换运算符的类型之间;
简单值类型就是int、long、float、double等类型。在这些类型之间转换的时候,如果不会发生精度损失,则可以使用隐式转换,否则必须使用显式转换。换言之,从较短的类型向较长的类型转换,可以用隐式转换;从较长的向较短的转换,必须用显式转换(“长短”指的是:int长4字节,long长8字节,所以int比long短。)
投射
当源类型和目标类型具有直接或间接的继承关系时,发生的类型转换属于投射。如果将子类型的一个对象转换成祖先类型,则称向上投射 (Upcast)。反之,将祖先类型的对象转换为子孙类型,则称向下投射 (Downcast)。
上图中,CA到CB、CA到CC、CA到CD、CB到CC的转换属于向下投射,而CB到CA、CC到CA、CD到CA、CC到CB的转换属于向上投射。
向上投射可以使用隐式转换,但向下投射必须使用显示转换。这是因为当发生继承关系时,子类将具有父类所有的成员(尽管父类中的private成员在子类中无法访问,但子类确实拥有这些成员),因此子类对象向父类转换时可以确保不会丢失成员,而如果父类对象向子类转换,不一定保证对象具备子类特有的成员。
需要注意的是,如果两个类型位于同一继承树中,但没有直接或间接继承关系(通常称这样的类型为“兄弟类型”或“邻居类型”,如上图中的CB和CD、CC和CD),是不允许发生任何转换的,除非重载了类型转换运算符。
下面解释一下为什么将这种类型的转换称为投射。
假设类CA中定义了一个属性、一个事件和一个方法,现在用方块表示属性、用三角表示事件、用圆圈表示方法,则可以将CA绘制为下面的图形。
假设类CB继承自CA,并定义了额外的一个属性和一个方法,则可以将CB绘制为下面的图形。
现在考虑用一个和CA形状一致的模板罩在CB类型的一个对象上,并透过模板去看这个对象(如下图所示),则可以看到对象的一个视图,并且从其中只能看到CA类中定义的成员。
这个情形符合这种转换。如果将视线想象成光线,则形成了一个光线的投射。
装箱/拆箱
当发生类型转换时,如果源类型和目标类型一个是值类型而另一个是引用类型时,将发生装箱/拆箱转换。
从值类型向引用类型的转换称为装箱,而从引用类型向值类型转换称为拆箱。
装箱时,会在堆中创建一个新的对象(作为一个“箱子”),并将值类型值复制到这个对象中(把这个值“装起来”);而拆箱时,只需将对象中的值复制到栈上。
在装箱/拆箱中,何时使用显式(隐式)转换是不确定的,取决于类型中重载了哪种转换运算符。