您现在的位置是:首页 > 正文

《果壳中的C# C# 5.0 权威指南》 (01-08章) - 学习笔记

2024-04-01 00:06:52阅读 4

《果壳中的C# C# 5.0 权威指南》

========== ========== ==========
[作者] (美) Joseph Albahari (美) Ben Albahari
[译者] (中) 陈昇 管学理 曾少宁 杨庆川
[出版] 中国水利水电出版社
[版次] 2013年08月 第1版
[印次] 2013年08月 第1次 印刷
[定价] 118.00元
========== ========== ==========

【前言】

C# 5.0 是微软旗舰编程语言的第4次重大升级。

C# 5.0 及相关 Framework 的新特性已经被标注清楚,因此也可以将本书作为 C# 4.0 参考书使用。

【第01章】

(P001)

C# 在面向对象方面的特性包括:

1. 统一的类型系统 —— C# 中的基础构建块是一种被称为类型的数据与函数的封装单元。C# 有一个统一的类型系统,其中所有类型最终都共享一个公共的基类。这意味着所有的类型,不管它们是表示业务对象,或者像数字等基本类型,都共享相同的基本功能集;

2. 类与接口 —— 在纯粹的的面向对象泛型中,唯一的类型就是类。但是 C# 中还有其他几种类型,其中一种是接口。接口与类相似,但它只是某种类型的定义,而不是实现。在需要用多继承时,它是非常有用的;

3. 方法、属性与事件 —— 在纯粹的面向对象泛型中,所有函数都是方法。在 C# 中,方法只是一种函数成员,也包含一些属性和事件以及其他组成部分。属性是封装了一部分对象状态的函数成员。事件是简化对象状态变化处理的函数成员;

C# 首先是一种类型安全的语言,这意味着类型只能够通过它们定义的协议进行交互,从而保证每一种类型的内部一致性。

C# 支持静态类型化,这意味着这种语言会在编译时执行静态类型安全性检查。

(P002)

静态类型化能够在程序运行之前去除大量的错误。

C# 允许部分代码通过新的 dynamic 关键字来动态指定类型。然而,C# 在大多数情况下仍然是一种静态类型化的语言。

C# 之所以被称为一种强类型语言,是因为它的类型规则是非常严格的。

C# 依靠运行时环境来执行自动的内存管理。

C# 并没有去除指针 : 它只是使大多数编程任务不需要使用指针。对于性能至关重要的热点和互操作性方面,还是可以使用指针,但是只允许在显式标记为不安全的代码块中使用。

C# 依赖于一个运行时环境,它包括许多特性,如自动内存管理和异常处理。

(P003)

.NET Framework 由名为 Common Language Runtime (CLR) 的运行时环境和大量的程序库组成。这些程序库由核心库和应用库组成。

CLR 是执行托管代码的运行时环境。C# 是几种将源代码编译为托管语言之一。托管代码会被打包成程序集,它可以是可执行文件或程序库的形式,包括类型信息或元数据。

托管代码用 Intermediate Language 或 IL 表示。

Red Gate 的 .Net Reflector 是一个重要的分析程序集内容的工具 (可以将它作为反编译器使用) 。

CLR 是无数运行时服务的主机。这些服务包括内存管理、程序库加载和安全性服务。

CLR 是与语言无关的,它允许开发人员用多种语言开发应用程序。

(P004)

.NET Framework 由只支持基于所有 Windows 平台或 Web 的应用程序的程序库组成。

C# 5.0 还实现了 Windows Runtime (WinRT) 库的互操作。

WinRT 是一个扩展接口和运行时环境,它可以用面向对象和与语言无关的方式访问库。Windows 8 带有这个运行时库,属于 Microsoft 组件对象模型或 COM 的扩展版本。

Windows 8 带有一组非托管 WinRT 库,它是通过 Microsoft 应用商店交付的支持触摸屏的 Metro 风格应用程序框架。作为 WinRT ,这些程序库不仅可以通过 C# 和 VB 访问,也可以通过 C++ 和 JavaScript 访问。

WinRT 与普通 COM 的区别是,WinRT 的程序库支持多种语言,包括 C# 、 VB 、 C++ 和 JavaScript,所以每一种语言 (几乎) 都将 WinRT 类型视为自己的专属类型。

(P005)

C# 5.0 两个较大的新特性是通过两个关键字 (async 和 await) 支持异步功能 (asynchronous function)。

C# 4.0 增加的新特性有 : 动态绑定、可选参数和命名参数、用泛型接口和代理实现类型变化、改进 COM 互操作性。

C# 3.0 增加的这些特性主要集中在语言集成查询功能上 (Language Integrated Query,简称 LINQ) 。

C# 3.0 中用于支持 LINQ 的新特性还包括隐式类型化局部变量 (Var) 、匿名类型、对象构造器、 Lambda 表达式、扩展方法、查询表达式和表达式树。

(P006)

C# 3.0 也增加了自动化和局部方法。

【第02章】

(P007)

在 C# 中语句按顺序执行。每个语句都以分号 (;) 结尾。

C# 语句按顺序执行,以分号 (;) 结尾。

(P008)

方法是执行一系列语句的行为。这些语句叫做语句块。语句块由一对大括号中的 0 个或多个语句组成。

编写可调用低级函数的高级函数可以简化程序。

方法可以通过参数来接收调用者输入的数据,并通过返回类型给调用者返回输出数据。

