收藏: .NET中类型的转换

 .NET中,所有类型都继承自System.Object类型,因此可以很容易的获得对象的准确类型,方法是:GetType()方法。例如:  

  输出结果是:System.Int32

  对于.NET中的各种类型,存在着相互转换的关系,也就是将一种类型的对象赋值给另外一个对象。当然,这是有条件的,例如想将整型123转换为“123”的字符串,这是完全可以的,但是想从字符串转换为整型,就不一定了,例如字符串“1a3b”,显然是无法转换的,这就牵扯到了.NET中各种类型转换的机制:

C#中的类型转换机制有两种分类方法:
        一种是根据转换方式的不同进行划分,可以分为显式 (Explicit) 转换和隐式 (Implicit) 转换两种;
        另外一种是根据源类型和目标类型之间的关系进行划分,可以分为变换 (Conversion)、投射 (Cast)和装箱/拆箱 (Boxing/Unboxing)。 

下面先从转换方式(为显式 (Explicit) 转换和隐式 (Implicit) 转换)来介绍:

     无论显式转换还是隐式转换,都可能会失败。如果显式转换失败,会在运行时抛出异常(这个异常可能是InvalidCastException,也可能是 InvalidOperationException、OverflowException等具体异常);如果隐式转换失败,则会在编译时得到一个错误,指出不能进行隐式转换。
        最后,隐式转换也可以用显式转换替代,但显式转换不能用隐式转换替代。换句话说,可以用显式转换的地方,用隐式转换也没什么问题;但需要显式转换的地方,就一定不能用隐式转换。

1.隐式转换

  就是系统默认的、不需要加以特别声明也不用特殊的方法就可以进行的转换。在隐式转换过程中,编译器无需对转换进行详细检查就能够安全地执行转换。 

  比如从int类型转换到long类型就是一种隐式转换。隐式转换一般不会失败,转换过程中也不会导致信息丢失。

  比如:

int i=10;

long l=i;

  在本节,我们将讲解以下隐式转换的规则:

1.1 隐式数值转换 

  简单类型有许多隐式转换;bool 和 string没有隐式转换,但数值类型有一些隐式转换(记住,char存储的是数值,所以char被当作一个数值类型)。

隐式数值转换包括以下几种:

●从sbyte类型到short,int,long,float,double,或decimal类型。

●从byte类型到short,ushort,int,uint,long,ulong,float,double,或decimal类型。

●从short类型到int,long,float,double,或decimal类型。

●从ushort类型到int,uint,long,ulong,float,double,或decimal类型。

●从int类型到long,float,double,或decimal类型。

●从uint类型到long,ulong,float,double,或decimal类型。

●从long类型到float,double,或decimal类型。

●从ulong类型到float,double,或decimal类型。

●从char类型到ushort,int,uint,long,ulong,float,double,或decimal类型。

●从float类型到double类型。

  其中,从int,uint,或long到float以及从long到double的转换可能会导致精度下降,但决不会引起数量上的丢失。其它的隐式数值转换则不会有任何信息丢失。

  不需要记住上面的规则,因为很容易看出编译器可以执行哪些隐式转换。这些类型的隐式转换规则是:任何类型A,只要其取值范围完全包含在类型B的取值范围内,就可以隐式转换为类型B。   

  其原因是很简单的。如果要把一个值放在变量中,而该值超出了变量的取值范围,就会出问题。例如,short类型的变量可以存储0~32677的数字,而byte可以存储的最大值是255,所以如果要把一个short值转换为byte值,就会出问题。如果short包含的值在256~32767之间,该数值就不能放在byte中。

  因此隐式数值转换实际上就是从低精度的数值类型到高精度的数值类型的转换

  下面的程序给出了隐式数值转换的例子。

 

程序的输出将是:

x=16;

y=16;

y=65535;

z=65535;

如果我们在上面程序中的语句之后再加上一句:

y=y+1;

再重新编译程序时,编译器将会给出一条错误信息:

can not implictly convert type 'int' to type 'ushort'

这说明,从整数类型65536到无符号短整型y不存在隐式转换。

