要理解什么是转换,让我们先从声明两个不同类型的变量,然后把一个变量(源)的值赋值给另一个变量(目标)的简单示例开始讲起。在赋值前,源的值必须转换成目标类型的值。
例:
short var1=5; sbyte var2=10; ... var2=(sbyte)var1;
有些类型的转换不会丢失数据或精度。
例:使用零扩展把8位的10转换为16位的10
对于有符号类型的转换而言,额外的高位用源表达式的符号位进行填充。
例:10和-10的转换
如果需要把长类型转换为短类型,目标类型也许无法在不损失数据的情况下提供源值。
例:把1365的ushort转换为byte,数据丢失,因为目标类型最大值只能是255.最终字节中的后8位保留,值为85。
对于预定义类型,C#会自动完成类型转换,但只是针对哪些从源类型到目标类型不会发生数据丢失的情况。
对于会发生数据丢失的情况,必须使用显式转换–强制转换表达式。
强制类型转换格式:
目标类型 ↓ (sbyte)var1; ↑ 源表达式
如果我们使用强制转换表达式,就要承担执行操作可能引起的丢失数据的结果。
例:两个ushort转byte
ushort sh=10; byte sb=(byte)sh; Console.WriteLine("sb: {0} =0x{0:X}",sb); sh=1365; sb=(byte)sh; Console.WriteLine("sb: {0} =0x{0:X}",sb);
有很多标准的、预定义的用于数字和引用类型的转换。下图演示了这些不同的转换类型。
任何数字都可以转换为其他数字类型。
一些转换是隐式的,而另外一些转换必须是显式的。
我们已经知道了,显式转换可能会丢失数据并且不能在目标类型中同等地表示源值。对于整数类型,C#给我们提供了选择运行时是否应该在进行类型转换时检测结果溢出的能力。这将通过checked运算符和checked语句来实现。
checked和unchecked运算符控制表达式的溢出检测上下文。表达式放置在一对圆括号内并且不能是一个方法。
checked(表达式) unchecked(表达式)
例:
ushort sh=2000; byte sb; sb=unchecked((byte)sh); Console.WriteLine("sb:{0}",sb); sb=checked((byte)sh); Console.WriteLine("sb:{0}",sb);
checked和unchecked运算符用于圆括号内的单个表达式。而checked和unchecked语句执行相同的功能,但控制的是一块代码中的所有转换,而不是单个表达式。
例:checked语句影响一段代码
ushort sh=2000; byte sb; unchecked { sb=(byte)sh; Console.WriteLine("sb:{0}",sb); checked { sb=(byte)sh; Console.WriteLine("sb:{0}",sb); } }
显式数字转换,可能丢失数据。因此,作为一个程序员,知道发生数据丢失时转换会如何处理很重要。
在本节中,我们来看看各种显式数字转换。
在checked的情况下,如果转换会丢失数据,操作会抛出一个OverflowException异常。
在unchecked的情况下,丢失的位不会发出警告。
当把浮点类型转换为整数类型时,值会舍掉小数截断为最接近的整数。
如果截断后的值不在目标类型的范围内:
从decimal转换到整数类型时,如果结果值不在目标类型的范围内,CLR会抛出OverflowException异常。
float类型的值占32位,而double类型的值占64位。double类型的值被舍入到最接近的float类型的值。
decimal转float类型总会成功。然而可能会损失精度。
我们已经知道引用类型对象由内存中的两部分组成:引用和数据。
例:引用转换示例
class A {public int Field1;} class B:A{public int Field2;} class Program { static void Main() { B myVar1=new B(); A myVar2=(A)myVar1; Console.WriteLine("{0}",myVar2.Field1); Console.WriteLine("{0}",myVar2.Field2);//编译错误 } }
与自动隐式数字转换类型,还有隐式引用转换
委托可以隐式转换成.NET BCL类和接口。ArrayS数组,其中的元素是是Ts类型,可以隐式转换成:
显式引用转换是从一个普通类型到一个更精确类型的引用转换。
如果转换的类型不受限制,很可能会导致我们很容易地尝试引用在内存中实际不存在的类成员。然而,编译器确实允许这样的转换。只不过,系统运行时遇到它们会抛出异常。
例:错误的显式引用转换
class A {public int Field1;} class B:A{public int Field2;} class Program { static void Main() { B myVar1=new B(); A myVar2=(A)myVar1; } }
在运行时能成功进行(不抛出InvalidCastException异常)的显示转换有3种情况。
第一种情况:显式转换是没必要的。即语言已经为我们进行了隐式转换。例如,从衍生类到基类的转换总是隐式转换。
class A{} class B:A{} ... B myVar1=new B(); A myVar2=(A)myVar1;
第二种情况:源引用是null。
class A{} class B:A{} ... A myVar1=null; B myVar2=(B)myVar1;
第三种情况:由源引用指向的实际数据可以被安全地进行隐式转换。
B myVar1=new B(); A myVar2=myVar1; //将myVar1隐式转换为A类型 B myVar3=(B)myVar2;
包括值类型在内的所有C#类型都派生自object类型。然而,值类型是高效轻量的类型,因为默认情况下在堆上不包括它们的对象组件。然而,如果需要对象组件,我们可以使用装箱(boxing)。装箱是一种隐式转换,它接受值类型的值,根据这个值在堆上创建一个完整的引用类型对象并返回对象引用。
例:装箱示例
int i=12; object oi=unll; oi=i;
在装箱后,该值有两份副本–原始值类型和引用类型副本,每个都可以独立操作。
int i=10; object oi=i; Console.WriteLine("i:{0},io:{1}",i,oi); i=12; oi=15; Console.WriteLine("i:{0},io:{1}",i,oi);
下图演示了装箱转换。任何值类型ValueTypeS都可以被隐式转换为object类型、System.ValueTpye或InterfaceT(如果ValueTypeS实现了InterfaceT)。
拆箱(unboxing)是把装箱后的对象转换回值类型的过程。
class Program { static void Main() { int i=10; object oi=i; int j=(int)oi; Console.WriteLine("i:{0},oi:{1},j:{2}",i,oi,j); } }
尝试将一个值拆箱为非原始类型会抛出一个InvalidCastException异常。
除了标准转换,我们还可以为类和结构定义隐式和显式转换。
用户自定义转换语法:
必须的 运算符 关键字 源 ↓ ↓ ↓ ↓ public static implicit operator TargetType(SourceType Identifier) { ... return ObjectOfTargetType; }
例:自定义转换示例
public static implicit operator int (Person p) { return p.Age; }
自定义转换有些很重要的约束:
class Person { public string Name; public int Age; public Person(string name,int age) { Name=name; Age=age; } public static implicit operator int(Person p) { return p.Age; } public static implicit operator Person(int i) { return new Person("Nemo",i);//"Nemo" is Latin for "No one". } } class Program { static void Main() { Person bill=new Person("bill",25); int age=bill; Console.WriteLine("Person Info:{0},{1}",bill.Name,age); Person anon=35; Console.WriteLine("Person Info:{0},{1}",anon.Name,anon.Age); } }
如果使用explicit运算符来定义转换,需要使用强制转换表达式来进行转换。
... public static explicit operator int(Person p) { return p.Age; } ... static void Main() { ... int age=(int)bill;//必须强制转换表达式 ... }
到目前为止讨论的用户自定义转换都是在一步完成。
但是,用户自定义转换在完成转换中最多可以有3个步骤。
在这个链中不能有一个以上的用户自定义转换。
class Employee:Person{} class Person { public string Name; public int Age; public static implicit operator int(Person p) { return p.Age; } } class Program { static void Main() { var bill=new Employee(); bill.Name="William"; bill.Age=25; float fVar=bill; Console.WriteLine("Person Info:{0},{1}",bill.Name,fVar); } }
之前说过,有些转换会失败,并会在运行时抛出InvalidCastException异常。
我们可以使用is运算符来检查转换是否会成功完成,从而避免盲目尝试转换。
is运算符语法如下,Expr是源表达式:
Expr is TargetType//返回bool值
例:is运算符示例
class Employee:Person{} class Person { public string Name="Anonymous"; public int Age=25; } class Program { static void Main() { var bill=new Employee(); Person p; if(bill is Person) { p=bill; Console.WriteLine("Person Info:{0},{1}",p.Name,p.Age); } } }
is运算符只可用于引用转换、装箱、拆箱,不能用于用户自定义转换。
as运算符和强制转换运算符类似,只是它不抛出异常。如果转换失败,它返回null而不是抛出异常。
as运算符语法如下:
Expr as TargetType//返回引用
由于as运算符返回引用表达式,它可以用作赋值操作中的源。
例:as运算符示例
class Employee:Person{} class Person { public string Name="Anonymous"; public int Age=25; } class Program { static void Main() { var bill=new Employee(); Person p; p=bill as Person; if(p!=null) { Console.WriteLine("Person Info:{0},{1}",p.Name,p.Age); } } }
和is运算符类型,as运算符只能用于引用转换和装箱转换。它不能用于用户自定义转换或到值类型的转换。
原文:http://www.cnblogs.com/moonache/p/6369047.html