C# 8.0本质论
上QQ阅读APP看书,第一时间看更新

1.2 C#语法基础

成功编译并运行HelloWorld程序之后,我们来分析代码,了解它的各个组成部分。首先熟悉一下C#关键字以及可供开发者选择的标识符。

初学者主题:关键字

为了帮助编译器解释代码,C#中的某些单词具有特殊地位和含义,它们称为关键字。编译器根据关键字的固有语法来解释程序员写的表达式。在HelloWorld程序中,class、static和void均是关键字。

编译器根据关键字识别代码的结构与组织方式。由于编译器对这些单词有着严格的解释,所以只能将关键字放在特定位置。如违反规则,编译器会报错。

1.2.1 C# 关键字

表1.1总结了C#关键字。

C# 1.0之后没有引入任何新的保留关键字,但在后续版本中,一些构造使用了上下文关键字,它们在特定位置才有意义,在其他位置则无意义[1]。这样大多数C# 1.0代码都能兼容后续版本[2]

1.2.2 标识符

和其他语言一样,C#用标识符标识程序员编码的构造。在代码清单1.1中,HelloWorld和Main均为标识符。分配标识符之后,以后将用它引用所标识的构造。因此,开发者应分配有意义的名称,不要随性而为。

好的程序员总能选择简洁而有意义的名称,这使代码更容易理解和重用。清晰和一致是如此重要,以至于“框架设计准则”(http://t.cn/RD6v4RB)建议不要在标识符中使用单词缩写[3],甚至不要使用不被广泛接受的首字母缩写词。即使被广泛接受(如HTML),使用时也要一致。不要一会儿这样用,一会儿那样用。为避免滥用,可限制所有首字母缩写词都必须包含到术语表中。总之,要选择清晰(甚至是详细)的名称,尤其是在团队中工作,或者开发要由别人使用的库的时候。

标识符有两种基本的大小写风格。第一种风格被.NET框架创建者称为Pascal大小写(PascalCase),这是因为它曾经在Pascal编程语言中很流行,它要求标识符的每个单词首字母大写,例如ComponentModel、Configuration和HttpFileCollection。注意在HttpFileCollection中,由于首字母缩写词HTTP的长度超过两个字母,所以仅首字母大写。第二种风格是camel大小写(camelCase),除第一个字母小写,其他约定一样,例如quotient、firstName、httpFileCollection、ioStream和theDreadPirateRoberts。

表1.1 C#关键字

*这些是上下文关键字,括号中的数字(n)代表加入该上下文关键字的C#版本。

设计规范

·要更注重标识符的清晰而不是简短。

·不要在标识符名称中使用单词缩写。

·不要使用不被广泛接受的首字母缩写词,对于那些已经被广泛接受的缩写词,在使用时也要保持一致。

下划线虽然合法,但标识符一般不要包含下划线、连字号或其他非字母/数字字符。此外,C#不像其前辈那样使用匈牙利命名法(为名称附加类型缩写前缀)。这避免了数据类型改变时还要重命名变量,也避免了数据类型前缀经常不一致的情况。

极少数情况下,有的标识符(比如Main)可能在C#语言中具有特殊含义。

设计规范

·要把两个字母的首字母缩写词全部大写,除非它是camelCase标识符的第一个单词。

·包含三个或更多字母的首字母缩写词,仅第一个字母才要大写,除非该缩写词是camelCase标识符的第一个单词。

·在camelCase标识符开头的首字母缩写词中,所有字母都不要大写。

·不要使用匈牙利命名法(不要为变量名称附加类型前缀)。

高级主题:关键字

虽然罕见,但关键字附加“@”前缀可作为标识符使用,例如可命名局部变量@return。类似地(虽不符合C#大小写规范),也可将方法命名为@throw()并且跟随一对括号,形如:@throw()。

在Microsoft的实现中,还有4个未文档化的保留关键字:__arglist,__makeref,__reftype,__refvalue。它们仅在罕见的互操作情形下才需要使用,平时完全可以忽略。注意这4个特殊关键字以双下划线开头。C#设计者保留将来把这种标识符转化为关键字的权利。为安全起见,自己不要创建这样的标识符。

1.2.3 类型定义

C#所有代码都出现在一个类型定义的内部,最常见的类型定义以关键字class开头。如代码清单1.3所示,类定义是class identifier {...}形式的代码块。

类型名称(本例是HelloWorld)可以随便取,但根据约定,它应当使用PascalCase风格。就本例来说,可选择的名称包括Greetings、HelloInigoMontoya、Hello或者简单地称为Program。(对于包含Main()方法的类,Program是个很好的名称。Main()方法的详情稍后讲述。)

代码清单1.3 基本的类声明

设计规范

·要用名词或名词短语命名类。

·要为所有类名使用PascalCase大小写风格。

程序通常包含多个类型,每个类型包含多个方法。

1.2.4 Main方法

初学者主题:什么是方法?

语法上说,C#方法是已命名代码块,由一个方法声明(例如static void Main())引入,后跟一对大括号({}),其中包含零条或多条语句。方法可执行计算或操作。与书面语言中的段落相似,方法提供了结构化和组织代码的一种方式,使之更易读。更重要的是,方法可以重用,可从多个地方调用,所以避免了代码的重复。方法声明除了引入方法并定义方法名,还要定义传入和传出方法的数据。在代码清单1.4中,Main()连同后面的{...}便是C#方法的例子。

C#程序从Main方法开始执行。该方法以static void Main()开头。在命令控制台中输入dotnet run HelloWorld.exe执行程序时,程序将启动并解析Main的位置,然后执行其中第一条语句。如代码清单1.4所示。

代码清单1.4 HelloWorld分解示意图

虽然Main方法声明可进行某种程度的变化,但关键字static和方法名Main是始终都需要的。

高级主题:Main方法声明

C#要求Main方法返回void或int,而且要么无参,要么接收一个字符串数组。代码清单1.5展示了Main方法的完整声明。

代码清单1.5 带有参数和返回类型的Main方法

args参数是用于接收命令行参数的字符串数组。但数组第一个元素不是程序名称,而是可执行文件名称[4]后的第一个命令行参数,这和C和C++不同。用System.Environment.CommandLine获取执行程序所用的完整命令。

Main返回的int是状态码,标识程序执行是否成功。返回非零值通常意味着错误。

语言对比:C++/Java——main()

Main返回的int是状态码,标识程序执行是否成功。返回非零值通常意味着错误。此外,从C#7.1开始,Main方法也支持async/await修饰词。

注意 全部小写问题

与C风格的“前辈”不同,C#的Main方法名使用大写M,和C#的PascalCase命名约定一致。

将Main方法指定为static意味着这是“静态”方法,可用类名.方法名的形式调用。若不指定static,用于启动程序的命令控制台还要先对类进行实例化,然后才能调用方法。第6章将用整节篇幅讲述静态成员。

Main()之前的void表明方法不返回任何数据(将在第2章进一步解释)。

C#和C/C++一样使用大括号封闭构造(比如类或者方法)的主体。例如,Main方法主体就是用大括号封闭起来的。本例方法主体仅一条语句。

1.2.5 语句和语句分隔符

Main方法只含一条语句,即System.Console.WriteLine();,它在控制台上输出一行文本。C#通常用分号标识语句结束。每条语句都由代码要执行的一个或多个行动构成。声明变量、控制程序流程或调用方法都是语句的例子。

语言对比:Visual Basic——基于行的语句

有的语言以行为基本单位,这意味着如果不加上特殊标记,语句便不能跨行。在Visual Basic 2010以前,Visual Basic一直是典型的基于行的语言。它要求在行末添加下划线表示语句跨越多行。从Visual Basic 2010起,行连续符在大多情况下是可选的。

高级主题:无分号的语句

C#的许多编程元素都以分号结尾。不要求分号的一个例子是switch语句。由于大括号总是包含在switch语句中,所以C#不要求语句后跟分号。事实上,代码块本身就被视为语句(它们也由语句构成),不要求以分号结尾。类似地,有的编程元素(比如using指令)虽然分号显示为后缀但不被视为语句。

由于换行与否不影响语句的分隔,所以可将多条语句放到同一行,C#编译器认为这一行包含多条指令。例如,代码清单1.6在同一行包含了两条语句。执行时在控制台窗口分两行显示Up和Down。

代码清单1.6 一行上的多条语句

C#还允许一条语句跨越多行。同样地,C#编译器根据分号判断语句结束位置。在代码清单1.7中,HelloWorld程序里原本单行的WriteLine()代码被分成多行书写。

代码清单1.7 一条语句跨越多行

1.2.6 空白

分号使C#编译器能忽略代码中的空白。除少数特殊情况,C#允许代码随意插入空白而不改变语义。在代码清单1.6和代码清单1.7中,在语句中或语句间换行,甚至不换行都可以,对编译器最终创建的可执行文件没有任何影响。

初学者主题:什么是空白?

空白是一个或多个连续的格式字符(比如制表符、空格和换行符)。删除单词间的所有空白肯定会造成歧义,删除引号字符串中的任何空白也会如此。

程序员经常利用空白对代码进行缩进来增强可读性。来看看代码清单1.8和代码清单1.9展示的两个版本的HelloWorld程序。

代码清单1.8 不缩进

代码清单1.9 删除一切可以删除的空白

虽然这两个版本看起来和原始版本颇有不同,但C#编译器认为所有版本无差别。

初学者主题:用空白格式化代码

为增强可读性,用空白对代码进行缩进很有必要。写代码时要遵循制订好的编码标准和约定,以增强代码的可读性。

本书约定每个大括号都单独占一行,并缩进大括号内的代码。如一对大括号嵌套了第二对大括号,第二对大括号中的代码也缩进。

这不是强制性的C#标准,只是风格偏好。

[1] 例如在C# 2.0设计之初,语言设计者将yield指定成关键字。在Microsoft发布的C# 2.0编译器的alpha版本中(该版本分发给了数千名开发者),yield以一个新关键字的身份存在。但语言设计者最终选择使用yield return而非yield,从而避免将yield作为新关键字。除非与return连用,否则它没有任何特殊意义。

[2] 偶尔也有不兼容的情况,比如C# 2.0要求为using语句提供的对象必须实现IDisposable接口,而不能只是实现Dispose()方法。还有一些少见的泛型表达式,比如F(G<A,B>(7))在C# 1.0中代表F((G<A),(B>7)),而在C# 2.0中代表调用泛型方法G<A,B>,传递实参7,结果传给F。

[3] 有两种单词缩写:一种是“Abbreviation”,比如Professor缩写为Prof.;另一种是“Contraction”,比如Doctor缩写为Dr。——译者注

[4] 也就是程序名称,比如HelloWorld.exe。——译者注