另外,这里涉及到四则运算过程中的自动类型转换问题,将回来另外的文章中进行介绍。

  从上面的10条我们可以看出,不存在到char类型的隐式转换,这意味着其它整型值不能自动转换为char类型。这一点我们需要特别注意。另外,浮点型(float)不能隐式地转化为decimal型。

1.2 隐式枚举转换(实际意义似乎不大)

    隐式枚举转换允许把十进制整数0转换成任何枚举类型,对应其它的整数则不存在这种隐式转换。还是让我们用例子来说明。

 程序的输出是:0

 但是如果我们把语句day=0改写为day=1,编译器就会给出错误:

Can not implictly convert type 'int' type 'enum'

1.3 隐式引用转换

    隐式引用转换是指一类引用类型之间的转换,这种转换总是可以成功,因此不需要在运行时进行任何检查。隐式引用转换包括以下几类: 

●从任何引用类型到对象类型的转换

●从类类型s到类类型t的转换,其中s是t的派生类。

●从类类型s到接口类型t的转换,其中类s实现了接口t。

●从接口类型s到接口类型t的转换,其中t是s的父接口。

●从任何数组类型到System.Array的转换。

●从任何代表类型到System.Delegate的转换。

●从任何数据类型或代表类型到System.ICLoneable的转换。

●从空类型(null)到任何引用类型的转换。

●从元素类型为Ts的数组类型S向元素类型为Tt的数组类型T转换,这种转换需要满足下列所有三个条件:

1.S和T只有元素类型不同,而维数相同 

2. Ts和Tt都是引用类型
3.存在从Ts到Tt的显示引用转换

    比如,下面的程序无法通过编译,因为数组的元素类型是值类型,C#中不存在这样的隐式转换。

程序清单: 

而下面这段程序则是正确的:

程序清单: 

 1.4装箱转换
    装箱转换允许将“值类型”隐式转换为“引用类型”。

    此例将整数变量 i 通过装箱转换为对象 o。此例显示对象保留了内容的原始副本,即 123。

 

2.显式转换

    又叫强制类型转换。与隐式转换正好相反,显式转换需要用户明确地指定转换的类型。比如下面的例子把一个类型显式转换为:    

例如:

long l=5000;

int i=(int)l;

拆箱转换就是一种显式转换。这里我们还将讲解以下转换的规则:

●显式数值转换

●显式枚举转换

●显式引用转换

    显式转换可以发生在表达式的计算过程中。它并不是总能成功,而且常常可能引起信息丢失。

    显式转换包括所有的隐式转换,也就是说把任何系统允许的隐式转换写成显式转换的形式都是允许的,如:

int i=10;

long l=(long)i;

2.1 显式数值转换

显式数值转换是指当不存在相应的隐式转换时,从一种数字类型到另一种数字类型的转换。包括:

●从sbyte到byte,ushort,uint,ulong,或char。

●从byte到sbyte或char。

●从short到sbyte,byte,ushort,uint,ulong,或char。

●从ushort到sbyte,byte,short,或char。

●从int到sbyte,byte,short,ushort,uint,ulong,或char。

●从uint到sbyte,byte,short,ushort,int,或char。

●从long到sbyte,byte,short,ushort,int,uint,ulong,或char。

●从ulong到sbyte,byte,short,ushort,int,uint,long,或char。

●从char到sbyte,byte,或short。

●从float到sbyte,byte,short,ushort,int,uint,long,ulong,char,或decimal。

●从double到sbyte,byte,short,ushort,int,uint,long,ulong,char,float,或decimal。

●从decimal到sbyte,byte,short,ushort,int,uint,long,ulong,char,float,或double。

这种类型转换有可能丢失信息或导致异常抛出,转换按照下列规则进行:

●对于从一种整型到另一种整型的转换,编译器将针对转换进行溢出检测,如果没有发生溢出,转换成功,否则抛出一个OverflowException异

常。这种检测还与编译器中是否设定了checked选项有关。

●对于从float,double,或decimal到整型的转换,源变量的值通过舍入到最接近的整型值作为转换的结果。如果这个整型值超出了目标类型的值域,则将抛出一个OverflowException异常。

