转换

什么是转换


要理解什么是转换,让我们先从声明两个不同类型的变量,然后把一个变量(源)的值赋值给另一个变量(目标)的简单示例开始讲起。在赋值前,源的值必须转换成目标类型的值。


  • 转换(conversion)是接受一个类型的值并使用它作为另一个类型的等价值的过程
  • 转换后的值应和源值一样的,但其类型为目标类型

例:

  • var1是short类型的16位符号整数,初始值为5。var2是byte类型的8位有符号整数,初始值为10
  • 第三行代码把var1赋给var2。由于它们类型不同,在赋值前,var1的值必须先转换为与var2类型相同的值类型。这将通过强制转换表达式来实现
  • var1的类型和值都没有改变。
short var1=;
sbyte var2=;
...
var2=(sbyte)var1;

隐式转换


有些类型的转换不会丢失数据或精度。

  • 语言会自动做这些转换,这叫做隐式转换
  • 从位数更少的源转换为位数更多的目标类型时,目标中多出来的位需要用0或1填充
  • 当从更小的无符号类型转换为更大的无符号类型时,目标类型多出来的最高位都以0进行填充,这叫做零扩展(zero extension)

例:使用零扩展把8位的10转换为16位的10


对于有符号类型的转换而言,额外的高位用源表达式的符号位进行填充。

  • 这样就维持了被转换的值的正确符号和大小
  • 这叫做符号扩展(sign extension)

例:10和-10的转换


显式转换和强制转换


如果需要把长类型转换为短类型,目标类型也许无法在不损失数据的情况下提供源值。
例:把1365的ushort转换为byte,数据丢失,因为目标类型最大值只能是255.最终字节中的后8位保留,值为85。


强制转换

对于预定义类型,C#会自动完成类型转换,但只是针对哪些从源类型到目标类型不会发生数据丢失的情况。
对于会发生数据丢失的情况,必须使用显式转换–强制转换表达式。
强制类型转换格式:

目标类型

(sbyte)var1;

源表达式

如果我们使用强制转换表达式,就要承担执行操作可能引起的丢失数据的结果。
例:两个ushort转byte

ushort sh=;
byte sb=(byte)sh;
Console.WriteLine("sb: {0} =0x{0:X}",sb);
sh=;
sb=(byte)sh;
Console.WriteLine("sb: {0} =0x{0:X}",sb);

转换的类型


有很多标准的、预定义的用于数字和引用类型的转换。下图演示了这些不同的转换类型。


  • 除了标准转换,还可以为自定义类型定义隐式转换和显式转换
  • 还有一个预定义转换类型,叫做装箱,可以将任何值类型转换为:
    • object类型
    • System.ValueType类型
  • 拆箱可以将一个装箱的值转换为原始类型

数字的转换


任何数字都可以转换为其他数字类型。
一些转换是隐式的,而另外一些转换必须是显式的。


隐式数字转换
  • 如果有路径,从源类型到目标类型可以按照箭头进行隐式转换
  • 任何从源类型到目标类型的箭头方向上没有路径的数字转换都必须是显式转换

溢出检测上下文

我们已经知道了,显式转换可能会丢失数据并且不能在目标类型中同等地表示源值。对于整数类型,C#给我们提供了选择运行时是否应该在进行类型转换时检测结果溢出的能力。这将通过checked运算符和checked语句来实现。

  • 代码片段是否被检查称作溢出检测上下文

    • 如果我们指定一个表达式或一段代码为checked,CLR会在转换产生溢出时抛出一个OverflowException异常
    • 如果代码不是checked,转换会继续而不管是否产生溢出
  • 默认的溢出检测上下文是不检查
1.checked和unchecked运算符

checked和unchecked运算符控制表达式的溢出检测上下文。表达式放置在一对圆括号内并且不能是一个方法。

checked(表达式)
unchecked(表达式)

例:

  • 在unchecked上下文中,会忽略溢出,结果值是208
  • 在checked上下文中,抛出OverflowException异常
ushort sh=;
byte sb;
sb=unchecked((byte)sh);
Console.WriteLine("sb:{0}",sb);
sb=checked((byte)sh);
Console.WriteLine("sb:{0}",sb);

