checked 和 unchecked 基元类型操作

对基元类型执行的许多算术运算都可能造成溢出:

Byte b = 100;
b = (Byte) (b + 200); // b 现在包含 44(或者十六进制值 2C)

重要提示:执行上述算术运算时,第一步要求所有操作数都扩大为 32 位值(或者 64 位值,如果任何操作数需要超过 32 位来表示的话)。所以, b 200(这两个值都不超过 32 位) 首先转换成 32 位值,然后加到一起。结果是一个 32 位值(十进制 300,或十六进制 12C)。该值在存回变量 b 之前,必须转型为一个 Byte
C#不会隐式执行这个转型操作,这正是第二行代码需要强制转换为 Byte 的原因。
在大多数编程情形中,这种静悄悄发生的溢出是我们不希望的。如果没有检测到这种溢出,会导致应用程序行为失常。但在极少数编程情形中,比如计算一个哈希值或者校验和,这种溢出不仅是可以接受的,还是我们希望的。
不同语言以不同方式处理溢出。 C 和 C++不将溢出视为错误,并允许值回滚( wrap) 39;应用程序将“若无其事”地运行。相反, Microsoft Visual Basic 总是将溢出视为错误,并会在检测到溢出时抛出一个异常。
CLR 提供了一些特殊的 IL 指令,允许编译器选择它认为最恰当的行为。 CLR 有一个 add 指令,作用是将两个值加到一起,但不执行溢出检查。 CLR 还有一个 add.ovf 指令,作用也是将两个值加到一起,但会在发生溢出时抛出一个 System.OverflowException 异常。除了用于加法运算的这两个 IL 指令, CLR 还为减、乘和数据转换提供了类似的 IL 指令,分别是 sub/sub.ovfmul/mul.ovf conv/conv.ovf
C#允许程序员自己决定如何处理溢出。溢出检查默认是关闭的。也就是说,编译器在生成 IL 代码时,会自动使用加、减、乘以及转换指令的不含溢出检查的版本。这样的结果是代码能够更快地运行——但是,开发人员必须保证不会发生溢出,或者他们的代码能预见到这些溢出。
让 C#编译器控制溢出的一个办法是使用 /checked+编译器开关。这个开关指示编译器在生成代码时,使用加、减、乘和转换指令的溢出检查版本。这样生成的代码在执行时会稍慢一些,因为 CLR 会检查这些运算,判断是否会发生溢出。如果发生溢出, CLR 会抛出一个 OverflowException 异常。
除了全局性地打开或关闭溢出检查,程序员还可在代码的特定区域控制溢出检查。 C#通过提供 checkedunchecked 操作符来实现这种灵活性。 下面是一个使用了 unchecked 操作符的例子:

UInt32 invalid = unchecked((UInt32) (-1));  // OK

下例则使用了 checked 操作符:

Byte b = 100;
b = checked((Byte) (b + 200)); // 抛出 OverflowException 异常

在这个例子中, b 200 首先转换成 32 位值,然后加到一起,结果是 300。然后,因为显式转型的存在, 300 被转换成一个 Byte,这造成一个OverflowException 异常。如果 Byte 是在 checked 操作符的外部转
型的,则不会发生异常:

b = (Byte) checked(b + 200); // b 包含 44;不会抛出 OverflowException 异常

除了 checked unchecked 操作符, C#还支持 checked unchecked 语句,它们造成一个块中的所有表达式都进行或不进行溢出检查:

checked { // 开始一个 checked 块
 Byte b = 100;
 b = (Byte) (b + 200); // 该表达式会进行溢出检查
} // 结束一个 checked 

事实上,如果使用了一个 checked 语句块,就可以将+=操作符用于 Byte,从而稍微简化一下代码:

checked { // 开始一个 checked 块
 Byte b = 100;
 b += 200; // 该表达式会进行溢出检查
}

重要提示:由于 checked 操作符和 checked 语句唯一的作用就是决定生成哪一个版本的加、减、乘和数据转换 IL 指令,所以在一个 checked 操作符或者语句中调用一个方法,不会对该方法造成任何影响。