●对于从double到float的转换,double值通过舍入取最接近的float值。如果这个值太小,结果将变成正0或负0;如果这个值太大,将变成正无穷或负无穷。如果原double值是Nan,则转换结果也是NaN。

●对于从float或double到decimal的转换,源值将转换成小数形式并通过舍入取到小数点后28位(如果有必要的话)。如果源值太小,则结果为0;如果太大以致不能用小数表示,或是无穷和NaN,则将抛出InvalidCastException异常。

●对于从decimal到float或double的转换,小数的值通过舍入取最接近的值。这种转换可能会丢失精度,但不会引起异常。

程序清单:

 

这个例子把一个int类型转换成为long类型,输出结果是:

(int)9223372036854775807=-1

这是因为发生了溢出,从而在显式类型转换时导致了信息丢失。

2.2 显式枚举转换

显式枚举转换包括以下内容:

●从sbye,byte,short,ushort,int,uint,long,ulong,char,float,double,或decimal到任何枚举类型。

●从任何枚举类型到sbyte,byte,short,ushort,int,uint,long,ulong,char,float,double,或decimal。

●从任何枚举类型到任何其它枚举类型。

显式枚举转换是这样进行的:它实际上是枚举类型的元素类型与相应类型之间的隐式或显式转换。比如,有一个元素类型为int的枚举类型E,

则当执行从E到byte的显式枚举转换时,实际上作的是从int到byte的显式数字转换;当执行从byte到E的显式枚举转换时,实际上是执行byte到int的隐式数字转换。

比如:

程序清单:

程序的输出是:3 

2.3 显式引用转换

显式引用转换包括:

●从对象到任何引用类型

●从类类型S到类类型T,其中S是T的基类。

●从基类型S到接口类型T,其中S不是密封类,而且没有实现T。

●从接口类型S到类类型T,其中T不是密封类,而且没有实现S。

●从接口类型S到接口类型T,其中S不是T的子接口。

从元素类型为Ts的数组类型S到元素类型为Tt的数组类型T的转换,这种转换需要同事满足下列三个条件:

1.S和T只有元素的数据类型不同,而维数相同。

2.Ts和Tt都是引用类型。

3.存在从Ts到Tt的显式引用转换。

●从System.Array到数组类型。

●从System.Delegate到代表类型。

●从System.ICloneable到数组类型或代表类型。 

2.4拆箱

取消装箱转换:取消装箱转换允许将引用类型显式转换为值类型。 

    取消装箱操作包括以下两个步骤:首先检查该对象实例是否是某个给定的值类型的装了箱的值,然后将值从实例中复制出来。
示例:

    下面的示例阐释无效取消装箱的情况,即错误的取消装箱如何导致 InvalidCastException。通过使用 try 和 catch,发生错误时会显示错误信息。

 

  1. using System;  
  2. public class UnboxingTest   
  3. {  
  4.    public static void Main()   
  5.    {  
  6.       int intI = 123;  
  7.       object o = intI; //装箱  
  8.       try   
  9.       {  
  10.          int intJ = (short) o; //取消装箱无效,short不是装了箱的值类型. 检查该对象实例是否是某个给定的值类型的装了箱的值  
  11.          Console.WriteLine("Unboxing OK.");  
  12.       }  
  13.       catch (InvalidCastException e)   
  14.       {  
  15.          Console.WriteLine("{0} Error: Incorrect unboxing.",e);  
  16.       }  
  17.    }  
  18. }  
  

    显式引用转换发生在引用类型之间,需要在运行时检测以确保正确。那么究竟如何保证转换时候不出现不可预料的问题呢?为了避免在进行强制类型转换时由于目标类型无效,而导致运行时抛出InvalidCastException异常,C#提供了IS与AS操作符进行类型判断与“安全”的强制类型转换,用这两个操作符,可以提供对类型兼容性的判断,从而使得类型转换控制在安全的范畴,提供了灵活的类型转换控制。这就是下一节将讲解的IS和AS操作符的作用。

另外,为了确保显式引用转换的正常执行,要求源变量的值必须是null或者它所引用的对象的类型可以被隐式引用转换为目标类型。否则显式引用转换失败,将抛出一个InvalidCastException异常。