2.checked语句和unchecked语句

checked和unchecked运算符用于圆括号内的单个表达式。而checked和unchecked语句执行相同的功能,但控制的是一块代码中的所有转换,而不是单个表达式。

  • checked语句和unchecked语句可以被嵌套在任意层次

例:checked语句影响一段代码

ushort sh=;
byte sb;
unchecked
{
sb=(byte)sh;
Console.WriteLine("sb:{0}",sb);
checked
{
sb=(byte)sh;
Console.WriteLine("sb:{0}",sb);
}
}
显式数字转换

显式数字转换,可能丢失数据。因此,作为一个程序员,知道发生数据丢失时转换会如何处理很重要。
在本节中,我们来看看各种显式数字转换。


1.整数类型到整数类型

在checked的情况下,如果转换会丢失数据,操作会抛出一个OverflowException异常。
在unchecked的情况下,丢失的位不会发出警告。


2.float或double转到整数类型

当把浮点类型转换为整数类型时,值会舍掉小数截断为最接近的整数。
如果截断后的值不在目标类型的范围内:

  • checked下,CLR会抛出OverflowException异常
  • unchecked下,则C#不定义它的值应该是什么


3.decimal到整数类型

从decimal转换到整数类型时,如果结果值不在目标类型的范围内,CLR会抛出OverflowException异常。


4.double到float

float类型的值占32位,而double类型的值占64位。double类型的值被舍入到最接近的float类型的值。

  • 如果值太小而不能用float表示,那么值会被设置为正或负0
  • 如果值太大而不能用float表示,那么值会被设置为正无穷大或负无穷大

5.float或double转decimal
  • 如果值太小而不能用decimal类型表示,那么值会被设置为0
  • 如果值太大,那么CLR会抛出OverflowException异常

6.decimal到float或double

decimal转float类型总会成功。然而可能会损失精度。


引用转换


我们已经知道引用类型对象由内存中的两部分组成:引用和数据。

  • 由引用保存的那部分信息是它指向的数据类型
  • 引用转换接受源引用并返回一个指向堆中同一位置的引用,但是把引用“标记”为其他类型

例:引用转换示例

  • myVar1,它引用的对象看上去是B类型–其实就是
  • myVar2,同样的对象看上去像类型A的对象
    • 即使它实际指向B类型的对象,它也不能看到B扩展到A的部分,因此不能看到Field2
    • 第二个WriteLine语句因此产生编译错误
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);//编译错误
}
}

隐式引用转换

与自动隐式数字转换类似,还有隐式引用转换

  • 所有引用类型可以被隐式转换为object类型
  • 任何类型可以隐式转换到它继承的接口
  • 类可以隐式转换到:
    • 它继承链中的任何类
    • 它实现的任何接口

委托可以隐式转换成.NET BCL类和接口。ArrayS数组,其中的元素是是Ts类型,可以隐式转换成:

  • .NET BCL类和接口
  • 另一个数组ArrayT,其中的元素是Tt类型(如果满足下面的所有条件)
    • 两个数组维度相同
    • Ts、Tt都是引用类型
    • 在Ts、Tt中存在隐式转换

显式引用转换

显式引用转换是从一个普通类型到一个更精确类型的引用转换。

  • 显式转换包括:

    • 从object到任何引用类型的转换
    • 从父类到子类的转换
  • 倒转图16-18、16-19的箭头方向

如果转换的类型不受限制,很可能会导致我们很容易地尝试引用在内存中实际不存在的类成员。然而,编译器确实允许这样的转换。只不过,系统运行时遇到它们会抛出异常。

例:错误的显式引用转换

  • 如果myVar2尝试访问Field2,它会尝试访问对象中“B部分”的字段(它不在内存中),这会导致内存错误
  • 运行时会捕获这种错误的强制转换并抛出InvalidCastException异常(它不会导致编译错误)
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=;
object oi=null;
oi=i;

装箱是创建副本

在装箱后,该值有两份副本–原始值类型和引用类型副本,每个都可以独立操作。