C# 把 Main 方法作为程序的默认执行入口。 Main 方法也可以返回一个整数 (而不是 void) ,从而为程序执行的环境返回一个值。 Main 方法也可以接受一个字符串数组作为参数 (数组中包含可传递给可执行内容的任何参数) 。

数组代表某种特定类型,固定数量的元素的集合。数组由元素类型和它后面的方括号指定。

类由函数成员和数据成员组成,形成面向对象的构建块。

(P009)

在程序的最外层,类型被组织到命名空间中。

.NET Framework 的组织方式为嵌套的命名空间。

using 指令仅仅是为了方便,也可以用 “命名空间 + 类型名” 这种完全限定名称来引用某种类型。

C# 编译器把一系列 .cs 扩展名的源代码文件编译成程序集。

程序集是 .NET 中的最小打包和部署单元。

一个程序集可以是一个应用程序,或者是一个库。

一个普通的控制台程序或 Windows 应用程序是一个 .exe 文件,包含一个 Main 方法。

一个库是一个 .dll 文件,它相当于一个没有入口的 .exe 文件。

库是用来被应用程序或其他的库调用 (引用) 的。

.NET Framework 就是一组库。

C# 编译器名称是 csc.exe。可以使用像 Visual Studio 这样的 IDE 编译 C# 程序,也可以在命令行中手动调用 csc 命令编译 C# 程序。

(P010)

标识符是程序员为类、方法、变量等选择的名字。

标识符必须是一个完整的词、它是由字母和下划线开头的 Unicode 字符组成的。

C# 标识符是区分大小写的。

通常约定参数、局部变量和私有变量字段应该由小写字母开头,而其他类型的标识符则应该由大写字母开头。

关键字是编译器保留的名称,不能把它们用作标识符。

如果用关键字作为标识符,可以在关键字前面加上 @ 前缀。

@ 并不是标识符的一部分。

@ 前缀在调用其他有不同关键字的 .NET 语言编写的库时非常有用。

(P011)

点号 (.) 表示某个对象的成员 (或数字的小数点)。

括号在声明或调用方法时使用,空括号在方法没有参数时使用。

等号则用于赋值操作。

C# 提供了两种方式的注释 : 单行注释和多行注释。

单行注释由双斜线开始,到本行结束为止。

多行注释由 /* 开始,由 */ 结束。

变量代表它的值可以改变,而常量则表示它的值不可以更改。

(P012)

C# 中所有值都是一种类型的实例。一个值或一个变量所包含的一组可能值均由其类型决定。

预定义类型是指那些由编译器特别支持的类型。

预定义类型 bool 只有两种值 : true 和 false 。 bool 类型通常与 if 语句一起用于条件分支。

在 C# 中,预定义类型 (也称为内建类型) 被当做 C# 关键字。在 .NET Framework 中的 System 命名空间下包含了很多并不是预定义类型的重要类型。

正如我们能使用简单函数来构建复杂函数一样,也可以使用基本类型来构建复杂类型。

(P013)

类型包含数据成员和函数成员。

C# 的一个优点就是预定义类型和自定义类型只有很少的不同。

实例化某种类型即可创建数据。

预定义类型可以简单地通过字面值进行实例化。

new 运算符用于创建自定义类型的实例。

使用 new 运算符后会立刻实例化一个对象,对象的构造方法会在初始化时被调用。

构造方法像方法一样被定义,不同的是方法名和返回类型简化成它所属的类型名。

由类型的实例操作的数据成员和函数成员被称为实例成员。

在默认情况下,成员就是实例成员。

(P014)

那些不是由类型的实例操作而是由类型本身操作的数据成员和函数成员必须标记为 static 。

public 关键字将成员公开给其他类。

把成员标记为 public 就是在说 : “这就是我想让其他类型看到的,其他的都是我自己私有的” 。

用面向对象语言,我们称之为公有 (public) 成员封装了类中的私有 (private) 成员。

在 C# 中,兼容类型的实例可以相互转换。

转换始终会根据一个已经存在的值创建一个新的值。

转换可以是隐式或显式。

隐式转换自动发生,而显式转换需要 cast 关键字。

long 容量是 int 的两倍。

(P015)

隐式转换只有在下列条件都满足时才被允许 :

1. 编译器能保证转换总是成功;

2. 没有信息在转换过程中丢失;

只有在满足下列条件时才需要显式转换:

1. 编译器不能保证转换总是能成功;

2. 信息在转换过程中有可能丢失;

C# 还支持引用转换,装箱转换和自定义转换。

对于自定义转换,编译器并没有强制遵守上面的规则,所以设计不好的类型有可能在转换时出现预想不到的结果。

所有 C# 类型可以分成以下几类 : 值类型、引用类型、泛型类型、指针类型。

值类型包含大多数内建类型 (具体包括所有的数值类型、 char 类型和 bool 类型) 以及自定义 struct 类型和 enum 类型。

引用类型包括所有的类、数据、委托和接口类型。

值类型和引用类型最根本的不同是它们在内存中的处理方式。

值类型变量或常量的内容仅仅是一个值。

可以通过 struct 关键字定义一个自定义值类型。

对值类型实例的赋值操作总是会复制这些实例。

将一个非常大的 long 转换成 double 类型时,有可能造成精度丢失。

(P016)

引用类型比值类型复杂,它由两部分组成 : 对象和对象的引用。

引用类型变量或常量的内容是对一个包含值的对象的引用。

(P017)

一个引用可以赋值为字面值 null,这表示它不指向任何对象;

相对的,值类型通常不能有 null 值;

