C#类型转换和GetType方法

在运行时,可以通过 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 决定最终调用哪个类型的哪个方法。

结论:

  • 一个对象是什么类型取决于它在初始化时,它左边的类型(编译时类型),而不是 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/22375.html

(0)
上一篇 2021年7月20日
下一篇 2021年7月20日

相关推荐

发表回复

登录后才能评论