重要提示:
System.Decimal 类型是一个非常特殊的类型。虽然许多编程语言(包括 C#和 Visual Basic) 都将 Decimal视为一个基元类型,但 CLR 则不然。这意味着 CLR 没有相应的 IL 指令来决定如何处理一个 Decimal 值。在.NETFramework SDK 文档中查看 Decimal 类型可以看出,它提供了一系列 public static 方法,包括 AddSubtractMultiplyDivide 等。除此之外, Decimal 类型还为+, -, *, /等提供了操作符重载方法。
编译使用了 Decimal 值的程序时,编译器会生成代码来调用 Decimal 的成员,并通过这些成员来执行实际的运算。这意味着 Decimal 值的处理速度慢于 CLR 基元类型的值的处理速度。另外,由于没有相应的IL 指令来处理 Decimal 值,所以 checked unchecked 操作符、 语句以及编译器开关都失去了效用。 如果对
Decimal 值执行的运算是不安全的,肯定会抛出一个 OverflowException 异常。
类似地, System.Numerics.BigInteger 类型也在内部使用一个 UInt32 数组来表示一个任意大的整数,它的值没有上限和下限。因此,对 BigInteger 执行的运算永远不会造成 OverflowException 异常。然而,如果值 太 大 , 而 且 没 有 足 够 多 的 内 存 来 改 变 数 组 的 大 小 , 对 BigInteger 的 运 算 可 能 抛 出 一 个OutOfMemoryException 异常。

时间: 2024-08-27 21:21:29

checked 和 unchecked 基元类型操作的相关文章

第五章 基元类型、引用类型 和 值类型

1. 概述 本章讨论MS.NET Framework开发人员经常接触到的各种类型. 2. 名词解释 ① 基元类型:编译器直接支持的数据类型. ② 装箱:将一个值类型转换成一个引用类型. ③ 拆箱:获取已装箱的对象中的各个字段的地址. 3. 主要内容 3.1 编程语言的基元类型 作者建议开发人员开发中使用FCL类型名称,不要使用简化的基元类型名称.原因如下: ① 简化名称的映射容易混淆. ② 不同的编程语言,规则不一. ③ 影响代码的阅读. ④ 时间长了容易忘掉多语言环境的支持. checked

读经典——《CLR via C#》(Jeffrey Richter著) 笔记_基元类型(三)

[checked 和 unchecked 基元类型操作] 1.第一种使用方式 UInt32 invalid = unchecked((UInt32) (-1)); //OK,不会抛异常 Byte b = 100; b = checked((Byte) (b + 200));//抛出OverflowException异常 2.第二种使用方式 checked {//开始一个checked块 Byte b = 100; b = (Byte) (b + 200);//该表达式会进行溢出检查 }//结束一

5.1 编程语言的基元类型

编译器直接支持的数据类型成为基元类型(primitive type).基元类型直接映射到 Framework类库(FCL)中存在的类型. int a = 0; // Most convenient syntax System.Int32 a = 0; // Convenient syntax int a = new int(); // Inconvenient syntax System.Int32 a = new System.Int32(); // Most inconvenient syn

5.基元类型、引用类型和值类型

5.1 基远类型 编译器直接支持的数据类型称为基远类型(primitive type). 以下4行到吗生成完全相同的IL int a = 0; //最方便的语法 System.Int32 b = 0; //方便的语法 int c = new int(); //不方便的语法 System.Int32 d = new System.Int32(); //最不方便的语法 C#基元类型与对应的FCL类型 C#中的基元类型 FCL类型 是否与CLS兼容 描述 sbyte System.SByte N 有符

编程语言的基元类型

编译器直接支持的数据类型称为基元类型.基元类型直接映射到Framework类库(FCL)中存在的类型.比如在c#中int直接映射到曹衍涛 System.Int32类型. IL(IL可以指Intermediate Language,同MSIL(Microsoft Intermediate Language),是将.NET代码转化为机器语言的一个中间语言的缩写) CLS(common language Specification)公共语言规范 下为c#基元类型与对应FCL类型 c#基元类型 FCL类

[CLR via C#]基元类型

一.什么是基元类型 某些数据类型如此常用,以至于许多编译器允许代码以简化的语法来操纵它们.例如,可以使用以下语法来分配一个整数: System.Int32 a = new System.Int32(); 但你肯定不愿意使用这种语法来声明并初始化一个整数,因为实在是太繁琐了.幸运的是,包括C#在内的许多编译器都允许换用如下所示的语法: int a = 0; 这种语法不仅增加了代码的可读性,而且生成的IL代码与使用System.Int32时生成的IL代码是完全一样的.这种编译器直接支持的数据类型称为

05 基元类型、引用类型和值类型

基元类型 书上一开头就说了一个概念 编译器直接支持的数据类型称为基元类型(primitive type). 以下是基元类型. C# Primitive Typ FCL Type CLS-Compliant sbyte System.SBte NO byte System.Byte YES short System.Int16 YES ushort System.UInt16 NO int System.Int32 YES uint System.UInt32 NO long System.Int

C#中的基元类型、值类型和引用类型

C# 中的基元类型.值类型和引用类型 1. 基元类型(Primitive Type) 编译器直接支持的类型称为基元类型.基元类型可以直接映射到 FCL 中存在的类型.例如,int a = 10 中的 int 就是基元类型,其对应着 FCL 中的 System.Int32,上面的代码你完全可以写作System.Int32 a = 10,编译器将生成完全形同的 IL,也可以理解为 C# 编译器为源代码文件中添加了 using int = System.Int32. 1.1 基元类型的算术运算的溢出检

CLR VIA C#: 基元类型、 引用类型 和 值类型

一.基元类型 . 引用类型 和 值类型的区别: 1.基元类型(primitive type):编译器直接支持的数据类型: 基元类型 直接映射到 FCL 中存在的类型. C# 小写是基元类型,例如:string ,  大写是FCL类型,例如String,  基元类型直接映射到FCL类型,所以这两者之间没有区别,一模一样,不用纠结使用哪种方式了. 支持 直接使用FCL类型, 不使用基元类型, Float 对应 Single FCL类型: dynamic 对应  System.Object FCL类型