第14讲类型转换2
视频讲师:陈光老师
大家好,我们接着上节课的内容继续讲解类型的转换。上节课我们讲的是值类型的转换,这节课我们来讲一下引用类型的转换。CLR最重要的特性之一就是类型安全性,
开发人员经常需要将一个对象从一种类型转换成其他类型。CLR允许将一个对象强制转换成它的类型或者它的任何基类型。我们来看一个例子:
我们进行编译代码,看看结果如何。通过测试本代码没有报错,运行正常。在代码中入口函数Main方法中,f是一个水果类对象的实例,而水果类又是Apple类的父类。new Apple();这句new一个Apple它将返回Apple类一个实例并赋给f,这也符合我们前面所讲的CLR允许一个对象强制转换成它的任何的基类型,而且这个转换是一个隐式的转换。
我们再来看一下能否把一个父类Fruit赋给子类Apple呢?
我们再来看一下能否把一个父类Fruit赋给子类Apple呢?
我们进行编译代码,看看是否能通过编译,如图:
我们看,编译失败。这里提示无法将类型“Fruit”隐式转换为“Apple”。下面我们来看一张图,来讲解其原因。
Apple类继承至Fruit类,在执行Fruit f = new Apple();这句代码的时候,由于Fruit是一个引用类型,所以Fruit f会在堆栈里创建一个指针f,也就是说这个f是在堆栈里的。而new Apple它会在托管堆里面创建了一个Apple类的实例,由于Apple类继承至Fruit类,所以呢在Apple类里面也包含了Fruit类的内容。接下来把Apple类的实例赋给f这个指针。这个f指向的并不是Apple类,而是Apple类里包含的Fruit类。这样做是被允许的,正是由于Apple类包含了Fruit类里的所有内容,所以这种转型才能成功。
接下来我们来看Apple a = new Fruit();这一句。首先Apple a在堆栈里创建了一个a指针,它准备指向一个实例。接下来new Fruit()它会在托管堆里面创建一个Fruit类实例,然后想把Fruit这个实例赋给a这个指针,显然由于Fruit类并不包含Apple类里面所包含特有的内容,这个时候把它赋给一个Apple a这样做是不行的。所以呢,导致这个语句失败。
我们可以这样去理解,打个比方,可以说苹果是一种水果,但是呢不能说水果是一种苹果。假如CLR允许随便的转型,比如说一种类型随便转换成另外一种类型,这样呢就会违反安全性原则,将出现难以预料的结果。其中包括应用程序崩溃,以及造成安全漏洞。因为一种类型能轻松地伪装成另一种类型,类型伪装是造成许多安全漏洞的根源,并会破坏应用程序的稳定性和可靠性。因此,类型安全性是CLR一个极其重要的目标。
CLR总是知道一个对象的类型,要想确切知道一个对象的类型可以调用GetType()方法。我们来试验一下:

我们看,编译失败。这里提示无法将类型“Fruit”隐式转换为“Apple”。下面我们来看一张图,来讲解其原因。

Apple类继承至Fruit类,在执行Fruit f = new Apple();这句代码的时候,由于Fruit是一个引用类型,所以Fruit f会在堆栈里创建一个指针f,也就是说这个f是在堆栈里的。而new Apple它会在托管堆里面创建了一个Apple类的实例,由于Apple类继承至Fruit类,所以呢在Apple类里面也包含了Fruit类的内容。接下来把Apple类的实例赋给f这个指针。这个f指向的并不是Apple类,而是Apple类里包含的Fruit类。这样做是被允许的,正是由于Apple类包含了Fruit类里的所有内容,所以这种转型才能成功。
接下来我们来看Apple a = new Fruit();这一句。首先Apple a在堆栈里创建了一个a指针,它准备指向一个实例。接下来new Fruit()它会在托管堆里面创建一个Fruit类实例,然后想把Fruit这个实例赋给a这个指针,显然由于Fruit类并不包含Apple类里面所包含特有的内容,这个时候把它赋给一个Apple a这样做是不行的。所以呢,导致这个语句失败。
我们可以这样去理解,打个比方,可以说苹果是一种水果,但是呢不能说水果是一种苹果。假如CLR允许随便的转型,比如说一种类型随便转换成另外一种类型,这样呢就会违反安全性原则,将出现难以预料的结果。其中包括应用程序崩溃,以及造成安全漏洞。因为一种类型能轻松地伪装成另一种类型,类型伪装是造成许多安全漏洞的根源,并会破坏应用程序的稳定性和可靠性。因此,类型安全性是CLR一个极其重要的目标。
CLR总是知道一个对象的类型,要想确切知道一个对象的类型可以调用GetType()方法。我们来试验一下:
我们可以执行下,可以得出“Apple”这个结果。虽然f声明为一个Fruit类,但是呢实际上是一个Apple类。这个时候我们就会想到了,能不能用f直接去访问Apple类的公共字段i呢?我们来实验一下,直接打印
Console.WriteLine(f.i);编译执行出错,这样的运行是不行的。屏幕上打印出“Fruit并不包含i的定义”。而实际上f指向托管堆中的Apple类中的实例,而这个Apple类的实例呢它包含了公用的字段i,那我们怎么样才能访问到它呢?这个时候就要用到强制类型转换。Apple a = f;这样转换也是不行的,大家可以自己在机器上实验下,出错提示为“无法将类型Fruit隐式转换为Apple”,也就是说必须要显示的去转换。这样呢就必须要用到cast方式(就是在预转换对象前加括号里面写要转换的类型)。
这样就可以访问到i,并打印出i的值1。当然我们还能简化一下:
在C#语言中进行强制类型转换的另一种方式是is操作符。is操作符是检查一个对象是否兼容于指定的类型,并返回一个Boolean值,也就是True和False。注意,is操作符永远不会抛出异常,下面我们来对is操作符做一个演示:
我们来执行下,大家可以自己先判断下结果,我们来看下结果:
我们先看下代码,f is Fruit,这个操作返回的是一个Boolean值。我们来看结果,“f is Fruit”f是一个水果,这个没错,所以返回的是True。而“f is Apple”也就是水果是一个水果,这个呢肯定是错了,所以呢返回了False。下面一句“a is Fruit”,a是一个苹果类的实例,换句话说苹果是一个水果,它返回的是True。“a is Apple”苹果是一个苹果,它返回的也是True。通过这个例子我们可以看出来,is操作符永远不会抛出异常,而只会返回True和False,这样就增加了我们代码的安全性。好,我们把前面的代码用is操作符来改写一下:

