引用转换
我们已经知道引用类型对象由内存中的两部分组成:引用和数据。
由引用保存的那部分信息是它指向的数据类型。
引用转换接受源引用并返回一个指向堆中同一位置的引用,但是把引用“标记”为其他
类型。
例如,如下代码给出了两个引用变量:myVar1和myVar2,它们指向内存中的相同对象。代
码如图17-17所示。对于myvarl,它引用的对象看上去是3类型的对象一一一其实就是。
对于myVar2,同样的对象看上去像A类型的对象。
- 即使它实际指向B类型的对象,它也看不到B扩展A的部分,因此看不到Field2。
- 第二个WriteLine语句因此会产生编译错误。
注意,“转换"不会改变myVar1。
class A {public int Field1;}
class B:A {public int Field2;}
class Program
{
static void Main()
{
B myVar1=new B();
//作为A类的引用返回myVar1的引用
A myVar2=(A)myVar1;
Console.WriteLine($"{myVar2.Field1}"); //正确
COnsole.WriteLine($"{mVar2.Field2}"); // 编译错误 Field2对于myVar2不可见
}
}
隐式引用转换
与语言为我们自动实现的隐式数字转换类似,还有隐式引用转换,如图17-18所示。
- 所有引用类型可以被隐式转换为object类型。
- 任何接口可以隐式转换为它继承的接口。
- 类可以隐式转换为:
- 它继承链中的任何类;
- 它实现的任何接口。
委托可以隐式转换成图17-19所示的.NETBCL类和接口。Arrays数组(其中的元素是Ts类
型)可以隐式转换成:
- 图17-19所示的.NET BCL类和接口;
- 另一个数组ArrayT,其中的元素是Tt类型(如果满足下面的所有条件)。
- 两个数组维度的一样。
- 元素类型Ts和Tt都是引用类型,不是值类型。
- 在类型Ts和Tt中存在隐式转换。
显式引用转换
显式引用转换是从一个普通类型到一个更精确类型的引用转换。
- 显式转换包括:
- 从object到任何引用类型的转换;
- 从基类到派生自它的类的转换。
- 倒转图17-18和图17-19的箭头方向,可以演示显式引用转换。
如果转换的类型不受限制,我们很容易尝试引用在内存中实际并不存在的类成员。然而,编
译器确实允许这样的转换。但如果系统在运行时遇到它们,则会抛出异常。
例如,图17-20中的代码将基类A的引用转换到它的派生类B,并且把它赋值给变量myVar2。
- 如果myVar2尝试访问Field2,它会尝试访问对象中"B部分"的字段(它不在内存中),
这会导致内存错误。 - 运行时会捕获到这种不正确的强制转换并且抛出InvalidCastException异常。然而,注
意,它不会导致编译错误。
有效显式引用转换
在运行时能成功进行(也就是不抛出InvalidCastException异常)的显式转换有3种情况。
第一种情况:显式转换是没有必要的。也就是说,语言已经为我们进行了隐式转换。例如,
在下面的代码中,显式转换是没有必要的,因为从派生类到基类的转换总是隐式转换。
class A {public int Field1;}
class B:A{public int Field2;}
...
B myVar1=new B();
A myVar2=(A)myVar2; //不必转换,因为A是B的基类
第二种情况:源引用是null。例如,在下面的代码中,即使转换基类的引用到派生类的引用
通常是不安全的,但是由于源引用是null,这种转换还是允许的。
class A {public int Field1;}
class B:A{public int Field2;}
...
A myVar1=null;
B myVar2=(B)myVar1; //允许转换,因为myVar1为空
第三种情况:由源引用指向的实际数据可以安全地进行隐式转换。如下代码给出了一个示例,
图17-21演示了这段代码。
- 第二行中的隐式转换使myVar2看上去像指向A类型的数据,其实它指向的是B类型的数
据对象。 - 第三行中的显式转换把基类引用强制转换为它的派生类的引用。这通会产生异常。然
而,在这种情况下,指向的对象实际就是B类型的数据项。
class A {public int Field1;}
class B:A{public int Field2;}
...
B myVar1=new B();
A myVar2=myVar1; //将myVar1隐式转换为A类型
B myVar2=(B)myVar2; //该转换是允许的,因为数据是B类型