C# 中也有一种代表类型值为 null 的结构,叫做可空 (nullable) 类型。

(P018)

值类型实例正好占用需要存储其字段的内存。

从技术上说,CLR 用整数倍字段的大小来分配内存地址。

引用类型要求为引用和对象单独分配存储空间。

对象占用了和字段一样的字节数,再加上额外的管理开销。

每一个对象的引用都需要额外的 4 或 8 字节,这取决于 .NET 运行时是运行在 32 位平台还是 64 位平台上。

C# 中的预定义类型又称框架类型,它们都在 System 命名空间下。

在 CLR 中,除了 decimal 之外的一系列预定义值类型被认为是基本类型。之所以将其称为基本类型,是因为它们在编译过的代码中被指令直接支持。因此它们通常被翻译成底层处理器直接支持的指令。

(P019)

System.IntPtr 和 System.UIntPtr 类型也是基本类型。

在整数类型中,int 和 long 是最基本的类型, C# 和运行时都支持它们。其他的整数类型通常用于实现互操作性或存储空间使用效率非常重要的情况。

在实数类型中,float 和 double 被称为浮点类型,通常用于科学计算。

decimal 类型通常用于要求10位精度以上的数值计算和高精度的金融计算。

整型字面值可使用小数或十六进制小数标记,十六进制小数用 0x 前缀表示。

实数字面值可使用小数和指数标记。

从技术上说,decimal 也是一种浮点类型,但是在 C# 语言规范中通常不将其认为是浮点类型。

(P020)

默认情况下,编译器认为数值字面值或者是 double 类型或者是整数类型 :

1. 如果这个字面值包含小数点或指数符号 (E),那么它被认为是 double ;

2. 否则,这个字面值的类型就是下列能满足这个字面值的第一个类型 : int 、 uint 、 long 和 ulong ;

数值后缀显式地定义了一个字面值的类型。后缀可以是下列小写或大写字母 : F (float) 、 D (double) 、 M (decimal) 、 U (uint) 、 L (long) 、 UL (ulong) 。

后缀 U 、 L 和 UL 很少需要,因为 uint 、 long 和 ulong 总是可以表示 int 或从 int 隐式转换过来的类型。

从技术上讲,后缀 D 是多余的,因为所有带小数点的字面值都被认为是 double 类型。总是可以给一个数字类型加上小数点。

后缀 F 和 M 是最有用的,它在指定 float 或 decimal 字面值时使用。

double 是无法隐式转换成 float 的,同样的规则也适用于 decimal 字面值。

整型转换在目标类型能表示源类型所有可能的值时是隐式转换,否则需要显式转换。

(P021)

float 能隐式转换成 double ,因为 double 能表示所有可能的 float 的值。反过来则必须是显式转换。

所有的整数类型可以隐式转换成浮点数,反过来则必须是显式转换。

将浮点数转换成整数时,小数点后的数值将被截去,而不会四舍五入。

静态类 System.Convert 提供了在不同值类型之间转换的四舍五入方法。

把一个大的整数类型隐式转换成浮点类型会保留整数部分,但是有时会丢失精度。这是因为浮点类型总是有比整数类型更大的数值,但是可能只有更少的精度。

所有的整数类型都能隐式转换成 decimal 类型,因为小数类型能表示所有可能的整数值。其他所有的数值类型转换成小数类型或从小数类型转换到数值类型必须是显式转换。

算术运算符 (+ 、 - 、 * 、 / 、 %) 用于除了 8 位和 16 位的整数类型之外的所有数值类型。

自增和自减运算符 (++ 、 --) 给数值加 1 或减 1 。这两个运算符可以放在变量的前面或后面,这取决于你想让变量在计算表达式之前还是之后被更新。

(P022)

整数类型的除法运算总是会截断余数。用一个值为 0 的变量做除数将产生一个运行时错误 (DivisionByZeroException) 。

用字面值 0 做除数将产生一个编译时错误。

整数类型在运行算术运算时可能会溢出。默认情况下,溢出默默地发生而不会抛出任何异常。尽管 C# 规范不能预知溢出的结果,但是 CLR (通用语言运行时) 总是会造成溢出行为。

checked 运算符的作用是在运行时当整型表达式或语句达到这个类型的算术限制时,产生一个 OverflowException 异常而不是默默的失败。

checked 运算法在有 ++ 、 -- 、 + 、 - (一元运算符和二元运算符) 、 * 、 / 和整数类型间显式转换运算符的表达式中起作用。

checked 操作符对 double 和 float 数据类型没有作用,对 decimal 类型也没有作用 (这种类型总是受检的)。

checked 运算符能用于表达式或语句块的周围。

可以通过在编译时加上 /checked+ 命令行开关 (在 Visual Studio 中,可以在 Advanced Build Settings 中设置) 来默认使程序中所有表达式都进行算术溢出检查。如果你只想禁用指定表达式或语句的溢出检查,可以用 unchecked 运算符。

(P023)

无论是否使用了 /checked 编译器开关,编译时的表达式计算总会检测溢出,除非应用了 unchecked 运算符。

C# 支持如下的位运算符 : ~ (按位取反) 、 & (按位与) 、 | (按位或) 、 ^ (按位异或) 、 << (按位左移) 、 >> (按位右移) 。

8 位和 16 位整数类型指的是 byte 、 sbyte 、 short 和 ushort 。这些类型缺少它们自己的算术运算符,所以 C# 隐式把它们转换成所需的大一些类型。