不论隐式还是显式引用转换,虽然可能会改变引用值的类型,却不会改变值本身。

3用户自定义转换
      所有的用户自定义转换都是静态的,要使用static关键字。
      用户自定义转换分显示和隐示,它们用implicit(隐式转换)或 explicit(显示转换)关键字声明。
      static 访问修辞符 转换修辞符 operator 转换类型(参数)。

示例:

 

  1. using System;  
  2. struct Number  
  3. {  
  4.        private int value;  
  5.        public Number(int value)   
  6.        {   
  7.               this.value = value;   
  8.        }  
  9.        //用户自定义整型到Number型的隐式转换  
  10.        static public implicit operator Number(int value)   
  11.        {  
  12.               return new Number(value);  
  13.        }  
  14.        // 用户自定义从Number型到整型的显示转换  
  15.        static public explicit operator int(Number n)  
  16.        {  
  17.               return n.value;  
  18.        }  
  19.        //用户自定义从Number类型到string类型的隐式转换  
  20.        static public implicit operator string(Number n)  
  21.        {  
  22.               return n.ToString();  
  23.        }   
  24. }  
  25.   
  26. class Test  
  27. {  
  28.       static public void Main()  
  29.        {  
  30.               Number n;  
  31.               n= 10;       
  32.               Console.WriteLine((int)n);  
  33.               //隐式转换到string  
  34.               Console.WriteLine(n);     
  35.       }  
  36. }  
 

 

3.IS和AS操作符

    为了避免在进行强制类型转换时由于目标类型无效,而导致运行时抛出InvalidCastException异常,C#提供了IS与AS操作符进行类型判断与“安全”的强制类型转换。

is 运算符用于检查对象的类型是否与给定类型兼容(对象是该类型,或是派生于该类型)。

 

 is的规则如下:

 

  • 检查对象类型的兼容性,并返回结果,true或者false;

  • 不会抛出异常;

  • 如果对象为null,则返回值永远为false。

 

代码如下:

  

  1. class Program {  
  2.          
  3.        static void Main(string[] args)  
  4.      {  
  5.           Object studentObj = new Student();  
  6.   
  7.          Object studentProgram = new Program();  
  8.            
  9.           //使用IS去检测studentObj引用的对象是否兼容于Student类型  
  10.           //IS永远不会发生异常,当studentObj变量的引用为null时则永远返回false;  
  11.           if (studentObj is Student)  
  12.          {  
  13.               Student studentTemp = (Student)studentObj;  
  14.   
  15.           }  
  16.           if (studentProgram is Student)  
  17.           {  
  18.               Console.WriteLine(studentProgram.ToString());  
  19.           }  
  20.   
  21.       }  
  22.   }  
 

由代码可以看出,CLR实际会检查两次对象的类型,IS操作符首先检测变量的引用是否兼容于指定的类型,如果返回TRUE,则CLR在进行强制类型转换行进行第二次类型的检测,即studentObj对象是否引用一个Student类型。

由于强制类型转换CLR必须首先判断变更引用对象的实际类型,然后CLR必须去遍历继承层次结构,用变量引用类型的每个基类型去核对目标类型。这肯定会对性能造成一定的影响。好在C#提供了AS操作符来弥补这一缺陷。

as 运算符用于执行引用类型的显式类型转换。 如果要转换的类型与指定类型兼容,转换就会成功;如果类型不兼容,则返回null。
       表达式as 类型
as 运算符类似于类型转换,所不同的是,当转换失败时,as 运算符将返回null,而不是引发异常。

 as的规则如下:

  • 检查对象类型的兼容性,并返回结果,如果不兼容就返回null;

  • 不会抛出异常;

  • 如果结果判断为空,则强制执行类型转换将抛出NullReferenceException异常。

 

