在运行时,可以通过 GetType 方法获得对象指向的类型对象的类型。
当需要类型转换时,有如下的几种情况:
假设我们有如下的两个类:
public class A
{
public int a { get; set; }
}
publie class B : A
{
public int b { get; set; }
}
这种情况永远都能转换成功,所以 C# 不要求额外的语法。当然,也可以使用显式转换:
A a = new B();
Console.WriteLine(a.GetType() ) ; // B
A a2 = (A)new B();
Console.WriteLine (a2 . GetType () ) ; // B
此时,我们看到 GetType 方法返回的值是“当前命名空间名 .B”。
但是如果我们试图在 Visual Studio 中访问对象 a/a2 的成员,会发现它只有 a —个成员,并没有 b (实际上,可以编写 IL 访问 B 的方法)。那么 a/a2 的类型究竟是什么呢?(答案是A)
实际上,根据之前对象初始化的知识,我们已经知道,在使用 new 关键字时,会在堆上初始化类型对象和普通对象。
当我们运行完上面的四行代码之后,内存中的布局大家应该可以想象出来了,如下图所示。
当调用 GetType 方法时,实际上获取的对象是指向的类型对象的名称 B,但是,这不意味着对象本身就是这个类型。
对象本身的类型是在定义时就决定的,在本例中为 A 类型(编译时类型)。至于 new 后跟的类型 B,只是意味着,对象(本例中是一个引用类型)在栈上所保存的引用指向一个类型 B 的实例(运行时类型)。
new 关键字会完成 B 的初始化工作, 然后返回堆上对应的地址。
在基类引用中保存派生类型总是安全的,这是隐式转换。
运行时类型决定对象调用方法时去哪个方法表,而编译时类型是 Callvirt 决定最终调用哪个类型的哪个方法。
结论:
对于这种情况,C# 要求必须使用显式转换,因为这样的转换可能会在运行时失败。
// B b = new A(); 不能通过编译
B b = (B)new A();
这是一个特例,对于逻辑上有从属关系的基元类型,C# 可以完成转换。
例如,你可以隐式地将一个 int 转换为 long,即使这两个结构体没有继承关系。
这是因为,C# 会在基元类 型的类型转换时使用自己的特殊规则,而 int 和 long 逻辑上有从属关系。
从较大的集合转换为较小的集合时,C# 总是要求显式转换。例如,long 转换为 int 必须显式转换,而反过来则可以隐式转换。
对于拥有继承关系的两个类,我们可以做显式转换和隐式转换,这取决于类的包含关系。
但如果两个类或者结构并没有继承关系,我们仍然可以通过自定义类型转换实现类的类型转换。
自定义类型转换需要 explicit 和 implicit 关键字。下面的例子实现了分数,它包括分子和分母两个成员,并实现了加和乘的重载。
class Fraction
{
public int fenzi { get; set; }
public int fenmu { get; set; }
public Fraction(int X,int Y)
{
fenzi = X;
fenmu = Y;
}
//两个分数相加
public static Fraction operator +(Fraction p1,Fraction p2)
{
return new Fraction(p1.fenzi * p2.fenmu + p2.fenzi * p1.fenmu, p1.fenmu + p2.fenmu);
}
//两个分数相乘
public static Fraction operator*(Fraction p1,Fraction p2)
{
return new Fraction(p1.fenzi * p2.fenzi, p1.fenmu * p2.fenmu);
}
public static explicit operator double(Fraction f1)
{
return (double)f1.fenzi / f1.fenmu;
}
}
之后我们就可以显式地将分数转化为小数:
Fraction f1 = new Fraction(1, 4);
Console.WriteLine((double) f1); // 0.25
注意:当有自定义类型转换时,构造函数必须是公有的,关键字 public 不能省略。
自定义类型转换的实质是重载类型参数的行为。如果为一个 a 类 -> b 类构建了显式转换,则不能同时构建隐式转换(隐式转换不会成功)。
不过如果定义了隐式转换,则显式转换自动 生效(两种转换都会成功)。