int i=;
object oi=i;
Console.WriteLine("i:{0},io:{1}",i,oi);
i=;
oi=;
Console.WriteLine("i:{0},io:{1}",i,oi);

装箱转换

下图演示了装箱转换。任何值类型ValueTypeS都可以被隐式转换为object类型、System.ValueTpye或InterfaceT(如果ValueTypeS实现了InterfaceT)。


拆箱转换


拆箱(unboxing)是把装箱后的对象转换回值类型的过程。

  • 拆箱是显式转换
  • 系统在拆箱时执行如下步骤:
    • 它检测到要拆箱的对象实际是ValueTypeT的装箱值
    • 它把对象的值复制到变量
class Program
{
static void Main()
{
int i=;
object oi=i;
int j=(int)oi;
Console.WriteLine("i:{0},oi:{1},j:{2}",i,oi,j);
}
}

尝试将一个值拆箱为非原始类型会抛出一个InvalidCastException异常。

用户自定义转换


除了标准转换,我们还可以为类和结构定义隐式和显式转换。

用户自定义转换语法:

  • 除了implict和explicit关键字外,隐式和显式转换的声明语法一样
  • 需要public和static修饰符
    必须的              运算符     关键字          源
↓ ↓ ↓ ↓
public static implicit operator TargetType(SourceType Identifier)
{
...
return ObjectOfTargetType;
}

例:自定义转换示例

public static implicit operator int (Person p)
{
return p.Age;
}
用户自定义转换的约束

自定义转换有些很重要的约束:

  • 只可以为类和结构定义用户自定义转换
  • 不能重定义标准隐式转换或显式转换
  • 对于源类型S和目标类型T,如下命题为真
    • S和T必须是不同类型
    • S和T不能通过继承关联
    • S和T都不能是接口类型或object类型
    • 转换运算符必须是S或T的成员
用户自定义转换示例
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",);
int age=bill;
Console.WriteLine("Person Info:{0},{1}",bill.Name,age);
Person anon=;
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=;
float fVar=bill;
Console.WriteLine("Person Info:{0},{1}",bill.Name,fVar);
}
}

is运算符


之前说过,有些转换会失败,并会在运行时抛出InvalidCastException异常。
我们可以使用is运算符来检查转换是否会成功完成,从而避免盲目尝试转换。

is运算符语法如下,Expr是源表达式:

Expr is TargetType//返回bool值

例:is运算符示例

class Employee:Person{}
class Person
{
public string Name="Anonymous";
public int Age=;
}
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运算符


as运算符和强制转换运算符类似,只是它不抛出异常。如果转换失败,它返回null而不是抛出异常。

as运算符语法如下:

  • Expr是源表达式
  • TargetType是目标类型,它必须是引用类型
Expr as TargetType//返回引用

由于as运算符返回引用表达式,它可以用作赋值操作中的源。

例:as运算符示例

class Employee:Person{}
class Person
{
public string Name="Anonymous";
public int Age=;
}
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运算符只能用于引用转换和装箱转换。它不能用于用户自定义转换或到值类型的转换。