代码如下:

 

 

  1. class Program {  
  2.           
  3.         static void Main(string[] args)  
  4.         {  
  5.             Object studentObj = new Student();  
  6.   
  7.             Object studentProgram = new Program();  
  8.   
  9.             //CLR检测studentObj引用对象类型兼容于Student,as将直接返回同一个对象的一个非null引用  
  10.             //即studentObj保存的对在托管堆上Student对象的引用  
  11.             Student s1 = studentObj as Student;  
  12.             //如果CLR检测studentProgram引用对象类型不兼容目标类型,即不能进行强制类型转换,则返回一个null,永远不会抛出异常  
  13.             Student s2 = studentProgram as Student;  
  14.   
  15.             if (s1 != null)  
  16.             {  
  17.                 s1.Work();  
  18.             }  
  19.   
  20.             if (s2 != null)  
  21.             {  
  22.                 s2.Work();  
  23.             }  
  24.         }  
  25.     }  
  26.    
 

 由此可以看出,在对s1 s2变量进行应用时,还需要进行非null的判断,从而避免出面NullReferenceException的异常。 

显然无论从性能还是代码的的直观上来说,AS都IS更胜一筹,然而实际应用中,还是根据环境再做取决了。

4.关键字checked 和 unchecked

  当需要显式地把一种数据类型转换为另一种数据类型时,最好知道是否有数据丢失了。如果不知道这些,就会产生严重的问题。

byte destinationVar;
short sourceVar = 281;
destinationVar = (byte)sourceVar;
Console.WriteLine("sourceVar val: {0}", sourceVar);
Console.WriteLine("destinationVar val: {0}", destinationVar);

 

 

结果如下:

sourceVar val: 281
destinationVar val: 25

  会发生什么?如果看看这两个数字的二进制表示,以及可以存储在byte中的最大值255:

281 = 100011001
25 = 000011001
255 = 011111111

  可以看出,源数据的最左边一位丢失了。这会导致一个问题:数据是何时的?

  一种方式是简单地检查源变量的值,把它与目标变量的取值范围进行比较。还有另一个技术,迫使系统特  别注意运行期间的转换。在把一个值放在一个变量中时,如果该值过大,不能放在该类型的变量中,就会导致溢出,这就需要检查。

  关键字checked 和 unchecked,称为表达式的溢出检查环境。以下述方式使用这两个关键字:

checked(expression)
unchecked(expression)

  下面对上一个示例进行溢出检查:

byte destinationVar;
short sourceVar = 281;
destinationVar = checked((byte)sourceVar);
Console.WriteLine("sourceVar val: {0}", sourceVar);
Console.WriteLine("destinationVar val: {0}", destinationVar);

  在执行这段代码时,程序会崩溃,并显示如图所示的错误信息(在项目中编译这段代码)。

  但是,在这段代码中,如果用unchecked替代checked,就会得到与以前一样的结果,不会出现错误。这与前面的默认操作是一样的。

  除了这两个关键字以外,还可以配置应用程序,让这种类型的表达式都包含checked关键字,除非表达式明确使用unchecked关键字(换言之,可以改变溢出检查的默认设置)。为此,应修改项目的属性:在VS中右击Solution Explorer窗口中的项目,选择属性选项。单击窗口左边的生成,打开生成设置,如图所示。

  要修改的属性是一个Advanced设置,所以单击Advanced按钮。在打开的对话框中,选中Check for arithmetic overflow/underflow选项,如图所示。在默认情况下,这个设置是禁用的,激活它可以进行如上所述的checked操作。

5.C#中常用的三种显示转换方法

(typename)valuename,是通用方法;

Convert类提供了灵活的类型转换封装;

Parse方法,适用于向数字类型的转换。

