当需要类型转换时,有如下的几种情况:
- 将一个对象转换为它的基类型。
- 将一个对象转换为它的派生类型。
- 将一个对象转换为和它无关的类型(不是基类也不是派生类)。
假设我们有如下的两个类:
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 决定最终调用哪个类型的哪个方法。
结论:
- 一个对象是什么类型取决于它在初始化时,它左边的类型(编译时类型),而不是 new 后边所跟的类型(运行时类型)。
- GetType 返回运行时类型。
- 对象只具有编译时类型所具有的成员,如果强行去访问运行时类型的成员,会无法通过编译。
- 不过,可以在 IL 中使用 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 类构建了显式转换,则不能同时构建隐式转换(隐式转换不会成功)。
不过如果定义了隐式转换,则显式转换自动 生效(两种转换都会成功)。
原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/tech/pnotes/22375.html