我们先看下代码,f is Fruit,这个操作返回的是一个Boolean值。我们来看结果,“f is Fruit”f是一个水果,这个没错,所以返回的是True。而“f is Apple”也就是水果是一个水果,这个呢肯定是错了,所以呢返回了False。下面一句“a is Fruit”,a是一个苹果类的实例,换句话说苹果是一个水果,它返回的是True。“a is Apple”苹果是一个苹果,它返回的也是True。通过这个例子我们可以看出来,is操作符永远不会抛出异常,而只会返回True和False,这样就增加了我们代码的安全性。好,我们把前面的代码用is操作符来改写一下:
CLR的类型检查增强了安全性,但无疑也会对性能造成一定的影响。所以在C#中专门提供了一个as操作符,它的目的就是简化这种代码的写法。下面我们来看下代码:
这个返回的结果是和is操作符是一样的,但是as操作符只检验了一次对象的类型,也就是说使用as操作符呢比使用is操作符的速度更快,as操作符的操工作方式跟is操作符是一样的,它永远不会抛出异常,而只会返回一个对象的引用,或者是一个空值。
说了这么久,有些人可能会有疑问,类型转换这么麻烦有必要去使用它吗?下面我们来做一个例子,来掩饰类型转换的作用。我们来建立一个windows应用程序,点击Form窗体,然后我们在工具栏中拖几个控件放置于Form窗体上。如图:
Windows的窗体控件它们都有一个共同的特点,它们全都继承至Control的类,现在我们运用刚才我们所学的类型转换的知识,把窗体上所有控件的名称打印在lsitbox控件上。双击button5控件,进入代码编辑窗口进行代码编写:
说了这么久,有些人可能会有疑问,类型转换这么麻烦有必要去使用它吗?下面我们来做一个例子,来掩饰类型转换的作用。我们来建立一个windows应用程序,点击Form窗体,然后我们在工具栏中拖几个控件放置于Form窗体上。如图:

Windows的窗体控件它们都有一个共同的特点,它们全都继承至Control的类,现在我们运用刚才我们所学的类型转换的知识,把窗体上所有控件的名称打印在lsitbox控件上。双击button5控件,进入代码编辑窗口进行代码编写:
我们运行程序,点击button5按钮,如图:
我们看,所有控件的名字都打印到了listbox控件框里面,关闭窗体,我们回过头看下代码。 this .Controls窗体的Controls属性它返回的是一个集合,这个集合包含了窗体上所有的控件。当然,这些控件都有一个共同的特点,它们全部都继承至Control类,在这里我们声明了一个Control类c,并通过foreach语句遍历窗体的所控件,并打印出名称。这里就是一个类型转换典型的例子,Control类可以包含所有的窗体控件,从而呢使得它的实例c可以访问所有窗体控件的名称并打印出来。
这时候可能会有人会问,如果我想访问指定类型的控件比如说button,那该怎么做呢?没有问题,运用刚才所学的知识就可以了。

我们看,所有控件的名字都打印到了listbox控件框里面,关闭窗体,我们回过头看下代码。 this .Controls窗体的Controls属性它返回的是一个集合,这个集合包含了窗体上所有的控件。当然,这些控件都有一个共同的特点,它们全部都继承至Control类,在这里我们声明了一个Control类c,并通过foreach语句遍历窗体的所控件,并打印出名称。这里就是一个类型转换典型的例子,Control类可以包含所有的窗体控件,从而呢使得它的实例c可以访问所有窗体控件的名称并打印出来。
这时候可能会有人会问,如果我想访问指定类型的控件比如说button,那该怎么做呢?没有问题,运用刚才所学的知识就可以了。
我们运行程序,点击button5按钮,可以看到窗体上所有button控件的Text属性都被改了,显示都为kkkk。而非button控件则没有任何的改变,我们来看一下新加进去的代码,
Control c1 = c
as
Button;首先呢先把c强制转换为button类,并赋给c1,然后判断c1是否为空并改变它的Text属性,当然如果c1是一个label或者是一个TextBox,它们并不兼容于button,这样c1就为空,它就不会执行下面的代码。从而使得我们可以只操纵button类的控件。
强制类型的转换还有很多的用途,我们在这里只是举一个简单例子,让大家更好的理解。
好,今天的内容就讲到这里。
强制类型的转换还有很多的用途,我们在这里只是举一个简单例子,让大家更好的理解。
好,今天的内容就讲到这里。
由快乐乔巴听课摘写笔记