例如,(int),Int32.Parse() 和 Convert.toInt32() 。那么三种方法有什么区别呢?

  int 关键字表示一种整型,是32位的,它的 .NET Framework 类型为 System.Int32。

  (int)表示使用显式强制转换,是一种类型转换。当我们从 int 类型到 long、float、double 或decimal 类型,可以使用隐式转换,但是当我们从 long 类型到 int  类型转换就需要使用显式强制转换,否则会产生编译错误。也就是说,这个转换方式,编译时会确保是存在显示转换关系的,如果不存在,就会提示无法转换。

  Int32.Parse()表示将数字的字符串转换为32 位有符号整数,属于内容转换。只要是字符串,都可以转换过去,至于是否正确,运行过程中会提示是否出错:
  我们一种常见的方法:public static int Parse(string)。
  如果 string 为空,则抛出 ArgumentNullException 异常;
  如果 string 格式不正确,则抛出 FormatException 异常;
  如果 string 的值小于 MinValue 或大于 MaxValue 的数字,则抛出 OverflowException 异常。

    Convert.ToInt32() 则可以将多种类型(包括 object  引用类型)的值转换为 int  类型,因为它有许多重载版本:
    public static int ToInt32(object);
    public static int ToInt32(bool);
    public static int ToInt32(byte);
    public static int ToInt32(char);
    public static int ToInt32(decimal);
    public static int ToInt32(double);
    public static int ToInt32(short);
    public static int ToInt32(long);
    public static int ToInt32(sbyte);
    public static int ToInt32(string);
    ......
    (int)和Int32.Parse(),Convert.ToInt32()三者的应用举几个例子:   

 

 

 

    例子一:

    long longType = 100;
    int intType  = longType;       // 错误,需要使用显式强制转换
    int intType = (int)longType; //正确,使用了显式强制转换

    例子二:

    string stringType = "12345"; 
    int intType = (int)stringType;                //错误,string 类型不能直接转换为 int  类型 
    int intType = Int32.Parse(stringType);   //正确

    例子三:

    long longType = 100;
    string stringType = "12345";
    object objectType = "54321";
    int intType = Convert.ToInt32(longType);       //正确
    int intType = Convert.ToInt32(stringType);     //正确
    int intType = Convert.ToInt32(objectType);    //正确

    例子四:

    double doubleType = Int32.MaxValue + 1.011; 
    int intType = (int)doubleType;                                //虽然运行正确,但是得出错误结果
    int intType = Convert.ToInt32(doubleType)            //抛出 OverflowException 异常

   (int)和Int32.Parse(),Convert.ToInt32()三者的区别:

    第一个在对long 类型或是浮点型到int 类型的显式强制转换中使用,但是如果被转换的数值大于 Int32.MaxValue 或小于 Int32.MinValue,那么则会得到一个错误的结果。

    第二个在符合数字格式的 string 到 int  类型转换过程中使用,并可以对错误的 string 数字格式的抛出相应的异常。

    第三个则可以将多种类型的值转换为 int 类型,也可以对错误的数值抛出相应的异常。

    无论进行什么类型的数值转换,数值的精度问题都是我们必须考虑的。
--------------------------------------------------------
使用Convert.ToInt32()把一个char型转换成int时,是把这个char的ascci码给过去而不是数字
如:
char c = '1';
int i;
i = Convert.ToInt32(c); //char需注意的事项
//这时i的值为49,是1的ascii码

想得到1,可以使用string类型,
如:
string str= "1";
int i;
i = int.Parse(str);
i = Convert.ToInt32(str);
//这时i的值为1,而不是1的ascii码

----------------------------------------------------

  隐式转换各显式转换要求是同类型的,就是说两种数据类型必须兼容,隐式转换是向上转型(相当是子类转父类),而强制类型转换则是向下转型(相当是父类转子类),就好像Double型的可以包含int型一样。
而强制转换可以是不是同一种类型,(如同class1与class2同级别的类一样),两都进行内容上的解析。Convert.ToInt32与int.Parse都是强制转换,int.Parse是转换String为int(这种情况很多,可能进行了些优化,也可能只是为了方便,处理逻辑一样), 而Convert.ToInt32是转换继承自Object的对象为int的(18种重载). 比如一个object对象,你想把它转换为int,用int.Parse就不可以,要用Convert.ToInt32。

6.从变换 (Conversion)、投射 (Cast)和装箱/拆箱角度的类型转换

    以上从隐式转换和显式转换角度介绍了类型的转换,应该说是告诉我们如何转换。本节将深入这些转化涉及到哪些原理,也就是知其然,更要知其所以然,这样记忆起来才不会机械化。

