C#6.0语言规范(二) 词法结构
程式
AC#程序由一个或多个源文件组成,正式称为编译单元(编译单元)。源文件是Unicode字符的有序序列。源文件通常与文件系统中的文件一一对应,但不需要此对应关系。为了获得最大的可移植性,建议使用UTF-8编码对文件系统中的文件进行编码。
从概念上讲,程序是使用三个步骤编译的:
- 转换,将文件从特定字符库和编码方案转换为Unicode字符序列。
- 词法分析,将Unicode输入字符流转换为标记流。
- 句法分析,将令牌流转换为可执行代码。
文法
本规范使用两个语法介绍了C#编程语言的语法。的词法(词法)定义Unicode字符如何组合以形成行结束,空白,注释,标记和预处理指令。的句法语法(句法语法)限定从词法文法产生的标记如何被组合以形成C#程序。
语法符号
使用ANTLR语法工具的符号以Backus-Naur形式呈现词法和句法语法。
词汇语法
C#的词汇语法在词法分析,标记和预处理指令中给出。词法语法的终端符号是Unicode字符集的字符,词法语法指定字符如何组合形成标记(标记),空格(白色空格),注释(注释)和预处理指令(预处理指令)。
C#程序中的每个源文件都必须符合词法语法的输入生成(词法分析)。
句法语法
C#的句法语法在本章后面的章节和附录中介绍。句法语法的终端符号是由词法语法定义的标记,语法语法指定如何组合标记以形成C#程序。
C#程序中的每个源文件都必须符合语法语法(编译单元)的compilation_unit生成。
词法分析
的输入产生式定义C#源文件的词法结构。C#程序中的每个源文件都必须符合此词法语法生成。
1 input 2 : input_section? 3 ; 4 5 input_section 6 : input_section_part+ 7 ; 8 9 input_section_part 10 : input_element* new_line 11 | pp_directive 12 ; 13 14 input_element 15 : whitespace 16 | comment 17 | token 18 ;
五个基本元素构成了C#源文件的词法结构:行终止符(行终止符),空格(空格),注释(注释),标记(标记)和预处理指令(预处理指令)。在这些基本元素中,只有令牌在C#程序(语法语法)的句法语法中具有重要意义。
C#源文件的词法处理包括将文件缩减为一系列令牌,这些令牌成为句法分析的输入。行终止符,空格和注释可用于分隔标记,预处理指令可以导致跳过源文件的各个部分,但这些词法元素对C#程序的语法结构没有影响。
在插值字符串文字(插值字符串文字)的情况下,单个标记最初由词法分析产生,但被分解为若干输入元素,这些输入元素重复进行词法分析,直到所有插值字符串文字都已解析为止。然后,所得到的标记用作句法分析的输入。
当几个词法语法产生与源文件中的一系列字符匹配时,词法处理总是形成最长的词法元素。例如,字符序列//
被处理为单行注释的开头,因为该词法元素比单个/
标记长。
行终止符
行终止符将C#源文件的字符分成行。
1 input 2 : input_section? 3 ; 4 5 input_section 6 : input_section_part+ 7 ; 8 9 input_section_part 10 : input_element* new_line 11 | pp_directive 12 ; 13 14 input_element 15 : whitespace 16 | comment 17 | token 18 ;
为了与添加文件结束标记的源代码编辑工具兼容,并使源文件可以被视为一系列正确终止的行,以下转换按顺序应用于C#程序中的每个源文件:
- 如果源文件的最后一个字符是Control-Z字符(
U+001A
),则删除此字符。 U+000D
如果源文件非空并且源文件的最后一个字符不是回车符(U+000D
),换行符(U+000A
),行分隔符,则将回车符()添加到源文件的末尾(U+2028
)或段落分隔符(U+2029
)。
注释
支持两种形式的注释:单行注释和分隔注释。单行注释以字符开头,//
并延伸到源行的末尾。分隔的注释以字符开头,以字符/*
结尾*/
。定界注释可能跨越多行。
1 comment 2 : single_line_comment 3 | delimited_comment 4 ; 5 6 single_line_comment 7 : '//' input_character* 8 ; 9 10 input_character 11 : '<Any Unicode character except a new_line_character>' 12 ; 13 14 new_line_character 15 : '<Carriage return character (U+000D)>' 16 | '<Line feed character (U+000A)>' 17 | '<Next line character (U+0085)>' 18 | '<Line separator character (U+2028)>' 19 | '<Paragraph separator character (U+2029)>' 20 ; 21 22 delimited_comment 23 : '/*' delimited_comment_section* asterisk* '/' 24 ; 25 26 delimited_comment_section 27 : '/' 28 | asterisk* not_slash_or_asterisk 29 ; 30 31 asterisk 32 : '*' 33 ; 34 35 not_slash_or_asterisk 36 : '<Any Unicode character except / or *>' 37 ;
评论不嵌套。字符序列/*
,并*/
有中没有任何特殊含义//
的评论和字符序列//
,并/*
有一个分隔符的注释中没有任何特殊含义。
不在字符和字符串文字中处理注释。
这个例子
1 /* Hello, world program 2 This program writes "hello, world" to the console 3 */ 4 class Hello 5 { 6 static void Main() { 7 System.Console.WriteLine("hello, world"); 8 } 9 }
包括分隔的评论。
这个例子
1 // Hello, world program 2 // This program writes "hello, world" to the console 3 // 4 class Hello // any name will do for this class 5 { 6 static void Main() { // this method must be named "Main" 7 System.Console.WriteLine("hello, world"); 8 } 9 }
显示了几个单行注释。
白色空间
空格被定义为具有Unicode类Zs(包括空格字符)的任何字符,以及水平制表符,垂直制表符和换页符。
1 whitespace 2 : '<Any character with Unicode class Zs>' 3 | '<Horizontal tab character (U+0009)>' 4 | '<Vertical tab character (U+000B)>' 5 | '<Form feed character (U+000C)>' 6 ;
令牌
有几种标记:标识符,关键字,文字,运算符和标点符号。空格和注释不是令牌,尽管它们充当令牌的分隔符。
1 token 2 : identifier 3 | keyword 4 | integer_literal 5 | real_literal 6 | character_literal 7 | string_literal 8 | interpolated_string_literal 9 | operator_or_punctuator 10 ;
Unicode字符转义序列
Unicode字符转义序列表示Unicode字符。Unicode字符转义序列在标识符(标识符),字符文字(字符文字)和常规字符串文字(字符串文字)中处理。Unicode字符转义不会在任何其他位置处理(例如,形成运算符,标点符号或关键字)。
1 unicode_escape_sequence 2 : '\\u' hex_digit hex_digit hex_digit hex_digit 3 | '\\U' hex_digit hex_digit hex_digit hex_digit hex_digit hex_digit hex_digit hex_digit 4 ;
Unicode转义序列表示由“ \u
”或“ \U
”字符后面的十六进制数字形成的单个Unicode 字符。由于C#在字符和字符串值中使用Unicode代码点的16位编码,因此字符文字中不允许使用U + 10000到U + 10FFFF范围内的Unicode字符,并使用字符串文字中的Unicode代理项对来表示。不支持代码点高于0x10FFFF的Unicode字符。
不执行多个翻译。例如,字符串文字“ \u005Cu005C
”等同于“ \u005C
”而不是“ \
”。Unicode值\u005C
是字符“ \
”。
这个例子
1 class Class1 2 { 3 static void Test(bool \u0066) { 4 char c = '\u0066'; 5 if (\u0066) 6 System.Console.WriteLine(c.ToString()); 7 } 8 }
显示了几个用法\u0066
,它是字母“ f
” 的转义序列。该计划相当于
1 class Class1 2 { 3 static void Test(bool f) { 4 char c = 'f'; 5 if (f) 6 System.Console.WriteLine(c.ToString()); 7 } 8 }
身份标识
本节中给出的标识符规则完全符合Unicode标准附件31推荐的规则,但允许下划线作为初始字符(在C编程语言中是传统的),标识符中允许使用Unicode转义序列,@
允许使用“ ”字符作为前缀,以使关键字可用作标识符。
1 identifier 2 : available_identifier 3 | '@' identifier_or_keyword 4 ; 5 6 available_identifier 7 : '<An identifier_or_keyword that is not a keyword>' 8 ; 9 10 identifier_or_keyword 11 : identifier_start_character identifier_part_character* 12 ; 13 14 identifier_start_character 15 : letter_character 16 | '_' 17 ; 18 19 identifier_part_character 20 : letter_character 21 | decimal_digit_character 22 | connecting_character 23 | combining_character 24 | formatting_character 25 ; 26 27 letter_character 28 : '<A Unicode character of classes Lu, Ll, Lt, Lm, Lo, or Nl>' 29 | '<A unicode_escape_sequence representing a character of classes Lu, Ll, Lt, Lm, Lo, or Nl>' 30 ; 31 32 combining_character 33 : '<A Unicode character of classes Mn or Mc>' 34 | '<A unicode_escape_sequence representing a character of classes Mn or Mc>' 35 ; 36 37 decimal_digit_character 38 : '<A Unicode character of the class Nd>' 39 | '<A unicode_escape_sequence representing a character of the class Nd>' 40 ; 41 42 connecting_character 43 : '<A Unicode character of the class Pc>' 44 | '<A unicode_escape_sequence representing a character of the class Pc>' 45 ; 46 47 formatting_character 48 : '<A Unicode character of the class Cf>' 49 | '<A unicode_escape_sequence representing a character of the class Cf>' 50 ;
有关上述Unicode字符类的信息,请参阅Unicode标准版本3.0,第4.5节。
有效标识符的示例包括“ identifier1
”,“ _identifier2
”和“ @if
”。
符合程序中的标识符必须采用Unicode标准化表C定义的规范格式,如Unicode标准附件15所定义。遇到不在标准化表单C中的标识符时的行为是实现定义的; 但是,不需要诊断。
前缀“ @
”允许使用关键字作为标识符,这在与其他编程语言交互时很有用。该字符@
实际上不是标识符的一部分,因此标识符可能在其他语言中看作普通标识符,没有前缀。带有@
前缀的标识符称为逐字标识符。允许使用@
非关键字标识符的前缀,但强烈建议不要使用样式。
这个例子:
1 class @class 2 { 3 public static void @static(bool @bool) { 4 if (@bool) 5 System.Console.WriteLine("true"); 6 else 7 System.Console.WriteLine("false"); 8 } 9 } 10 11 class Class1 12 { 13 static void M() { 14 cl\u0061ss.st\u0061tic(true); 15 } 16 }
使用名为“ class
”的静态方法定义名为“ ” 的类,该方法采用名为“ static
”的参数bool
。请注意,由于关键字中不允许使用Unicode转义,因此标记“ cl\u0061ss
”是标识符,与“ ”的标识符相同@class
。
如果在应用以下转换后它们相同,则认为两个标识符相同,顺序如下:
@
如果使用前缀“ ”,则将其删除。- 每个unicode_escape_sequence都会转换为相应的Unicode字符。
- 删除任何formatting_character。
包含两个连续下划线字符(U+005F
)的标识符保留供实现使用。例如,实现可能会提供以两个下划线开头的扩展关键字。
关键词
甲关键字是保留的字符的标识符样序列,并且不能被用作当由除了开头的识别符@
的字符。
1 keyword 2 : 'abstract' | 'as' | 'base' | 'bool' | 'break' 3 | 'byte' | 'case' | 'catch' | 'char' | 'checked' 4 | 'class' | 'const' | 'continue' | 'decimal' | 'default' 5 | 'delegate' | 'do' | 'double' | 'else' | 'enum' 6 | 'event' | 'explicit' | 'extern' | 'false' | 'finally' 7 | 'fixed' | 'float' | 'for' | 'foreach' | 'goto' 8 | 'if' | 'implicit' | 'in' | 'int' | 'interface' 9 | 'internal' | 'is' | 'lock' | 'long' | 'namespace' 10 | 'new' | 'null' | 'object' | 'operator' | 'out' 11 | 'override' | 'params' | 'private' | 'protected' | 'public' 12 | 'readonly' | 'ref' | 'return' | 'sbyte' | 'sealed' 13 | 'short' | 'sizeof' | 'stackalloc' | 'static' | 'string' 14 | 'struct' | 'switch' | 'this' | 'throw' | 'true' 15 | 'try' | 'typeof' | 'uint' | 'ulong' | 'unchecked' 16 | 'unsafe' | 'ushort' | 'using' | 'virtual' | 'void' 17 | 'volatile' | 'while' 18 ;
在语法的某些地方,特定标识符具有特殊含义,但不是关键字。这种标识符有时被称为“上下文关键字”。例如,在属性声明中,“ get
”和“ set
”标识符具有特殊含义(Accessors)。在这些位置以外的标识符get
或set
从不允许的标识符,因此这种使用不会与使用这些单词作为标识符冲突。在其他情况下,例如var
在隐式类型的局部变量声明(局部变量声明)中使用标识符“ ” ,上下文关键字可能与声明的名称冲突。在这种情况下,声明的名称优先于使用标识符作为上下文关键字。
字面
甲文字是一个值的一个源代码表示。
1 literal 2 : boolean_literal 3 | integer_literal 4 | real_literal 5 | character_literal 6 | string_literal 7 | null_literal 8 ;
布尔文字
有两个布尔文字值:true
和false
。
1 boolean_literal 2 : 'true' 3 | 'false' 4 ;
boolean_literal的类型是bool
。
整数文字
整型数用于编写类型int
,uint
,long
,和ulong
。整数文字有两种可能的形式:十进制和十六进制。
1 integer_literal 2 : decimal_integer_literal 3 | hexadecimal_integer_literal 4 ; 5 6 decimal_integer_literal 7 : decimal_digit+ integer_type_suffix? 8 ; 9 10 decimal_digit 11 : '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' 12 ; 13 14 integer_type_suffix 15 : 'U' | 'u' | 'L' | 'l' | 'UL' | 'Ul' | 'uL' | 'ul' | 'LU' | 'Lu' | 'lU' | 'lu' 16 ; 17 18 hexadecimal_integer_literal 19 : '0x' hex_digit+ integer_type_suffix? 20 | '0X' hex_digit+ integer_type_suffix? 21 ; 22 23 hex_digit 24 : '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' 25 | 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'a' | 'b' | 'c' | 'd' | 'e' | 'f';
整数文字的类型确定如下:
- 如果文字没有后缀,它具有第一这些类型的,其中它的值可以表示的:
int
,uint
,long
,ulong
。 - 如果文字后缀为
U
或u
,则它具有这些类型中的第一个,其值可以表示为:uint
,ulong
。 - 如果文字后缀为
L
或l
,则它具有这些类型中的第一个,其值可以表示为:long
,ulong
。 - 如果文字是由后缀
UL
,Ul
,uL
,ul
,LU
,Lu
,lU
,或lu
,它的类型的ulong
。
如果整数文字表示的值超出ulong
类型范围,则会发生编译时错误。
作为一种风格问题,建议在编写类型的文字时L
使用“ ”代替“ l
” long
,因为很容易将字母“ l
”与数字“ 1
” 混淆。
为了允许尽可能小的值int
和long
值写为十进制整数文字,存在以下两个规则:
- 当decimal_integer_literal具有值2147483648(2 ^ 31)和没有integer_type_suffix显示为紧接在一元负运算符标记(以下令牌元负运算),结果是类型的常量
int
用值-2147483648(-2 ^ 31) 。在所有其他情况下,这样的decimal_integer_literal属于类型uint
。 - 当decimal_integer_literal的值为9223372036854775808(2 ^ 63)并且没有integer_type_suffix或integer_type_suffix
L
或者l
在一元减号运算符令牌(一元减号运算符)之后立即显示为令牌时,结果是类型的常量,long
其值为-9223372036854775808( - 2 ^ 63)。在所有其他情况下,这样的decimal_integer_literal属于类型ulong
。
实数
实数用于编写类型值float
,double
和decimal
。
1 real_literal 2 : decimal_digit+ '.' decimal_digit+ exponent_part? real_type_suffix? 3 | '.' decimal_digit+ exponent_part? real_type_suffix? 4 | decimal_digit+ exponent_part real_type_suffix? 5 | decimal_digit+ real_type_suffix 6 ; 7 8 exponent_part 9 : 'e' sign? decimal_digit+ 10 | 'E' sign? decimal_digit+ 11 ; 12 13 sign 14 : '+' 15 | '-' 16 ; 17 18 real_type_suffix 19 : 'F' | 'f' | 'D' | 'd' | 'M' | 'm' 20 ;
如果未指定real_type_suffix,则实数的类型为double
。否则,实际类型后缀确定实际文字的类型,如下所示:
- 真实的文字后缀
F
或f
类型float
。例如,文字1f
,1.5f
,1e10f
,和123.456F
都是类型的float
。 - 真实的文字后缀
D
或d
类型double
。例如,文字1d
,1.5d
,1e10d
,和123.456D
都是类型的double
。 - 真实的文字后缀
M
或m
类型decimal
。例如,文字1m
,1.5m
,1e10m
,和123.456M
都是类型的decimal
。decimal
通过获取精确值将此文字转换为值,并在必要时使用银行家的舍入(小数类型)舍入到最接近的可表示值。除非值被舍入或值为零,否则将保留文字中明显的任何比例(在后一种情况下,符号和比例将为0)。因此,2.900m
将解析文字以形成带符号0
,系数2900
和比例的小数3
。
如果指定的文字无法以指示的类型表示,则会发生编译时错误。
类型的真实文字的值float
或double
通过使用IEEE“舍入到最近”模式来确定。
请注意,在实际文字中,小数点后总是需要十进制数字。例如,1.3F
是一个真正的文字,但1.F
不是。
字符文字
字符文字表示单个字符,通常由引号中的字符组成,如'a'
。
注意:ANTLR语法表示法令人困惑!在ANTLR中,当你写\'
它时代表单引号'
。当你写\\
它代表一个反斜杠\
。因此,字符文字的第一个规则意味着它以单引号开头,然后是字符,然后是单引号。而11个简单的转义序列\'
,\"
,\\
,\0
,\a
,\b
,\f
,\n
,\r
,\t
,\v
。
1 character_literal 2 : '\'' character '\'' 3 ; 4 5 character 6 : single_character 7 | simple_escape_sequence 8 | hexadecimal_escape_sequence 9 | unicode_escape_sequence 10 ; 11 12 single_character 13 : '<Any character except \' (U+0027), \\ (U+005C), and new_line_character>' 14 ; 15 16 simple_escape_sequence 17 : '\\\'' | '\\"' | '\\\\' | '\\0' | '\\a' | '\\b' | '\\f' | '\\n' | '\\r' | '\\t' | '\\v' 18 ; 19 20 hexadecimal_escape_sequence 21 : '\\x' hex_digit hex_digit? hex_digit? hex_digit?;
下面反斜线字符的字符(\
)是一个字符必须是以下字符中的一个:'
,"
,\
,0
,a
,b
,f
,n
,r
,t
,u
,U
,x
,v
。否则,发生编译时错误。
十六进制转义序列表示单个Unicode字符,其值由“ \x
” 后面的十六进制数字组成。
如果字符文字表示的值大于U+FFFF
,则发生编译时错误。
Unicode字符转义序列(Unicode字符转义序列中的字符)文本必须在范围U+0000
到U+FFFF
。
简单的转义序列表示Unicode字符编码,如下表所述。
逃脱序列 | 角色名字 | Unicode编码 |
---|---|---|
\' |
单引号 | 0x0027 |
\" |
双引号 | 0x0022 |
\\ |
反斜杠 | 0x005C |
\0 |
空值 | 0x0000 |
\a |
警报 | 0x0007 |
\b |
退格 | 0x0008 |
\f |
表格饲料 | 0x000C |
\n |
新队 | 0x000A |
\r |
回程 | 0x000D |
\t |
水平标签 | 0x0009 |
\v |
垂直标签 | 0x000B |
character_literal的类型是char
。
字符串文字
C#支持两种形式的字符串文字:常规字符串文字和逐字字符串文字。
常规字符串文字由用双引号括起来的零个或多个字符组成,例如"hello"
,可以包括简单转义序列(例如\t
制表符),以及十六进制和Unicode转义序列。
逐字字符串文字由一个@
字符后跟一个双引号字符,零个或多个字符以及一个结束双引号字符组成。一个简单的例子是@"hello"
。在逐字字符串文字中,分隔符之间的字符是逐字解释的,唯一的例外是quote_escape_sequence。特别是,简单的转义序列,十六进制和Unicode转义序列不会在逐字字符串文字中处理。逐字字符串文字可以跨越多行。
1 string_literal 2 : regular_string_literal 3 | verbatim_string_literal 4 ; 5 6 regular_string_literal 7 : '"' regular_string_literal_character* '"' 8 ; 9 10 regular_string_literal_character 11 : single_regular_string_literal_character 12 | simple_escape_sequence 13 | hexadecimal_escape_sequence 14 | unicode_escape_sequence 15 ; 16 17 single_regular_string_literal_character 18 : '<Any character except " (U+0022), \\ (U+005C), and new_line_character>' 19 ; 20 21 verbatim_string_literal 22 : '@"' verbatim_string_literal_character* '"' 23 ; 24 25 verbatim_string_literal_character 26 : single_verbatim_string_literal_character 27 | quote_escape_sequence 28 ; 29 30 single_verbatim_string_literal_character 31 : '<any character except ">' 32 ; 33 34 quote_escape_sequence 35 : '""' 36 ;
下面反斜线字符的字符(\
)是一个regular_string_literal_character必须是以下字符中的一个:'
,"
,\
,0
,a
,b
,f
,n
,r
,t
,u
,U
,x
,v
。否则,发生编译时错误。
这个例子
1 string a = "hello, world"; // hello, world 2 string b = @"hello, world"; // hello, world 3 4 string c = "hello \t world"; // hello world 5 string d = @"hello \t world"; // hello \t world 6 7 string e = "Joe said \"Hello\" to me"; // Joe said "Hello" to me 8 string f = @"Joe said ""Hello"" to me"; // Joe said "Hello" to me 9 10 string g = "\\\\server\\share\\file.txt"; // \\server\share\file.txt 11 string h = @"\\server\share\file.txt"; // \\server\share\file.txt 12 13 string i = "one\r\ntwo\r\nthree"; 14 string j = @"one 15 two 16 three";
显示各种字符串文字。最后一个字符串文字j
是一个跨越多行的逐字字符串文字。引号之间的字符(包括新行字符等空格)将逐字保留。
由于十六进制转义序列可以具有可变数量的十六进制数字,因此字符串文字"\x123"
包含具有十六进制值123的单个字符。要创建包含十六进制值12后跟字符3的字符的字符串,可以写入"\x00123"
或"\x12" + "3"
替换。
string_literal的类型是string
。
每个字符串文字不一定会产生新的字符串实例。当两个或多个根据字符串相等运算符(字符串相等运算符)等效的字符串文字出现在同一程序中时,这些字符串文字引用相同的字符串实例。例如,产生的输出
1 class Test 2 { 3 static void Main() { 4 object a = "hello"; 5 object b = "hello"; 6 System.Console.WriteLine(a == b); 7 } 8 }
是True
因为两个文字引用相同的字符串实例。
插值字符串文字
插值字符串文字类似于字符串文字,但包含由{
and 分隔的孔}
,其中可以出现表达式。在运行时,表达式的计算目的是将其文本形式替换为出现孔的位置的字符串。字符串插值的语法和语义在章节(插值字符串)中描述。
与字符串文字一样,插值字符串文字可以是常规字符串也可以是逐字字符串。插值的常规字符串文字由$"
和分隔"
,插值的逐字字符串文字由$@"
和分隔"
。
与其他文字一样,插值字符串文字的词法分析最初会产生单个标记,如下面的语法所示。然而,在句法分析之前,插值字符串文字的单个标记被分成几个标记,用于包围空洞的字符串部分,并且再次对词汇中出现的输入元素进行词法分析。这可能反过来产生更多内插的字符串文字进行处理,但是,如果词汇上的正确,最终会导致一系列令牌进行句法分析处理。
1 interpolated_string_literal 2 : '$' interpolated_regular_string_literal 3 | '$' interpolated_verbatim_string_literal 4 ; 5 6 interpolated_regular_string_literal 7 : interpolated_regular_string_whole 8 | interpolated_regular_string_start interpolated_regular_string_literal_body interpolated_regular_string_end 9 ; 10 11 interpolated_regular_string_literal_body 12 : regular_balanced_text 13 | interpolated_regular_string_literal_body interpolated_regular_string_mid regular_balanced_text 14 ; 15 16 interpolated_regular_string_whole 17 : '"' interpolated_regular_string_character* '"' 18 ; 19 20 interpolated_regular_string_start 21 : '"' interpolated_regular_string_character* '{' 22 ; 23 24 interpolated_regular_string_mid 25 : interpolation_format? '}' interpolated_regular_string_characters_after_brace? '{' 26 ; 27 28 interpolated_regular_string_end 29 : interpolation_format? '}' interpolated_regular_string_characters_after_brace? '"' 30 ; 31 32 interpolated_regular_string_characters_after_brace 33 : interpolated_regular_string_character_no_brace 34 | interpolated_regular_string_characters_after_brace interpolated_regular_string_character 35 ; 36 37 interpolated_regular_string_character 38 : single_interpolated_regular_string_character 39 | simple_escape_sequence 40 | hexadecimal_escape_sequence 41 | unicode_escape_sequence 42 | open_brace_escape_sequence 43 | close_brace_escape_sequence 44 ; 45 46 interpolated_regular_string_character_no_brace 47 : '<Any interpolated_regular_string_character except close_brace_escape_sequence and any hexadecimal_escape_sequence or unicode_escape_sequence designating } (U+007D)>' 48 ; 49 50 single_interpolated_regular_string_character 51 : '<Any character except \" (U+0022), \\ (U+005C), { (U+007B), } (U+007D), and new_line_character>' 52 ; 53 54 open_brace_escape_sequence 55 : '{{' 56 ; 57 58 close_brace_escape_sequence 59 : '}}' 60 ; 61 62 regular_balanced_text 63 : regular_balanced_text_part+ 64 ; 65 66 regular_balanced_text_part 67 : single_regular_balanced_text_character 68 | delimited_comment 69 | '@' identifier_or_keyword 70 | string_literal 71 | interpolated_string_literal 72 | '(' regular_balanced_text ')' 73 | '[' regular_balanced_text ']' 74 | '{' regular_balanced_text '}' 75 ; 76 77 single_regular_balanced_text_character 78 : '<Any character except / (U+002F), @ (U+0040), \" (U+0022), $ (U+0024), ( (U+0028), ) (U+0029), [ (U+005B), ] (U+005D), { (U+007B), } (U+007D) and new_line_character>' 79 | '</ (U+002F), if not directly followed by / (U+002F) or * (U+002A)>' 80 ; 81 82 interpolation_format 83 : interpolation_format_character+ 84 ; 85 86 interpolation_format_character 87 : '<Any character except \" (U+0022), : (U+003A), { (U+007B) and } (U+007D)>' 88 ; 89 90 interpolated_verbatim_string_literal 91 : interpolated_verbatim_string_whole 92 | interpolated_verbatim_string_start interpolated_verbatim_string_literal_body interpolated_verbatim_string_end 93 ; 94 95 interpolated_verbatim_string_literal_body 96 : verbatim_balanced_text 97 | interpolated_verbatim_string_literal_body interpolated_verbatim_string_mid verbatim_balanced_text 98 ; 99 100 interpolated_verbatim_string_whole 101 : '@"' interpolated_verbatim_string_character* '"' 102 ; 103 104 interpolated_verbatim_string_start 105 : '@"' interpolated_verbatim_string_character* '{' 106 ; 107 108 interpolated_verbatim_string_mid 109 : interpolation_format? '}' interpolated_verbatim_string_characters_after_brace? '{' 110 ; 111 112 interpolated_verbatim_string_end 113 : interpolation_format? '}' interpolated_verbatim_string_characters_after_brace? '"' 114 ; 115 116 interpolated_verbatim_string_characters_after_brace 117 : interpolated_verbatim_string_character_no_brace 118 | interpolated_verbatim_string_characters_after_brace interpolated_verbatim_string_character 119 ; 120 121 interpolated_verbatim_string_character 122 : single_interpolated_verbatim_string_character 123 | quote_escape_sequence 124 | open_brace_escape_sequence 125 | close_brace_escape_sequence 126 ; 127 128 interpolated_verbatim_string_character_no_brace 129 : '<Any interpolated_verbatim_string_character except close_brace_escape_sequence>' 130 ; 131 132 single_interpolated_verbatim_string_character 133 : '<Any character except \" (U+0022), { (U+007B) and } (U+007D)>' 134 ; 135 136 verbatim_balanced_text 137 : verbatim_balanced_text_part+ 138 ; 139 140 verbatim_balanced_text_part 141 : single_verbatim_balanced_text_character 142 | comment 143 | '@' identifier_or_keyword 144 | string_literal 145 | interpolated_string_literal 146 | '(' verbatim_balanced_text ')' 147 | '[' verbatim_balanced_text ']' 148 | '{' verbatim_balanced_text '}' 149 ; 150 151 single_verbatim_balanced_text_character 152 : '<Any character except / (U+002F), @ (U+0040), \" (U+0022), $ (U+0024), ( (U+0028), ) (U+0029), [ (U+005B), ] (U+005D), { (U+007B) and } (U+007D)>' 153 | '</ (U+002F), if not directly followed by / (U+002F) or * (U+002A)>' 154 ;
一个interpolated_string_literal令牌被重新解释为多个令牌和其他输入元件如下,在发生在顺序interpolated_string_literal:
- 下面的出现被重新解释为单独的单个令牌:前导
$
符号,interpolated_regular_string_whole,interpolated_regular_string_start,interpolated_regular_string_mid,interpolated_regular_string_end,interpolated_verbatim_string_whole,interpolated_verbatim_string_start,interpolated_verbatim_string_mid和interpolated_verbatim_string_end。 - 它们之间出现的regular_balanced_text和verbatim_balanced_text被重新处理为input_section(词法分析),并被重新解释为输入元素的结果序列。这些又可以包括要重新解释的内插字符串文字标记。
句法分析将令牌重新组合为interpolated_string_expression(插值字符串)。
示例ALL
null文字
1 null_literal 2 : 'null' 3 ;
的 null_literal可以隐式转换为引用类型或空类型。
运营商和标点符号
有几种运算符和标点符号。表达式中使用运算符来描述涉及一个或多个操作数的操作。例如,表达式a + b
使用+
运算符添加两个操作数a
和b
。标点符号用于分组和分隔。
1 operator_or_punctuator 2 : '{' | '}' | '[' | ']' | '(' | ')' | '.' | ',' | ':' | ';' 3 | '+' | '-' | '*' | '/' | '%' | '&' | '|' | '^' | '!' | '~' 4 | '=' | '<' | '>' | '?' | '??' | '::' | '++' | '--' | '&&' | '||' 5 | '->' | '==' | '!=' | '<=' | '>=' | '+=' | '-=' | '*=' | '/=' | '%=' 6 | '&=' | '|=' | '^=' | '<<' | '<<=' | '=>' 7 ; 8 9 right_shift 10 : '>>' 11 ; 12 13 right_shift_assignment 14 : '>>=' 15 ;
right_shift和right_shift_assignment产生中的竖线用于表示,与语法语法中的其他产品不同,令牌之间不允许任何类型的字符(甚至不是空格)。这些产品经过特殊处理,以便能够正确处理type_parameter_list(类型参数)。
预处理指令
预处理指令提供了有条件地跳过源文件部分,报告错误和警告条件以及描述源代码的不同区域的能力。术语“预处理指令”仅用于与C和C ++编程语言的一致性。在C#中,没有单独的预处理步骤; 预处理指令作为词法分析阶段的一部分进行处理。
1 pp_directive 2 : pp_declaration 3 | pp_conditional 4 | pp_line 5 | pp_diagnostic 6 | pp_region 7 | pp_pragma 8 ;
可以使用以下预处理指令:
#define
和#undef
,分别用于定义和取消定义条件编译符号(声明指令)。#if
,#elif
,#else
,和#endif
,其用于有条件跳过的源代码(第条件编译指令)。#line
,用于控制为错误和警告发出的行号(行指令)。#error
并且#warning
,它们分别用于发出错误和警告(诊断指令)。#region
和#endregion
,用于显式标记源代码的部分(Region指令)。#pragma
,用于为编译器指定可选的上下文信息(Pragma指令)。
预处理指令始终占用单独的源代码行,并始终以#
字符和预处理指令名称开头。#
字符前面以及#
字符和指令名称之间可能出现空格。
含有源极线#define
,#undef
,#if
,#elif
,#else
,#endif
,#line
,或#endregion
指令可以用单个行注释结束。/* */
包含预处理指令的源代码行不允许使用分隔注释(注释样式)。
预处理指令不是令牌,也不是C#语法语法的一部分。但是,预处理指令可用于包含或排除令牌序列,并且可以以这种方式影响C#程序的含义。例如,编译时,程序:
1 #define A 2 #undef B 3 4 class C 5 { 6 #if A 7 void F() {} 8 #else 9 void G() {} 10 #endif 11 12 #if B 13 void H() {} 14 #else 15 void I() {} 16 #endif 17 }
导致与程序完全相同的令牌序列:
1 class C 2 { 3 void F() {} 4 void I() {} 5 }
因此,而词汇上,两个程序是完全不同的,从语法上讲,它们是相同的。
条件编译符号
通过所提供的条件编译功能#if
,#elif
,#else
,和#endif
指令是通过预处理表达式(受控预处理表达式)和条件编译符号。
1 conditional_symbol 2 : '<Any identifier_or_keyword except true or false>' 3 ;
条件编译符号有两种可能的状态:已定义或未定义。在源文件的词法处理开始时,条件编译符号是未定义的,除非它已由外部机制(例如命令行编译器选项)显式定义。#define
处理指令时,该指令中指定的条件编译符号将在该源文件中定义。符号保持定义,直到#undef
处理该相同符号的指令,或者直到达到源文件的末尾。这方面的一个含义是,#define
和#undef
在一个源文件中的指令在同一程序中的其他源文件没有任何影响。
在预处理表达式中引用时,定义的条件编译符号具有布尔值true
,未定义的条件编译符号具有布尔值false
。在预处理表达式中引用条件编译符号之前,不要求显式声明它们。相反,未声明的符号只是未定义的,因此具有价值false
。
条件编译符号的名称空间是不同的,并且与C#程序中的所有其他命名实体分开。条件编译符号只能在#define
和#undef
指令以及预处理表达式中引用。
预处理表达式
预处理表达式可以出现在#if
和#elif
指令中。运营商!
,==
,!=
,&&
和||
被允许在预处理表达式,并且可以使用用于分组括号。
1 pp_expression 2 : whitespace? pp_or_expression whitespace? 3 ; 4 5 pp_or_expression 6 : pp_and_expression 7 | pp_or_expression whitespace? '||' whitespace? pp_and_expression 8 ; 9 10 pp_and_expression 11 : pp_equality_expression 12 | pp_and_expression whitespace? '&&' whitespace? pp_equality_expression 13 ; 14 15 pp_equality_expression 16 : pp_unary_expression 17 | pp_equality_expression whitespace? '==' whitespace? pp_unary_expression 18 | pp_equality_expression whitespace? '!=' whitespace? pp_unary_expression 19 ; 20 21 pp_unary_expression 22 : pp_primary_expression 23 | '!' whitespace? pp_unary_expression 24 ; 25 26 pp_primary_expression 27 : 'true' 28 | 'false' 29 | conditional_symbol 30 | '(' whitespace? pp_expression whitespace? ')' 31 ;
在预处理表达式中引用时,定义的条件编译符号具有布尔值true
,未定义的条件编译符号具有布尔值false
。
预处理表达式的评估总是产生布尔值。预处理表达式的评估规则与常量表达式(常量表达式)的评估规则相同,只是可以引用的唯一用户定义实体是条件编译符号。
声明指令
声明指令用于定义或取消定义条件编译符号。
1 pp_declaration 2 : whitespace? '#' whitespace? 'define' whitespace conditional_symbol pp_new_line 3 | whitespace? '#' whitespace? 'undef' whitespace conditional_symbol pp_new_line 4 ; 5 6 pp_new_line 7 : whitespace? single_line_comment? new_line 8 ;
#define
指令的处理导致给定的条件编译符号被定义,从指令后面的源代码行开始。同样,#undef
指令的处理会导致给定的条件编译符号变为未定义,从指令后面的源代码行开始。
源文件中的任何#define
和#undef
指令必须在源文件中的第一个标记(标记)之前发生; 否则会发生编译时错误。直观地讲,#define
与#undef
指令必须位于源文件中所有“实代码”。
这个例子:
1 #define Enterprise 2 3 #if Professional || Enterprise 4 #define Advanced 5 #endif 6 7 namespace Megacorp.Data 8 { 9 #if Advanced 10 class PivotTable {...} 11 #endif 12 }
是有效的,因为#define
指令在namespace
源文件中的第一个标记(关键字)之前。
以下示例导致编译时错误,因为#define
以下实际代码:
1 #define A 2 namespace N 3 { 4 #define B 5 #if B 6 class Class1 {} 7 #endif 8 }
A #define
可以定义已经定义的条件编译符号,而不#undef
对该符号进行任何干预。下面的示例定义了条件编译符号A
,然后再次定义它。
1 #define A 2 #define A
A #undef
可以“取消定义”未定义的条件编译符号。下面的示例定义了条件编译符号A
,然后将其取消定义两次; 虽然第二个#undef
没有效果,但它仍然有效。
1 #define A 2 #undef A 3 #undef A
条件编译指令
条件编译指令用于有条件地包含或排除源文件的各个部分。
1 pp_conditional 2 : pp_if_section pp_elif_section* pp_else_section? pp_endif 3 ; 4 5 pp_if_section 6 : whitespace? '#' whitespace? 'if' whitespace pp_expression pp_new_line conditional_section? 7 ; 8 9 pp_elif_section 10 : whitespace? '#' whitespace? 'elif' whitespace pp_expression pp_new_line conditional_section? 11 ; 12 13 pp_else_section: 14 | whitespace? '#' whitespace? 'else' pp_new_line conditional_section? 15 ; 16 17 pp_endif 18 : whitespace? '#' whitespace? 'endif' pp_new_line 19 ; 20 21 conditional_section 22 : input_section 23 | skipped_section 24 ; 25 26 skipped_section 27 : skipped_section_part+ 28 ; 29 30 skipped_section_part 31 : skipped_characters? new_line 32 | pp_directive 33 ; 34 35 skipped_characters 36 : whitespace? not_number_sign input_character* 37 ; 38 39 not_number_sign 40 : '<Any input_character except #>' 41 ;
如语法所示,条件编译指令必须按顺序编写,包括#if
指令,零个或多个#elif
指令,零个或一个#else
指令以及#endif
指令。指令之间是源代码的条件部分。每个部分由前一个指令控制。条件部分本身可以包含嵌套的条件编译指令,前提是这些指令形成完整集。
甲pp_conditional选择含有的至多一个conditional_section S表示正常词法处理:
- 所述pp_expression的s-
#if
和#elif
指令,以便进行计算,直到一个产量true
。如果表达式产生true
,则选择相应指令的conditional_section。 - 如果所有pp_expression都产生
false
,并且如果#else
存在指令,则选择该指令的conditional_section#else
。 - 否则,不选择conditional_section。
选定的conditional_section(如果有)将作为普通input_section处理:该部分中包含的源代码必须符合词法语法; 令牌是从该部分的源代码生成的; 本节中的预处理指令具有规定的效果。
剩下的conditional_section(如果有的话)被处理为skipped_section:除了预处理指令之外,本节中的源代码不需要遵循词法语法; 该部分的源代码不生成令牌; 并且该部分中的预处理指令必须在词法上正确,但不以其他方式处理。内的conditional_section正被处理为skipped_section,任何嵌套conditional_section(包含在嵌套小号#if
... #endif
和#region
... #endregion
构建体)也被处理为skipped_section秒。
以下示例说明了条件编译指令如何嵌套:
1 #define Debug // Debugging on 2 #undef Trace // Tracing off 3 4 class PurchaseTransaction 5 { 6 void Commit() { 7 #if Debug 8 CheckConsistency(); 9 #if Trace 10 WriteToLog(this.ToString()); 11 #endif 12 #endif 13 CommitHelper(); 14 } 15 }
除预处理指令外,跳过的源代码不受词法分析的限制。例如,尽管该#else
部分中有未确定的评论,但以下内容仍然有效:
1 #define Debug // Debugging on 2 3 class PurchaseTransaction 4 { 5 void Commit() { 6 #if Debug 7 CheckConsistency(); 8 #else 9 /* Do something else 10 #endif 11 } 12 }
但请注意,即使在源代码的跳过部分中,预处理指令也必须在词法上正确。
当预处理指令出现在多行输入元素中时,不会处理它们。例如,程序:
1 class Hello 2 { 3 static void Main() { 4 System.Console.WriteLine(@"hello, 5 #if Debug 6 world 7 #else 8 Nebraska 9 #endif 10 "); 11 } 12 }
结果输出:
1 hello, 2 #if Debug 3 world 4 #else 5 Nebraska 6 #endif
在特殊情况下,处理的预处理指令集可能取决于pp_expression的评估。这个例子:
1 #if X 2 /* 3 #else 4 /* */ class Q { } 5 #endif
class
Q
{
}
无论是否X
定义,始终生成相同的令牌流()。如果X
已定义,则由于多行注释,唯一处理的指令是#if
和#endif
。如果X
是不确定的,则这三个指令(#if
,#else
,#endif
)是指令集的组成部分。
诊断指令
诊断指令用于显式生成以与其他编译时错误和警告相同的方式报告的错误和警告消息。
1 pp_diagnostic 2 : whitespace? '#' whitespace? 'error' pp_message 3 | whitespace? '#' whitespace? 'warning' pp_message 4 ; 5 6 pp_message 7 : new_line 8 | whitespace input_character* new_line 9 ;
这个例子:
1 #warning Code review needed before check-in 2 3 #if Debug && Retail 4 #error A build can't be both debug and retail 5 #endif 6 7 class Test {...}
总是产生一个警告(“登记前需要进行代码审查”),并且如果条件符号Debug
和Retail
两者都被定义,则产生编译时错误(“构建不能同时是调试和零售”)。请注意,pp_message可以包含任意文本; 具体而言,它不需要包含格式良好的标记,如单词中的单引号所示can't
。
区域指令
region指令用于显式标记源代码区域。
1 pp_region 2 : pp_start_region conditional_section? pp_end_region 3 ; 4 5 pp_start_region 6 : whitespace? '#' whitespace? 'region' pp_message 7 ; 8 9 pp_end_region 10 : whitespace? '#' whitespace? 'endregion' pp_message 11 ;
区域没有语义含义; 区域旨在供程序员或自动化工具用于标记源代码的一部分。#region
或#endregion
指令中指定的消息同样没有语义含义; 它只是用来识别该地区。匹配#region
和#endregion
指令可能有不同的pp_message。
一个地区的词汇处理:
1 #region 2 ... 3 #endregion
完全对应于表单的条件编译指令的词法处理:
1 #if true 2 ... 3 #endif
行指令
行指令可用于更改编译器在输出中报告的行号和源文件名,例如警告和错误,以及调用者信息属性(调用者信息属性)使用的行号。
行指令最常用于从其他文本输入生成C#源代码的元编程工具。
1 pp_line 2 : whitespace? '#' whitespace? 'line' whitespace line_indicator pp_new_line 3 ; 4 5 line_indicator 6 : decimal_digit+ whitespace file_name 7 | decimal_digit+ 8 | 'default' 9 | 'hidden' 10 ; 11 12 file_name 13 : '"' file_name_character+ '"' 14 ; 15 16 file_name_character 17 : '<Any input_character except ">' 18 ;
如果没有#line
指令,编译器会在其输出中报告真行号和源文件名。当处理#line
指令,其包括一个line_indicator不是default
,编译器的指令之后的行视为具有给定行号(和文件名,如果指定)。
一个#line default
指令消除前面所有#line指令的影响。编译器报告后续行的真#line
实行信息,就像没有处理任何指令一样。
一个#line hidden
指令对错误信息中报告的文件和行号没有影响,但对源代码级调试。在调试时,#line hidden
指令和后续#line
指令(不是#line hidden
)之间的所有行都没有行号信息。在调试器中单步执行代码时,将完全跳过这些行。
请注意,file_name与常规字符串文字的不同之处在于不处理转义字符; “ \
”字符只是在file_name中指定一个普通的反斜杠字符。
Pragma指令
该#pragma
预处理指令用于指定可选的上下文信息的编译器。#pragma
指令中提供的信息永远不会改变程序语义。
1 pp_pragma 2 : whitespace? '#' whitespace? 'pragma' whitespace pragma_body pp_new_line 3 ; 4 5 pragma_body 6 : pragma_warning_body 7 ;
C#提供#pragma
了控制编译器警告的指令。该语言的未来版本可能包括其他#pragma
指令。为确保与其他C#编译器的互操作性,Microsoft C#编译器不会为未知#pragma
指令发出编译错误; 但是,这样的指令会产生警告。
语用警告
该#pragma warning
指令用于在编译后续程序文本期间禁用或恢复所有或一组特定的警告消息。
1 pragma_warning_body 2 : 'warning' whitespace warning_action 3 | 'warning' whitespace warning_action whitespace warning_list 4 ; 5 6 warning_action 7 : 'disable' 8 | 'restore' 9 ; 10 11 warning_list 12 : decimal_digit+ (whitespace? ',' whitespace? decimal_digit+)* 13 ;
一个#pragma warning
省略了警告列表中的指令将影响所有警告。一个#pragma warning
指令中包含警告列表只影响在列表中指定的警告。
一个#pragma warning disable
指令禁止所有或给定的警告。
一个#pragma warning restore
指令恢复所有或给定的警告,这是有效的编译单元开始的状态。请注意,如果在外部禁用了特定警告,则#pragma warning restore
无论是对于所有警告还是特定警告,都不会重新启用该警告。
以下示例显示了使用#pragma warning
Microsoft C#编译器中的警告编号临时禁用引用过时成员时报告的警告。
1 using System; 2 3 class Program 4 { 5 [Obsolete] 6 static void Foo() {} 7 8 static void Main() { 9 #pragma warning disable 612 10 Foo(); 11 #pragma warning restore 612 12 } 13 }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)