C#图解教程 第十六章 转换的更多相关文章

  1. python 教程 第十六章、 正则表达式

    第十六章. 正则表达式 1)    匹配多个表达式 记号  re1|re2 说明  匹配正则表达式re1或re2 举例  foo|bar  匹配  foo, bar 记号  {N} 说明  匹配前面出 ...

  2. Flask 教程 第十六章:全文搜索

    本文翻译自The Flask Mega-Tutorial Part XVI: Full-Text Search 这是Flask Mega-Tutorial系列的第十六部分,我将在其中为Microblo ...

  3. C#图解教程 第二十五章 其他主题

    其他主题 概述字符串使用 StringBuilder类把字符串解析为数据值关于可空类型的更多内容 为可空类型赋值使用空接合运算符使用可空用户自定义类型 Main 方法文档注释 插入文档注释使用其他XM ...

  4. C#图解教程 第十二章 数组

    数组 数组 定义重要细节 数组的类型数组是对象一维数组和矩形数组实例化一维数组或矩形数组访问数组元素初始化数组 显式初始化一维数组显式初始化矩形数组快捷语法隐式类型数组综合内容 交错数组 声明交错数组 ...

  5. C#图解教程 第十四章 事件

    事件 发布者和订阅者源代码组件概览声明事件订阅事件触发事件标准事件的用法 通过扩展EventArgs来传递数据移除事件处理程序 事件访问器 事件 发布者和订阅者 很多程序都有一个共同的需求,既当一个特 ...

  6. C#图解教程 第十五章 接口

    接口 什么是接口 使用IComparable接口的示例 声明接口实现接口 简单接口示例 接口是引用类型接口和as运算符实现多个接口实现具有重复成员的接口多个接口的引用派生成员作为实现显式接口成员实现 ...

  7. C#图解教程 第十九章 LINQ

    LINQ 什么是LINQLINQ提供程序 匿名类型 方法语法和查询语法查询变量查询表达式的结构 from子句join子句什么是联结查询主体中的from-let-where片段 from子句let子句w ...

  8. C#图解教程 第二十四章 反射和特性

    反射和特性 元数据和反射Type 类获取Type对象什么是特性应用特性预定义的保留的特性 Obsolete(废弃)特性Conditional特性调用者信息特性DebuggerStepThrough 特 ...

  9. C#图解教程 第二十二章 异常

    异常 什么是异常try语句 处理异常 异常类catch 子句使用特定catch子句的示例catch子句段finally块为异常寻找处理程序更进一步搜索 一般法则搜索调用栈的示例 抛出异常不带异常对象的 ...

随机推荐

  1. BZOJ 1923: [Sdoi2010]外星千足虫 [高斯消元XOR]

    1923: [Sdoi2010]外星千足虫 对于 100%的数据,满足 N≤1,000,M≤2,000. 裸高斯消元解异或方程组 给定方程顺序要求用从上到下最少的方程,那么找主元时记录一下最远找到哪个 ...

  2. VUE2.0 elemenui-ui 2.0.X 封装 省市区三级

    1. 效果图 2. 版本依赖  vue 2.X , elementui  2.0.11  使用element ui  <el-form>标签 3. 源码  components/CityL ...

  3. ThinkPHP删除栏目(单)

    当我们做一些网站项目的时候,都会遇到这样一类问题,删除一个栏目,而这个栏目又不是最底层栏目,也就是说,被删除的栏目拥有子栏目,这时,我们执行删除该栏目的命令,就需要将该栏目及其子栏目一并删除,因为我们 ...

  4. 「POJ2505」A multiplication game [博弈论]

    题目链接:http://poj.org/problem?id=2505 题目大意: 两个人轮流玩游戏,Stan先手,数字 p从1开始,Stan乘以一个2-9的数,然后Ollie再乘以一个2-9的数,直 ...

  5. MySQL对sum()字段 进行条件筛选,使用having,不能用where

    显示每个地区的总人口数和总面积.仅显示那些面积超过1000000的地区. SELECT region, SUM(population), SUM(area) FROM bbc GROUP BY reg ...

  6. python 两个list 求交集,并集,差集

    def diff(listA,listB): #求交集的两种方式 retA = [i for i in listA if i in listB] retB = list(set(listA).inte ...

  7. linux、windows系统间传输文件

    日常工作中经常涉及到系统间的文件传输,下面就简单说一下常用的方法   linux--windows      工具:winscp.SecureCRT.Zmodem(sz, rz)   linux--l ...

  8. Activiti中的各个service的作用

    各个Service的作用: RepositoryService 管理流程定义 RuntimeService 执行管理,包括启动.推进.删除流程实例等操作 TaskService 任务管理 Histor ...

  9. Yii2框架ACF(AccessControl Filter)的使用

    AccessControl其实也就是 yii\filters\AccessControl Filter, 我们下面简写为 ACF 作为描述. ACF,访问控制过滤器,适用于简单的验证,面对的对象便是控 ...

  10. SSRF漏洞总结

    SSRF漏洞:(服务端请求伪造)是一种由攻击者构造形成由服务端发起请求的一个安全漏洞.一般情况下,SSRF攻击的目标是从外网无法访问的内部系统.(正是因为它是由服务端发起的,所以它能够请求到与它相连而 ...