不同于整数类型,浮点类型包含某些操作要特殊对待的值。这些特殊的值是 NaN (Not a Number) 、 +∞ 、 -∞ 和 -0 。

float 和 double 类型包含用于 NaN 、 +∞ 、 -∞ 值 (MaxValue 、 MinValue 和 Epsilon) 的常量。

(P024)

非零值除以零的结果是无穷大。

零除以零或无穷大减去无穷大的结果是 NaN。

使用比较运算符 (==) 时,一个 NaN 的值永远也不等于其他的值,甚至不等于其他的 NaN 值。

必须使用 float.IsNaN 或 double.IsNaN 方法来判断一个值是不是 NaN 。

无论何时使用 object.Equals 方法,两个 NaN 的值都是相等的。

NaN 在表示特殊值时很有用。

float 和 double 遵循 IEEE 754 格式类型规范,原生支持几乎所有的处理器。

double 类型在科学计算时很有用。

decimal 类型在金融计算和计算那些 “人为” 的而非真实世界的值时很有用。

(P025)

float 和 double 在内部是基于 2 来表示数值的。因此只有基于 2 表示的数值才能被精确的表示。事实上,这意味着大多数有小数的字面值 (它们基于10) 将无法精确的表示。

decimal 基于 10,它能够精确地表示基于10的数值 (也包括它的因子,基于2和基于5) 。因为实型字面值是基于 10 的,所以 decimal 能精确地表示像 0.1 这样的数。然而,double 和 decimal 都不能精确表示那些基于 10 的极小数。

C# 中的 bool (System.Boolean 类型的别名) 能表示 true 和 false 的逻辑值。

尽管布尔类型值仅需要 1 位存储空间,但是运行时却用 1 字节空间。这是因为字节是运行时和处理器能够有效使用的最小单位。为避免在使用数组时的空间浪费,.NET Framework 提供了 System.Collections 命名空间下的 BitArray 类,它被设置成每个布尔值使用 1 位。

bool 不能转换成数值类型,反之亦然。

== 和 != 运算符用于判断任何类型相等还是不相等,总是返回一个 bool 值。

(P026)

对于引用类型,默认情况的相同是基于引用的,而不是底层对象的实际值。

相等和比较运算符 == 、 != 、 < 、 > 、 >= 和 <= 用于所有的数值类型,但是用于实数时要特别注意。

比较运算符也用于枚举 (enum) 类型成员,它比较枚举的潜在整数值。

&& 和 || 运算符用于判断 “与” 和 “或” 条件。它们常常与代表 “非” 的 (!) 运算符一起使用。

&& 和 || 运算符会在可能的情况下执行短路计算。

短路计算在允许某些表达式时是必要的。

& 和 | 运算符也用于判断 “与” 和 “或” 条件。

不同之处是 & 和 | 运算符不支持短路计算。因此它们很少用于代替条件运算符。

不同于 C 和 C++ , & 和 | 运算符在用于布尔表达式时执行布尔比较 (非短路计算) 。& 和 | 运算符只在用于数值运算时才执行位操作。

三元条件运算符 (简称为条件运算符) 使用 q ? a : b 的形式,它在条件 q 为真时,计算 a,否则计算 b 。

(P027)

条件表达式在 LINQ 语句中特别有用。

C# 中的 char (System.Char 类型的别名) 表示一个 Unicode 字符,它占用 2 个字节。字符字面值在单引号 (') 中指定。

转义字符不能按照字面表示或解释。转义字符由反斜杠(\)和一个表示特殊意思的字符组成。

\' 单引号
\" 双引号
\\ 斜线
\0 空
\a 警告
\b 退格
\f 走纸
\n 换行
\r 回车
\t 水平制表符
\v 垂直制表符

\u (或 \x ) 转义字符通过 4 位十六进制代码来指定任意 Unicode 字符。

从字符类型到数值类型的隐式转换只在这个数值类型可以容纳无符号 short 类型时有效。对于其他的数值类型,则需要显式转换。

(P028)

C# 中的字符串类型 (System.String 的别名) 表示一些不变的、按顺序的 Unicode 字符。字符串字面值在双引号 (") 中指定。

string 类型是引用类型而不是值类型,但是它的相等运算符却遵守值类型的语义。

对 char 字面值有效的转移字符在字符串中也有效。

C# 允许逐字字符串字面值,逐字字符串字面值要加前缀 @ ,它不支持转义字符。

逐字字符串字面值也可以贯穿多行。

可以通过在逐字字符串中写两次的方式包含双引号字符。

(+) 运算符连接两个字符串。

右面的操作对象可以是非字符串类型的值,在这种情况下这个值的 ToString 方法将被调用。

既然字符串是不变的,那么重复地用 (+) 运算符来组成字符串是低效率的 : 一个更好的解决方案是用 System.Text.StringBuilder 类型。

(P029)

字符串类型并不支持 < 和 > 的比较,必须使用字符串类型的 CompareTo 方法。

数组代表固定数量的特定类型元素,为了高效率地读取,数组中的元素总是存储在连续的内存块中。

数组用元素类型后加方括号表示。

方括号也可以检索数组,通过位置读取特定元素。

数组索引从 0 开始。

数组的 Length 属性返回数组中的元素数量。一旦数组被建立,它的长度将不能被更改。

System.Collection 命名空间和子命名空间提供了像可变数组等高级数据结构。

数组初始化语句定义了数组中的每个元素。

所有的数组都继承自 System.Array 类,它提供了所有数组的通用服务。这些成员包括与数组类型无关的获取和定义元素的方法。