根据参与类型转换的两种类型(源类型和目标类型)的关系不同,可以将类型转换分成三种;即:
如果源类型和目标类型一个是值类型一个是引用类型,则称为装箱/拆箱,也就是1.4和2.4转换方式;
如果源类型和目标类型之间存在着直接或间接继承,则称为投射,也就是1.2和2.2的转换方式;
如果源类型和目标类型不具备上述两种关系,如两种简单值类型或兄弟/邻居类型(有着共同的祖先类)之间,则称为普通类型转换(或称“变换”),也就是1.1和2.1的转换方式。 
变换
变换是最普通的一种类型转换,通常发生在:
简单(内置)值类型之间;
重载了类型转换运算符的类型之间;

          简单值类型就是int、long、float、double等类型。在这些类型之间转换的时候,如果不会发生精度损失,则可以使用隐式转换,否则必须使用显式转换。换言之,从较短的类型向较长的类型转换,可以用隐式转换;从较长的向较短的转换,必须用显式转换(“长短”指的是:int长4字节,long长8 字节,所以int比long短。)
 

投射

         当源类型和目标类型具有直接或间接的继承关系时,发生的类型转换属于投射。如果将子类型的一个对象转换成祖先类型,则称向上投射 (Upcast)。反之,将祖先类型的对象转换为子孙类型,则称向下投射 (Downcast)。

        上图中,CA到CB、CA到CC、CA到CD、CB到CC的转换属于向下投射,而CB到CA、CC到CA、CD到CA、CC到CB的转换属于向上投射。 
        向上投射可以使用隐式转换,但向下投射必须使用显示转换。这是因为当发生继承关系时,子类将具有父类所有的成员(尽管父类中的private成员在子类中无法访问,但子类确实拥有这些成员),因此子类对象向父类转换时可以确保不会丢失成员,而如果父类对象向子类转换,不一定保证对象具备子类特有的成员。
        需要注意的是,如果两个类型位于同一继承树中,但没有直接或间接继承关系(通常称这样的类型为“兄弟类型”或“邻居类型”,如上图中的CB和CD、CC和CD),是不允许发生任何转换的,除非重载了类型转换运算符。 
         下面解释一下为什么将这种类型的转换称为投射。 
         假设类CA中定义了一个属性、一个事件和一个方法,现在用方块表示属性、用三角表示事件、用圆圈表示方法,则可以将CA绘制为下面的图形。 

 

         假设类CB继承自CA,并定义了额外的一个属性和一个方法,则可以将CB绘制为下面的图形。

         现在考虑用一个和CA形状一致的模板罩在CB类型的一个对象上,并透过模板去看这个对象(如下图所示),则可以看到对象的一个视图,并且从其中只能看到CA类中定义的成员。

这个情形符合这种转换。如果将视线想象成光线,则形成了一个光线的投射。

装箱/拆箱

        当发生类型转换时,如果源类型和目标类型一个是值类型而另一个是引用类型时,将发生装箱/拆箱转换。 
        从值类型向引用类型的转换称为装箱,而从引用类型向值类型转换称为拆箱。 
        装箱时,会在堆中创建一个新的对象(作为一个“箱子”),并将值类型值复制到这个对象中(把这个值“装起来”);而拆箱时,只需将对象中的值复制到栈上。
       在装箱/拆箱中,何时使用显式(隐式)转换是不确定的,取决于类型中重载了哪种转换运算符。

7.总结归纳

  上面虽然罗嗦了很多,大概可以总结为以下内容:

  类型转换分为隐式转换和显式转换,他们的转换方式都对应了变换、投射、和装箱/拆箱三种类型。

  当直接将一种类型赋值给另外一个类型,就是隐式转换。系统会在编译时候进行判断,能否进行隐式转换,如果提示出错,就说明无法隐式转换,如果没有提示,这个隐式转换是成立的,并且不会出现错误;接下来就是利用上面三种方式进行转换了。

  当采取显式转换时,利用诸如(int)a,convert和parse方法吧。这时候有四种情况,一种是编译就出错,因为根本无法强制转换,另一种是可以编译通过,但是运行时候出错,还一种,就是运行也成功了,但是由于截断等原因,结果是不正确的了,最后一种当然就是转换成功喽。其中对于(int)a,编译时候会确保是存在显示转换关系的,如果不存在,就会提示无法转换。如果可以转换,那不会出错,但是结果不一定对,因为可能截断了部分内容;对于Int32.Parse()表示将数字的字符串转换为32 位有符号整数,属于内容转换。只要是字符串,都可以转换过去,至于是否正确,运行过程中会提示是否出错,包括空字符串错,格式不对,或者是溢出。