建立数组时总是用默认值初始化数组中的元素,类型的默认值是值为 0 的项。

无论数组元素类型是值类型还是引用类型都有重要的性能影响,若元素类型是值类型,每个元素的值将作为数组的一部分进行分配。

(P030)

无论是任何元素类型,数组本身总是引用类型对象。

多维数组分为两种类型 : “矩形数组” 和 “锯齿形数组” 。 “矩形数组” 代表 n 维的内存块,而 “锯齿形数组” 则是数组的数组。

矩形数组声明时用逗号 (,) 分隔每个维度。

数组的 GetLength() 方法返回给定维度的长度 (从 0 开始) 。

锯齿形数组在声明时用两个方括号表示每个维度。

锯齿形数组内层维度在声明时可不指定。

不同于矩形数组,锯齿形数组的每个内层数组都可以是任意长度;每个内层数组隐式初始化成空 (null) 而不是一个空数组;每个内层数组必须手工创建。

有两种方式可以简化数组初始化表达式。第一种是省略 new 运算符和类型限制条件,第二种是使用 var 关键字,使编译器隐式确定局部变量类型。

(P032)

隐式类型转换能进一步用于一维数组的这种情况,能在 new 关键字之后忽略类型限制符,而由编译器推断数组类型。

为了使隐式确定数组类型正常工作,所有的元素都必须可以隐式转换成同一种类型。

运行时给所有的数组索引进行边界检查,如果使用了不合法的索引,就会抛出 IndexOutOfRangeException 异常。

和 Java 一样,数组边界检查对类型安全和简化调试是很有必要的。

通常来说,边界检查的性能消耗很小,即时编译器会进行优化。像在进入循环之前预先检查所有的索引是不安全的,以此来避免在每轮循环中都检查索引。

C# 提供 "unsafe" 关键字来显式绕过边界检查。

变量表示存储着可变值的存储空间,变量可以是局部变量、参数 (value 、 ref 或 out) 、 字段 (instance 或 static) 或数组元素。

“堆” 和 “栈” 是存储变量和常量的地方,它们每个都有不同的生存期语义。

“栈” 是存储局部变量和参数的内存块,栈在进入和离开一个函数时逻辑增加和减少。

(P033)

“堆” 是指对象残留的内存块,每当一个新的对象被创建时,它就被分配进堆,同时返回这个对象的引用。

当程序执行时,堆在新对象创建时开始填充。

.NET 运行时有垃圾回收器,它会定期从堆上释放对象。

只要对象没有被引用,他就会被选中释放。

无论变量在哪里声明,值类型实例以及对象引用一直存在。如果声明的实例作为对象中的字段或数组元素,那么实例存储于堆上。

在 C# 中你无法显式删除对象,但在 C++ 中可以。未引用的对象最终被垃圾回收器回收。

堆也存储静态字段和常量。不同于堆上被分配的对象 (可以被垃圾回收器回收),静态字段和常量将一直存在直到应用程序域结束。

C# 遵守明确赋值的规定。在实践中,这是指在没有 unsafe 上下文情况下是不能访问未初始化内存的。明确赋值有三种含义 :

1. 局部变量在读取之前必须被赋值;

2. 当调用方法时必须提供函数的参数;

3. 其他的所有变量 (像字段和数组元素) 都自动在运行时被初始化;

(P034)

字段和数组元素都会用其类型的默认值自动初始化。

所有类型实例都有默认值。预定义类型的默认值是值为 0 的项 :

[类型] - [默认值]

所有引用类型 - null
所有数值和枚举类型 - 0
字符类型 - '\0'
布尔类型 - false

能够对任何类型使用 default 关键字来获得其默认值。

自定义值类型中的默认值与自定义类型定义的每个字段的默认值相同。

方法有一连串的参数,其中定义了一系列必须提供给方法的参数。

(P035)

能通过 ref 和 out 修饰符来改变参数传递的方式 :

[参数修饰符] - [传递类型] - [必须明确赋值的参数]

none - 值类型 - 传入
ref - 引用类型 - 传入
out - 引用类型 - 传出

通常,C# 中参数默认是按值传递的,这意味着在将参数值传给方法时创建参数值的副本。

值传递引用类型参数将赋值给引用而不是对象本身。

(P036)

如果按引用传递参数,C# 使用 ref 参数修饰符。

注意 ref 修饰符在声明和调用时都是必需的,这样就清楚地表明了将执行什么。

ref 修饰符对于转换方法是必要的。

无论参数是引用类型还是值类型,都可以实现值传递或引用传递。

out 参数和 ref 参数类似,除了 :

1. 不需要在传入函数之前赋值;

2. 必须在函数结束之前赋值;

(P037)

out 修饰符通常用于获得方法的多个返回值。

和 ref 参数一样, out 参数是引用传递。

当引用传递参数时,是为已存变量的存储空间起了个别名,而不是创建了新的存储空间。

params 参数修饰符在方法最后的参数中指定,它使方法接收任意数量的指定类型参数,参数类型必须声明为数组。

(P038)

也可以将通常的数组提供给 params 参数。

从 C# 4.0 开始,方法、构造方法和索引器都可以被声明成可选参数,只要在声明时提供默认值,这个参数就是可选参数。

可选参数在调用方法时可以被省略。

编译器在可选参数被用到的地方用了默认值代替了可选参数。

被其他程序集调用的 public 方法在添加可选参数时要求重新编译所有的程序集,因为参数是强制的。

可选参数的默认值必须由常量表达式或无参数的值类型构造方法指定,可选参数不能被标记为 ref 或 out 。

强制参数必须在可选参数方法声明和调用之前出现 (params 参数例外,它总是最后出现)。

相反的,必须将命名参数和可选参数联合使用。

命名参数可以按名称而不是按参数的位置确定参数。

(P039)

命名参数能按任意顺序出现。

不同的是参数表达式按调用端参数出现的顺序计算。通常,这只对相互作用的局部有效表达式有所不同。

命名参数和可选参数可以混合使用。

按位置的参数必须出现在命名参数之前。

命名参数在和可选参数混合使用时特别有用。

如果编译器能够从初始化表达式中推断出变量的类型,就能够使用 var 关键字 (C# 3.0 中引入) 来代替类型声明。

因为是直接等价,所以隐式类型变量是静态指定类型的。

(P040)

当无法直接从变量声明中推断出变量类型时,var 关键字将降低代码的可读性。

表达式本质上表示的是值。最简单的表达式是常量和变量。表达式能够用运算符进行转换和组合。运算符用一个或多个输入操作数来输出新的表达式。

C# 中的运算符分为一元运算符、二元运算符和三元运算符,这取决它们使用的操作数数量 (1 、 2 或 3) 。

二元运算符总是使用中缀标记法,运算符在两个操作数中间。

基础表达式由 C# 语言内置的基础运算符表达式组成。

(. 运算符) 执行成员查找;

(() 运算符) 执行方法调用;

空表达式是没有值的表达式。

因为空表达式没有值,所以不能作为操作数来创建更复杂的表达式。

赋值表达式用 = 运算符将一个表达式的值赋给一个变量。

(P041)

赋值表达式不是空表达式,实际上它包含了赋值操作的值,因此能再加上另一个表达式。

复合赋值运算符是由其他运算符组合而成的简化运算符。

当表达式包含多个运算符时,运算符的优先级和结合性决定了计算的顺序。

优先级高的运算符先于优先级低的运算符执行。

如果运算符的优先级相同,那么运算符的结合性决定计算的顺序。

二元运算符 (除了赋值运算符、 lambda 运算符 、 null 合并运算符) 是左结合运算符。换句话说,它们是从左往右计算。

赋值运算符、 lambda 运算符、 null 合并运算符和条件运算符是右结合运算符。换句话说,它们从右往左计算。右结合运算符允许多重赋值。

(P043)

函数包含按出现的字面顺序执行的语句。语句块是大括号 ({}) 中出现的一系列语句。

(P044)

声明语句可以声明新变量,也可以用表达式初始化变量。声明语句以分号结束。可以用逗号分隔的列表声明多个同类型的变量。

常量的声明和变量声明类似,除了不能在声明之后改变它的值和必须在声明时初始化。

局部变量和常量的作用范围是在当前的语句块中。不能在当前的或嵌套的语句块中声明另一个同名的局部变量。

变量的作用范围是它所在的整个代码段。

表达式语句是表达式也是合法的语句,表达式语句必须改变状态或调用某些改变的状态,改变的状态本质上是指改变一个变量。

可能的表达式语句是 :

1. 赋值表达式 (包括自增和自减表达式) ;

2. 方法调用表达式 (有返回值的和无返回值的) ;

3. 对象实例化表达式;

(P045)

当调用有返回值的构造函数或方法时,并不一定要使用返回值。除非构造函数或方法改变了某些状态,否则这些语句完全没作用。

C# 有下面几种语句来有条件地控制程序的执行顺序 :

1. 选择语句 (if, switch) ;

2. 条件语句 (? :) ;

3. 循环语句 (while 、 do-while 、 for 、 foreach) ;

if 语句是否执行代码体取决于布尔表达式是否为真。

如果代码体是一条语句,可以省略大括号。

if 语句之后可以紧跟 else 分句。

在 else 分句中,能嵌套另一个 if 语句。

(P046)

else 分句总是与其前语句块中紧邻的未配对的 if 语句结合。

可以通过改变大括号的位置来改变执行顺序。

大括号可以明确地表明结构,这能提高嵌套 if 语句的可读性 (即使编译器并不需要)。

从语义上讲,紧跟着每一个 if 语句的 else 语句从功能上都是嵌套在 else 语句之中的。

switch 语句可以根据变量可能值的选择来转移程序的执行。

switch 语句可以拥有比嵌套 if 语句更加简短的代码,因为 switch 语句只要求表达式计算一次。

(P047)

只能在支持静态计算的类型表达式中使用 switch 语句,因此限制了它只适用于整数类型、字符串类型和枚举类型。

在每个 case 分句的结尾,必须用某种跳转语句明确说明下一步要执行的代码。这里有选项 :

1. break (跳转到 switch 语句结尾) ;

2. goto case x (跳转到另一个 case 分句) ;

3. goto default (跳转到 default 分句) ;

4. 任何其他的跳转语句 —— return 、 throw 、 continue 或 goto 标签;

当多于一个值要执行相同代码时,可以按顺序列出共同的 case 条件。

switch 语句的这种特性对于写出比嵌套 if-else 语句更清晰的代码来说很重要。

C# 能够用 while 、 do-while 、 for 和 foreach 语句重复执行一系列语句。

while 循环在布尔表达式为真时重复执行一段代码,这个表达式在循环体被执行之前被检测。

(P048)

do-while 循环在功能上不同于 while 循环的是它在语句块执行之后检测表达式 (保证语句块至少被执行一次) 。

for 循环类似有特殊分句的 while 循环,这些特殊分句用于初始化和累积循环变量。

for 循环有下面的3个分句 :

for (initialization-clause; condition-clause; interation-clause) {statement-or-statement-block}

initialization-clause : 在循环之前执行,用于初始化一个或多个循环变量;
condition-clause : 是布尔表达式,当它为真时,将执行循环体;
interation-clause : 在每次循环语句体之后执行,通常用于更新循环变量;

for 语句的3个部分都可以被省略,可以通过下面的代码来实现一个无限循环 (也可以用 while(true) 代替) : for (;;)

(P049)

foreach 语句遍历可枚举对象的每一个元素,大多数 C# 和 .NET Framework 中表示集合或元素列表的类型都是可枚举的。

数组和字符串都是可枚举的。

C# 中的跳转语句有 break 、 continue 、 goto 、 return 和 throw 。

跳转语句违背了 try 语句的可靠性规则,这意味着 :

1. 跳转到 try 语句块之外的跳转总是在到达目的地之前执行 try 语句的 finally 语句块;

2. 跳转语句不能从 finally 语句块内跳到块外;

break 语句用来结束循环体或 switch 语句体的执行。

continue 语句放弃循环体中其后的语句,继续下一轮循环。

(P050)

goto 语句用于转移执行到语句块中的另一个标签处,或者用于 switch 语句内。

标签语句仅仅是语句块中的占位符,用冒号后缀表示。

goto case case-constant 用于转移执行到 switch 语句块中的另一个条件。

return 语句退出方法,如果这个方法有返回值,同时必须返回方法指定返回类型的表达式。

return 语句能出现在方法的任意位置。

throw 语句抛出异常来表示有错误发生。

using 语句用于调用在 finally 语句块中实现 IDisposable 接口的 Dispose 方法。

C# 重载了 using 关键字,使它在不同上下文中有不同的含义。

特别注意 using 指令不同于 using 语句。

(P051)

lock 语句是调用 Monitor 类 Enter() 方法和 Exit() 方法的简化操作。

命名空间是类型名称必须唯一的作用域,类型通常被组织到分层的命名空间里,这样既避免了命名冲突又使类型名更容易被找到。

命名空间组成了类型名的基本部分。

命名空间是独立于程序集的。

程序集是像 .exe 或 .dll 一样的部署单元。

命名空间不影响成员的可见性 —— public 、 internal 、 private 等。

namespace 关键字为其中的类型定义了命名空间。

命名空间中的点 (.) 表明嵌套命名空间的层次结构。

可以用包含从外到内的所有命名空间的完全限定名来指代一种类型。

如果类型没有在任何命名空间中被定义,则说明它存在于全局命名空间内。

全局命名空间也包含了顶级命名空间。

using 指令用于导入命名空间。这是不使用完全限定名来指代某种类型的便捷方法。

(P052)

在不同命名空间定义相同类型名称是合法的 (而且通常是需要的)。

外层命名空间中声明的名称能够直接在内层命名空间中使用。

如果想使用同一命名空间分层结构的不同分支中的类型,你就要使用部分限定名。

如果相同的类型名出现在内层和外层命名空间中,内层的类型优先。如果要使用外层命名空间中的类型,必须使用它的完全限定名。

(P053)

所有的类型名在编译时都被转换成完全限定名,中间语言 (IL) 代码不包含非限定名和部分限定名。

可以重复声明同一命名空间,只要它里面的类型名不冲突。

我们能在命名空间中使用嵌套 using 指令,可以在命名空间声明中指定 using 指令的范围。

(P054)

引入命名空间有可能引起类型名的冲突,因此可以只引入需要的类型而不是整个命名空间,为每个类型创建别名。

外部别名允许引用两个完全限定名相同的类型,这种特殊情况只发生在两种类型来自不同的程序集。

(P055)

内层命名空间中的名称隐藏了外层命名空间中的名称,但是,有时候即使使用类型的完全限定名也无法解决冲突。

(::) 用于限定命名空间别名。

【第03章】

(P057)

类是最常见的一种引用类型。

复杂的类可能包含一下内容 :

1. 类属性 —— 类属性及类修饰符。非嵌套的类修饰符有 : public 、 internal 、 abstract 、 sealed 、 static 、 unsafe 、 partial ;

2. 类名 —— 各种类型参数、唯一基类,多个接口;

3. 花括号内 —— 类成员 (方法、成员属性、索引器、事件、字段、构造方法、运算符函数、嵌套类型和终止器) ;

字段是类或结构体中的变量。

以下修饰符可以用来修饰字段 :

[静态修饰符] —— static

[访问权限修饰符] —— public internal private protected

[继承修饰符] —— new

[不安全代码修饰符] —— unsafe

[只读修饰符] —— readonly

[跨线程访问修饰符] —— volatile

(P058)

“只读修饰符” 防止字段值在构造后被更改,只读字段只能在声明时或在其所属的类构造方法中被赋值。

字段不一定要初始化,没有被初始化的字段系统会赋一个默认值 ( 0 、 \0 、 null 、 false ) 。字段初始化语句在构造方法之前执行。

为了简便,可以用逗号分隔的列表声明一组同类型的字段,这是声明具有共同属性和修饰符的一组字段的简洁写法。

方法是用一组语句实现某个行为。方法能从调用语句的特定类型的传入参数中接收输入数据,并把输出数据以特定的返回值类型返回给调用语句。方法也可以返回 void 类型,表明这个方法不向调用方返回任何值。此外,方法还可以通过 ref / out 参数向调用方返回值。

方法签名在整个类中必须是唯一的,方法签名包括方法名、参数类型 (但不包括参数名及返回值类型) 。

方法可以用以下的修饰符 :

[静态修饰符] ——  static

[访问权限修饰符] ——  public internal private protected

[继承修饰符] —— new virtual abstract override sealed

[部分方法修饰符] —— partial

[非托管代码修饰符] —— unsafe extern

只要确保方法签名不同,可以在类中重载方法 (多个方法共用同一个方法名) 。

返回值类型和参数修饰符不属于方法签名的一部分。

参数是按值传递还是按引用传递,也是方法签名的一部分。

构造方法执行类或结构体的初始化代码,构造方法的定义和方法的定义类似,区别仅在于构造方法名和返回值只能和封装它的类相同。

(P059)

构造方法支持以下修饰符 :

[访问权限修饰符] —— public internal private protected

[非托管代码修饰符] —— unsafe extern

类或结构体可以重载构造方法,为了避免重复编码,一个构造方法可以用 this 关键字调用另一个构造方法。

(P060)

当一个构造方法调用另一个时,被调用的构造方法先执行。

C# 编译器自动为没有显式定义构造方法的类生成构造方法。但是,一旦显式定义了构造方法,系统将不再生成无参数构造方法。

对于结构体来说,无参数构造方法是结构体所固有的,因此,不能自己定义。结构体的隐

网站文章

  • 【2021ACM-ICPC亚洲区域赛济南站】C、D、J、K四题超详细题解

    【2021ACM-ICPC亚洲区域赛济南站】C、D、J、K四题超详细题解

    2021ACM-ICPC亚洲区域赛济南站C、D、J、K四题超详细题解

    2024-04-01 00:06:26
  • 如何设计一个百万级用户的抽奖系统?

    如何设计一个百万级用户的抽奖系统?

    目录1.抽奖系统的背景引入 2.结合具体业务需求分析抽奖系统 3.一个未经过优化的系统架构 4.负载均衡层的限流 5.Tomcat线程数量的优化 6.基于Redis实现抽奖业务逻辑 7.发放礼品环节进行限流削峰 8.系统架构设计总结 1、抽奖系统的背景引入本文给大家分享一个之前经历过的抽奖系统的流量削峰...

    2024-04-01 00:06:19
  • 6种epoll的设计,让你吊打面试官,而且他不能还嘴

    6种epoll的设计,让你吊打面试官,而且他不能还嘴丨单线程epoll的精妙 丨多线程的3种设计 丨蓦然回首还是多进程免费学习地址:C/C++Linux服务器开发/后台架构师-学习视频

    2024-04-01 00:06:10
  • 【js】由浅到深了解JavaScript类

    想学习javascript的类,这篇文章会给你很大的帮助,由著名的泣红亭撰写,绝对的精品。来自:无忧脚本 作者:泣红亭最近在无忧脚本混了一阵子,回复了一些贴子,自己却没有做出什么东东让大家看看,心里有些不安,于是写了下边的一点东西,本来应该发在类封装区的,考虑到那里比较冷,而这篇文章我希望能够帮助到更多的朋友,因此放到这里来了。>>>>>>>>>>>>>>>>>>>>>>>>>>>

    2024-04-01 00:05:41
  • java反射调用get/set方法

    java反射调用get/set方法

    java反射调用get/set方法

    2024-04-01 00:05:34
  • JS中的eval及json介绍

    转自:微点阅读 https://www.weidianyuedu.com首先声明一下,本人是JS新手,所以不敢说深入,只是把最近对eval的学习经验拿出来跟大家分享,如果您是高手可略去不看。适合读者:...

    2024-04-01 00:05:27
  • HTML——链接标签、锚标签、功能标签

    HTML——链接标签、锚标签、功能标签

    <!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>链接标签</title></head><body><a name="top">顶部</a><!--文本超链接、图像超链接href:必填,表示跳转到那个界面_blank:生成一个新网页标签打开_se...

    2024-04-01 00:05:03
  • 笔记神器Markdown之完美实现图床(Typora+PicGo+Github)

    笔记神器Markdown之完美实现图床(Typora+PicGo+Github)

    使用Markdown编写笔记非常方便,但是想把自己的博客笔记同步到多个平台,确是非常让人头疼的问题,今天我们就用Typora+PicGo+Github完美实现图床,让我们简化时间成本!

    2024-04-01 00:04:55
  • 一文让你玩转Gradle

    一文让你玩转Gradle

    Gradle是一个构建工具,面向开发者的脚本语言是Groovy和Kotlin,简而言之就是我们常用的build.gradle和build.gradle.kts或plugin等。在Gradle中,有大量配置都是通过脚本语言来写的,所以不管是Groovy还是Kotlin,最后的表现都是。

    2024-04-01 00:04:50
  • 如何判断数据库中的两个表是否相同?比较数据库中的两个表是否完全相同,包括字段和每条记录

    如何判断数据库中的两个表是否相同?比较数据库中的两个表是否完全相同,包括字段和每条记录

    在数据库应用程序中,经常需要比较两个表是否相同,包括表的结构和数据。通过以上步骤,我们可以比较数据库中的两个表是否完全相同,包括字段和每条记录。请注意,上述示例代码中的数据库连接信息需要根据实际情况进...

    2024-04-01 00:04:25