8.目前还不懂的问题

(1)

这个是一个子类转换为父类的隐式转换,可以运行,问题是,为什么将子类对象赋值给父类后,父类的类型都变为子类类型了,但是同时还可以访问a的变量d?

(2)

对于“,从int,uint,或long到float以及从long到double的转换可能会导致精度下降”,不太理解。

等待着进一步的学习和解决吧!

  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5.   
  6. namespace ConsoleApplication1  
  7. {  
  8.     class Program  
  9.     {  
  10.         static void Main(string[] args)  
  11.         {  
  12.             partnerclass a=new partnerclass();  
  13.             childclass b=new childclass();  
  14.             a = b;            
  15.             Console.Write(a.GetType().ToString());  
  16.             Console.Read();  
  17.         }  
  18.     }  
  19.     class partnerclass  
  20.     {  
  21.         public int d = 4;  
  22.     }  
  23.     class childclass:partnerclass  
  24.     {  
  25.         public int c = 7;  
  26.         public void test()  
  27.         { }  
  28.     }  
  29. }  

  1. using System;  
  2.   
  3. enum Weekday{  
  4.    Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday  
  5. };  
  6.   
  7. class Test  
  8.   
  9. {  
  10. public static void Main(){  
  11.      Weekday day;  
  12.      day=(Weekday)3;  
  13.      Console.WriteLine(day);  
  14. }  
  15. }  
  

  1. using system;  
  2.   
  3. class Test  
  4. {  
  5. static void Main(){  
  6.       long longValue=Int64.MaxValue;  
  7.       int intValue=(int)longValue;  
  8.       Console.WriteLine("(int){0}={1}",longValue,intValue);  
  9.     }  
  10. }  
  

  1. using System;  
  2. class TestBoxing   
  3. {  
  4.    public static void Main()   
  5.    {  
  6.       int i = 123;  
  7.       object o = i; // 隐式的装箱  
  8.       i = 456;       // 改变变量i的值  
  9.       Console.WriteLine("The value-type value = {0}", i);       //456  
  10.       Console.WriteLine("The object-type value = {0}", o);     //123 是i的复制值  
  11.    }  
  12. }  
 

  1. using System;  
  2. class Class1  
  3. {}  
  4. class Class2:Class1  
  5. {}  
  6. class Test  
  7. {  
  8.    public static void Main(){  
  9.      Class1[] class1_arr=new Class1[10];  
  10.      class2[] class2_arr=new Class2[10];  
  11.      class1_arr=class2_arr;  
  12.     }  
  13. }  
  

  1. using System;  
  2. class Test  
  3. {  
  4. public static void Main(){  
  5.     float[] float_arr=new float[10];  
  6.     int[] int_arr=new int[10];  
  7.     float_arr=int_arr;  
  8.    }  
  9. }  
  

  1. using System;  
  2. enum Weekday{  
  3. Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday  
  4. };  
  5. class Test  
  6. {  
  7. public static void Main(){  
  8.      Weekday day;  
  9.      day=0;  
  10.      Console.WriteLine(day);  
  11.      }  
  12. }  
  

  1. using System;  
  2. class Test  
  3. {  
  4. public static void Main()  
  5. {  
  6.     byte x=16;  
  7.     Console.WriteLine("x={0}",x);  
  8.     ushort y=x;  
  9.     Console.WriteLine("y={0}",y);  
  10.     y=65535;  
  11.     Console.WriteLine("y={0}",y);  
  12.     float z=y;  
  13.     Console.WriteLine("z={0}",z);  
  14. }  
  15. }  
  

  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5.   
  6. namespace ConsoleApplication1  
  7. {  
  8.     class Program  
  9.     {  
  10.         static void Main(string[] args)  
  11.         {  
  12.        int myInt= 1;   
  13.          
  14.        Console.Write(myInt.GetType().ToString());  
  15.   
  16.        Console.Read();  
  17.         }  
  18.     }  
  19. }  
原文地址:https://www.cnblogs.com/mistor/p/2094353.html