C# 语言规范_版本5.0 (第5章 变量)
1. 变量
变量表示存储位置。每个变量都具有一个类型,用于确定哪些值可以存储在该变量中。C# 是一种类型安全的语言,C# 编译器保证存储在变量中的值总是具有合适的类型。通过赋值或使用 ++ 和 ‑‑ 运算符可以更改变量的值。
在可以获取变量的值之前,变量必须已明确赋值 (definitely assigned)(第 5.3 节)。
如下面的章节所述,变量是初始已赋值 (initially assigned) 或初始未赋值 (initially unassigned)。初始已赋值的变量有一个正确定义了的初始值,并且总是被视为已明确赋值。初始未赋值的变量没有初始值。为了使初始未赋值的变量在某个位置被视为已明确赋值,变量赋值必须发生在通向该位置的每个可能的执行路径中。
1.1 变量类别
C# 定义了 7 类变量:静态变量、实例变量、数组元素、值参数、引用参数、输出参数和局部变量。后面的章节将介绍其中的每一种类别。
在下面的示例中
class A
{
public static int x;
int y;
void F(int[] v, int a, ref int b, out int c) {
int i = 1;
c = a + b++;
}
}
x 是静态变量,yy 是实例变量,v[0] 是数组元素,a 是值参数,b 是引用参数,c 是输出参数,i 是局部变量。
1.1.1 静态变量
用 static 修饰符声明的字段称为静态变量 (static variable)。静态变量在包含了它的那个类型的静态构造函数(第 10.12 节)执行之前就存在了,在退出关联的应用程序域时不复存在。
静态变量的初始值是该变量的类型的默认值(第 5.2 节)。
出于明确赋值检查的目的,静态变量被视为初始已赋值。
1.1.2 实例变量
未用 static 修饰符声明的字段称为实例变量 (instance variable)。
1.1.2.1 类中的实例变量
类的实例变量在创建该类的新实例时开始存在,在所有对该实例的引用都已终止,并且已执行了该实例的析构函数(若有)时终止。
类实例变量的初始值是该变量的类型的默认值(第 5.2 节)。
出于明确赋值检查的目的,类的实例变量被视为初始已赋值。
1.1.2.2 结构中的实例变量
结构的实例变量与它所属的结构变量具有完全相同的生存期。换言之,当结构类型的变量开始存在或停止存在时,该结构的实例变量也随之存在或消失。
结构的实例变量与包含它的结构变量具有相同的初始赋值状态。换言之,当结构变量本身被视为初始已赋值时,它的实例变量也被视为初始已赋值。而当结构变量被视为初始未赋值时,它的实例变量同样被视为未赋值。
1.1.3 数组元素
数组的元素在创建数组实例时开始存在,在没有对该数组实例的引用时停止存在。
每个数组元素的初始值都是其数组元素类型的默认值(第 5.2 节)。
出于明确赋值检查的目的,数组元素被视为初始已赋值。
1.1.4 值参数
未用 ref 或 out 修饰符声明的参数为值参数 (value parameter)。
值形参在调用该形参所属的函数成员(方法、实例构造函数、访问器或运算符)或匿名函数时开始存在,并用调用中给定的实参的值初始化。当返回该函数成员或匿名函数时值形参通常停止存在。但是,如果值形参被匿名函数(第 7.15 节)捕获,则其生存期将至少延长到从该匿名函数创建的委托或表达式目录树可以被垃圾回收为止。
出于明确赋值检查的目的,值形参被视为初始已赋值。
1.1.5 引用参数
引用形参是用 ref 修饰符声明的形参。
引用形参不创建新的存储位置。它表示在对该函数成员或匿名函数调用中以实参形式给出的变量所在的存储位置。因此,引用形参的值总是与基础变量相同。
下面的明确赋值规则适用于引用形参。注意第 5.1.6 节中描述的输出形参的不同规则。
- 变量在可以作为引用形参在函数成员或委托调用中传递之前,必须已明确赋值(第 5.3 节)。
- 在函数成员或匿名函数内部,引用形参被视为初始已赋值。
在结构类型的实例方法或实例访问器内部,this 关键字的行为与该结构类型的引用形参完全相同(第 7.6.7 节)。
1.1.6 输出形参
用 out 修饰符声明的形参是输出形参。
输出形参不创建新的存储位置。而输出形参表示在对该函数成员或委托调用中以实参形式给出的变量所在的存储位置。因此,输出形参的值总是与基础变量相同。
下面的明确赋值规则应用于输出形参。注意第 5.1.5 节中描述的引用形参的不同规则。
- 变量在可以作为输出形参在函数成员或委托调用中传递之前无需明确赋值。
- 在正常完成函数成员或委托调用之后,每个作为输出形参传递的变量都被认为在该执行路径中已赋值。
- 在函数成员或匿名函数内部,输出形参被视为初始未赋值。
- 函数成员或匿名函数的每个输出形参在该函数成员或匿名函数正常返回前都必须已明确赋值(第 5.3 节)。
在结构类型的实例构造函数内部,this 关键字的行为与结构类型的输出形参完全相同(第 7.6.7 节)。
1.1.7 局部变量
局部变量 (local variable) 可通过 local-variable-declaration 来声明,此声明可以出现在 block、for-statement、switch-statement 或 using-statement 中;也可由 foreach-statement 或 try-statement 的 specific-catch-clause 来声明。
局部变量的生存期是程序执行过程中的某一“段”,在此期间,一定会为该局部变量保留存储。此生存期从进入关联的 block、for-statement、switch-statementusing-statement、foreach-statement 或specific-catch-clause 开始,至少延长到该 block、for-statement、switch-statement、using-statement、foreach-statement 或 specific-catch-clause 以任何方式结束为止。(进入封闭 block 或调用方法会挂起(但不会结束)当前的 block、for-statement、switch-statement、using-statement、foreach-statement 或 specific-catch-clause 的执行)。如果局部变量被匿名函数捕获(第 7.15.5.1 节),其生存期将至少延长到从该匿名函数创建的委托或表达式目录树以及引用该被捕获变量的其他所有对象可以被垃圾回收为止。
如果以递归方式进入父 block、for-statement、switch-statement、using-statement、foreach-statement 或 specific-catch-clause,则每次都创建局部变量的新实例,并且重新计算它的 local-variable-initializer(如果有)。
由 local-variable-declaration 引入的局部变量不自动初始化,因此没有默认值。出于明确赋值检查的目的,由 local-variable-declaration 引入的局部变量被视为未赋初始值。local-variable-declaration 可包括 local-variable-initializer,在此情况下,只有在初始化表达式之后,变量才被视为已明确赋值(第 5.3.3.4 节)。
在由 local-variable-declaration 引入的局部变量的范围内,在 local-variable-declarator 之前的文本位置引用该局部变量是编译时错误。如果局部变量的声明是隐式的(第 8.5.1 节),则在 local-variable-declarator 内引用该变量也是错误的。
foreach-statement 或 specific-catch-clause 引入的局部变量被视为在它的整个范围内已明确赋值。
局部变量的实际生存期依赖于具体实现。例如,编译器可能静态地确定块中的某个局部变量只用于该块的一小部分。使用这种分析,编译器生成的代码可能会使该变量存储的生存期短于包含该变量的块的生存期。
局部引用变量所引用的存储的回收与该局部引用变量(第 3.9 节)的生存期无关。
1.2 默认值
以下类别的变量自动初始化为它们的默认值:
- 静态变量。
- 类实例的实例变量。
- 数组元素。
变量的默认值取决于该变量的类型,并按下如下规则确定:
- 对于 value-type 的变量,默认值与该 value-type 的默认构造函数(第 4.1.2 节)所计算的值相同。
- 对于 reference-type 的变量,默认值为 null。
初始化为默认值的实现方法一般是让内存管理器或垃圾回收器在分配内存以供使用之前,将内存初始化为“所有位归零”。由于这个原因,使用所有位归零来表示 null 引用很方便。
1.3 明确赋值
在函数成员可执行代码中的给定位置,如果编译器可通过特定的静态流程分析(第 5.3.3 节)证明变量已自动初始化或已成为至少一个赋值的目标,则称该变量已明确赋值 (definitely assigned)。非正式地讲,明确赋值的规则为:
- 初始已赋值的变量(第 5.3.1 节)总是被视为已明确赋值。
- 如果所有可能通向给定位置的执行路径都至少包含以下内容之一,则初始未赋值的变量(第 5.3.2 节)被视为在该位置已明确赋值:
- 将变量作为左操作数的简单赋值(第 7.17.1 节)。
- 将变量作为输出形参传递的调用表达式(第 7.6.5 节)或对象创建表达式(第 7.6.10.1 节)。
- 对于局部变量,包含变量初始值设定项的局部变量声明(第 8.5.1 节)。
以上非正式规则所基于的正式规范在第 5.3.1 节、第 5.3.2 节和第 5.3.3 节中说明。
关于对一个 struct-type 变量的实例变量是否明确赋值,既可个别地也可作为整体进行跟踪。除了上述规则,下面的规则也应用于 struct-type 变量及其实例变量:
- 如果一个实例变量的包含它的那个 struct-type 变量被视为已明确赋值,则该实例变量被视为已明确赋值。
- 如果一个 struct-type 变量的每个实例变量都被视为已明确赋值,则该结构类型变量被视为已明确赋值。
在下列上下文中要求实施明确赋值:
- 变量必须在获取其值的每个位置都已明确赋值。这确保了从来不会出现未定义的值。变量在表达式中出现被视为要获取该变量的值,除非当
- 该变量为简单赋值的左操作数,
- 该变量作为输出形参传递,或者
- 该变量为 struct-type 变量并作为成员访问的左操作数出现。
- 变量必须在它作为引用形参传递的每个位置都已明确赋值。这确保了被调用的函数成员可以将引用形参视为初始已赋值。
- 函数成员的所有输出形参必须在函数成员返回的每个位置都已明确赋值,返回位置包括通过 return 语句实现的返回,或者通过执行语句到达函数成员体结尾的返回。这确保了函数成员不在输出形参中返回未定义的值,从而使编译器能够把一个对函数成员的调用当作对某些变量的赋值,这些变量在该调用中被当作输出形参传递。
- struct-type 实例构造函数的 this 变量必须在该实例构造函数返回的每个位置明确赋值。
1.3.1 初始已赋值变量
以下类别的变量属于初始已赋值变量:
- 静态变量。
- 类实例的实例变量。
- 初始已赋值结构变量的实例变量。
- 数组元素。
- 值形参。
- 引用参数。
- 在 catch 子句或 foreach 语句中声明的变量。
1.3.2 初始未赋值变量
以下类别的变量属于初始未赋值变量:
- 初始未赋值结构变量的实例变量。
- 输出形参,包括结构实例构造函数的 this 变量。
- 局部变量,在 catch 子句或 foreach 语句中声明的那些除外。
1.3.3 确定明确赋值的细则
为了确定每个已使用变量都已明确赋值,编译器必须使用与本节中描述的进程等效的进程。
编译器处理每个具有一个或多个初始未赋值变量的函数成员的体。对于每个初始未赋值的变量 v,编译器在函数成员中的下列每个点上确定 v 的明确赋值状态 (definite assignment state):
- 在每个语句的开头处
- 在每个语句的结束点(第 8.1 节)
- 在每个将控制转移到另一个语句或语句结束点的 arc 上
- 在每个表达式的开头处
- 在每个表达式的结尾处
v 的明确赋值状态可以是:
- 明确赋值。这表明在能达到该点的所有可能的控制流上,v 都已赋值。
- 未明确赋值。当在 bool 类型表达式结尾处确定变量的状态时,未明确赋值的变量的状态可能(但不一定)属于下列子状态:
- 在 true 表达式后明确赋值。此状态表明如果该布尔表达式计算为 true,则 v 是明确赋值的,但如果布尔表达式计算为 false,则不一定要赋值。
- 在 false 表达式后明确赋值。此状态表明如果该布尔表达式计算为 false,则 v 是明确赋值的,但如果布尔表达式计算为 true,则不一定要赋值。
下列规则控制变量 v 的状态在每个位置是如何确定的。
1.3.3.1 一般语句规则
- v 在函数成员体的开头处不是明确赋值的。
- v 在任何无法访问的语句的开头处都是明确赋值的。
- 在任何其他语句开头处,为了确定 v 的明确赋值状态,请检查以该语句开头处为目标的所有控制流转移上的 v 的明确赋值状态。当且仅当 v 在所有此类控制流转移上是明确赋值的时,v 才在该语句的开始处明确赋值。确定可能的控制流转移集的方法与检查语句可访问性的方法(第 8.1 节)相同。
- 在块、checked、unchecked、if、while、 do、for、foreach、lock、using 或 switch 等语句的结束点处,为了确定 v 的明确赋值状态,需检查以该语句结束点为目标的所有控制流转移上的 v 的明确赋值状态。如果 v 在所有此类控制流转移上是明确赋值的,则 v 在该语句结束点明确赋值。否则,v 在语句结束点处不是明确赋值的。确定可能的控制流转移集的方法与检查语句可访问性的方法(第 8.1 节)相同。
1.3.3.2 块语句、checked 和 unchecked 语句
在指向位于某块中语句列表的第一个语句(如果语句列表为空,则指向该块的结束点)的控制转移上,v 的明确赋值状态与块语句、checked 或 unchecked 语句之前的 v 的明确赋值状态相同。
1.3.3.3 表达式语句
对于由表达式 expr 组成的表达式语句 stmt:
- v 在 expr 的开头处与在 stmt 的开头处具有相同的明确赋值状态。
- 如果 v 在 expr 的结尾处明确赋值,则它在 stmt 的结束点也明确赋值;否则,它在 stmt 的结束点也不明确赋值。
- 如果 stmt 是不带有初始值设定项的声明语句,则 v 在 stmt 的结束点与在 stmt 的开头处具有相同的明确赋值状态。
- 如果 stmt 是带有初始值设定项的声明语句,则确定 v 的明确赋值状态时可把 stmt 当作一个语句列表,其中每个带有初始值设定项的声明对应一个赋值语句(按声明的顺序)。
1.3.3.4 声明语句
1.3.3.5 if 语句
对于以下形式的 if 语句 stmt:
if ( expr ) then-stmt else else-stmt
- v 在 expr 的开头处与在 stmt 的开头处具有相同的明确赋值状态。
- 如果 v 在 expr 的结尾处明确赋值,则它在指向 then-stmt 和 else-stmt 或指向 stmt 的结束点(如果没有 else 子句)的控制流转移上是明确赋值的。
- 如果 v 在 expr 的结尾处具有“在 true 表达式后明确赋值”状态,则它在指向 then-stmt 的控制流转移上是明确赋值的,在指向 else-stmt 或指向 stmt 的结束点(如果没有 else 子句)的控制流转移上不是明确赋值的。
- 如果 v 在 expr 的结尾处具有“在 false 表达式后明确赋值”状态,则它在指向 else-stmt 的控制流转移上是明确赋值的,在指向 then-stmt 的控制流转移上不是明确赋值的。此后,当且仅当它在 stmt 的结束点是明确赋值的时,它在 then-stmt 的结束点才是明确赋值的。
- 否则,认为 v 在指向 then-stmt 或 else-stmt,或指向 stmt 的结束点(如果没有 else 子句)的控制流转移上都不是明确赋值的。
1.3.3.6 switch 语句
在带有控制表达式 expr 的 switch 语句 stmt 中:
- 位于 expr 开头处的 v 的明确赋值状态与位于 stmt 开头处的 v 的状态相同。
- 在指向可访问的 switch 块语句列表的控制流转移上,v 的明确赋值状态就是 v 在 expr 结尾处的明确赋值状态。
1.3.3.7 while 语句
对于以下形式的 while 语句 stmt:
while ( expr ) while-body
- v 在 expr 的开头处与在 stmt 的开头处具有相同的明确赋值状态。
- 如果 v 在 expr 的结尾处明确赋值,则它在指向 while-body 和指向 stmt 结束点的控制流转移上是明确赋值的。
- 如果 v 在 expr 的结尾处具有“在 true 表达式后明确赋值”状态,则它在指向 while-body 的控制流转移上是明确赋值的,但在 stmt 的结束点处不是明确赋值的。
- 如果 v 在 expr 的结尾处具有“在 false 表达式后明确赋值”状态,则它在指向 stmt 的结束点的控制流转移上是明确赋值的,但在指向 while-body 的控制流转移上不是明确赋值的。
1.3.3.8 do 语句
对于以下形式的 do 语句 stmt:
do do-body while ( expr ) ;
- v 在从 stmt 的开头处到 do-body 的控制流转移上的明确赋值状态与在 stmt 的开头处的状态相同。
- v 在 expr 的开头处与在 do-body 的结束点具有相同的明确赋值状态。
- 如果 v 在 expr 的结尾处是明确赋值的,则它在指向 stmt 的结束点的控制流转移上是明确赋值的。
- 如果 v 在 expr 的结尾处的状态为“在 false 表达式后明确赋值”,则它在指向 stmt 的结束点的控制流转移上是明确赋值的。
1.3.3.9 for 语句
对具有以下形式的 for 语句进行的明确赋值检查:
for ( for-initializer ; for-condition ; for-iterator ) embedded-statement
就如执行下列语句一样:
{
for-initializer ;
while ( for-condition ) {
embedded-statement ;
for-iterator ;
}
}
如果 for 语句中省略了 for-condition,则在确定关于明确赋值的状态时,可把上述展开语句列表中的 for-condition 当作 true。
1.3.3.10 break、continue 和 goto 语句
由 break、continue 或 goto 语句引起的控制流转移上的 v 的明确赋值状态与 v 在该语句开头处的明确赋值状态是一样的。
1.3.3.11 throw 语句
对于以下形式的语句 stmt
throw expr ;
位于 expr 开头处的 v 的明确赋值状态与位于 stmt 开头处的 v 的明确赋值状态相同。
1.3.3.12 return 语句
对于以下形式的语句 stmt
return expr ;
- 位于 expr 开头处的 v 的明确赋值状态与位于 stmt 开头处的 v 的明确赋值状态相同。
- 如果 v 是输出形参,则它必须在下列两个位置之一被明确赋值:
- 在 expr 之后
- 或者在包含 return 语句的 try-finally 或 try-catch-finally 的 finally 块的结尾处。
对于以下形式的语句 stmt:
return ;
- 如果 v 是输出形参,则它必须在下列两个位置之一被明确赋值:
- 在 stmt 之前
- 或者在包含 return 语句的 try-finally 或 try-catch-finally 的 finally 块的结尾处。
1.3.3.13 try-catch 语句
对于以下形式的语句 stmt:
try try-block
catch(...) catch-block-1
...
catch(...) catch-block-n
- 位于 try-block 开头处的 v 的明确赋值状态与位于 stmt 开头处的 v 的明确赋值状态相同。
- 位于 catch-block-i(对于所有的 i)开头处的 v 的明确赋值状态与位于 stmt 开头处的 v 的明确赋值状态相同。
- 当且仅当 v 在 try-block 和每个 catch-block-i(每个 i 从 1 到 n)的结束点明确赋值时,stmt 结束点处的 v 的明确赋值状态才是明确赋值的。
1.3.3.14 try-finally 语句
对于以下形式的 try 语句 stmt:
try try-block finally finally-block
- 位于 try-block 开头处的 v 的明确赋值状态与位于 stmt 开头处的 v 的明确赋值状态相同。
- 位于 finally-block 开头处的 v 的明确赋值状态与位于 stmt 开头处的 v 的明确赋值状态相同。
- 当且仅当下列条件中至少有一个为真时,位于 stmt 结束点处的 v 的明确赋值状态才是明确赋值的:
- v 在 try-block 的结束点明确赋值
- v 在 finally-block 的结束点明确赋值
如果控制流转移(例如,goto 语句)从 try-block 内开始,在 try-block 外结束,那么如果 v 在 finally-block 的结束点明确赋值,v 也被认为在该控制流转移上明确赋值。(这不是必要条件,如果 v 由于其他原因在该控制流转移上明确赋值,则它仍被视为明确赋值)。
1.3.3.15 try-catch-finally 语句
对具有以下形式的 try-catch-finally 语句进行的明确赋值分析:
try try-block
catch(...) catch-block-1
...
catch(...) catch-block-n
finally finally-block
在进行明确赋值分析时,可把该语句当作包含了 try-catch 语句的 try-finally 语句,如下所示:
try {
try try-block
catch(...) catch-block-1
...
catch(...) catch-block-n
}
finally finally-block
下面的示例演示 try 语句(第 8.10 节)的不同块如何影响明确赋值状态。 \r \h
class A
{
static void F() {
int i, j;
try {
goto LABEL;
// neither i nor j definitely
assigned
i = 1;
// i definitely assigned
}
catch
{
// neither i nor j definitely
assigned
i = 3;
// i definitely assigned
}
finally
{
// neither i nor j definitely
assigned
j = 5;
// j definitely assigned
}
// i and j definitely assigned
LABEL:;
// j definitely assigned
}
}
1.3.3.16 foreach 语句
对于以下形式的 foreach 语句 stmt:
foreach ( type identifier in expr ) embedded-statement
- 位于 expr 开头处的 v 的明确赋值状态与位于 stmt 开头处的 v 的状态相同。
- 在指向 embedded-statement 或指向 stmt 结束点处的控制流转移上,v 的明确赋值状态与位于 expr 结尾处的 v 的状态相同。
1.3.3.17 using 语句
对于以下形式的 using 语句 stmt:
using ( resource-acquisition ) embedded-statement
- 位于 resource-acquisition 开头处的 v 的明确赋值状态与位于 stmt 开头处的 v 的状态相同。
- 在指向 embedded-statement 的控制流转移上,v 的明确赋值状态与位于 resource-acquisition 结尾处的 v 的状态相同。
1.3.3.18 lock 语句
对于以下形式的 lock 语句 stmt:
lock ( expr ) embedded-statement
- 位于 expr 开头处的 v 的明确赋值状态与位于 stmt 开头处的 v 的状态相同。
- 在指向 embedded-statement 的控制流转移上,v 的明确赋值状态与位于 expr 结尾处的 v 的状态相同。
1.3.3.19 yield 语句
对于以下形式的 yield return 语句 stmt:
yield return expr ;
- 位于 expr 开头处的 v 的明确赋值状态与位于 stmt 开头处的 v 的状态相同。
- 位于 stmt 结尾处的 v 的明确赋值状态与位于 expr 结尾处的 v 的状态相同。
yield break 语句对明确赋值状态没有任何影响。
1.3.3.20 简单表达式的一般规则
以下规则适用于这些类型的表达式:文本(第 7.6.1 节)、简单名称(第 7.6.2 节)、成员访问表达式(第 7.6.4 节)、非索引基访问表达式(第 7.6.8 节)、typeof 表达式(第 7.6.11 节)和默认值表达式(第 7.6.13 节)。
- 位于此类表达式结尾处的 v 的明确赋值状态与位于表达式开头处的 v 的明确赋值状态相同。
1.3.3.21 带有嵌入表达式的表达式的一般规则
下列规则应用于这些类型的表达式:带括号的表达式(第 7.6.3 节);元素访问表达式(第 7.6.6 节);带索引的基访问表达式(第 7.6.8 节);增量和减量表达式(第 7.6.9 节、第 7.7.5 节);强制转换表达式(第 7.7.6 节);一元 +、-、~、* 表达式;二元 +、-、*、/、%、<<、>>、<、<=、>、>=、==、!=、is、as、&、|、^ 表达式(第 7.8 节、第 7.9 节、第 7.10 节、第 7.11 节);复合赋值表达式(第 7.17.2 节);checked 和 unchecked 表达式(第 7.6.12 节);数组和委托创建表达式(第 7.6.10 节)。
这些表达式的每一个都有一个和多个按固定顺序无条件计算的子表达式。例如,二元运算符 % 先计算运算符左边的值,然后计算右边的值。索引操作先计算索引表达式,然后按从左到右的顺序计算每个索引表达式。对于具有子表达式 expr1、expr2、...、exprn 的表达式 expr,按下列顺序计算:
- 位于 expr1 开头处的 v 的明确赋值状态与位于 expr 开头处的 v 的明确赋值状态相同。
- 位于 expri(i 大于 1)开头处的 v 的明确赋值状态与位于 expri-1 结尾处的 v 的明确赋值状态相同。
- 位于 expr 结尾处的 v 的明确赋值状态与位于 exprn 结尾处的 v 的明确赋值状态相同。
1.3.3.22 调用表达式和对象创建表达式
对于以下形式的调用表达式 expr:
primary-expression ( arg1 , arg2 , … , argn )
或以下形式的对象创建表达式:
new type ( arg1 , arg2 , … , argn )
- 对于调用表达式,位于 primary-expression 之前的 v 的明确赋值状态与位于 expr 之前的 v 的状态相同。
- 对于调用表达式,位于 arg1 之前的 v 的明确赋值状态与位于 primary-expression 之后的 v 的明确赋值状态相同。
- 对于对象创建表达式,位于 arg1 之前的 v 的明确赋值状态与位于 expr 之前的 v 的状态相同。
- 对于每一个参数 argi,位于 argi 之后的 v 的明确赋值状态由标准表达式规则决定,其中忽略所有的 ref 或 out 修饰符。
- 对于每一个 i 大于 1 的实参 argi,位于 argi 之前的 v 的明确赋值状态与位于 argi-1 之后的 v 的状态相同。
- 如果变量 v 是被作为 out 参数传递(即,形式为“out v”的参数),则无论将它用作哪一个参数,在 expr 之后,v 的状态是明确赋值的。否则,位于 expr 之后的 v 的状态与位于 argn 之后的 v 的状态相同。
- 对于数组初始值设定项(第 7.6.10.4 节)、对象初始值设定项(第 7.6.10.2 节)、集合初始值设定项(第 7.6.10.3 节)和匿名对象初始值设定项(第 7.6.10.6 节),明确赋值状态由定义这些构造所依据的扩展决定。
1.3.3.23 简单赋值表达式
对于具有形式 w = expr-rhs 的表达式 expr:
- 位于 expr-rhs 之前的 v 的明确赋值状态与位于 expr 之前的 v 的明确赋值状态相同。
- 如果 w 与 v 是同一变量,则位于 expr 之后的 v 的明确赋值状态是明确赋值的。否则,位于 expr 之后的 v 的明确赋值状态与位于 expr-rhs 之后的 v 的明确赋值状态相同。
1.3.3.24 && 表达式
对于形式为 expr-first && expr-second 的表达式 expr:
- 位于 expr-first 之前的 v 的明确赋值状态与位于 expr 之前的 v 的明确赋值状态相同。
- 如果位于 expr-first 之后的 v 的状态是明确赋值的或为“在 true 表达式后明确赋值”,则位于 expr-second 之前的 v 的明确赋值状态是明确赋值的。否则,它就不是明确赋值的。
- 位于 expr 之后的 v 的明确赋值状态取决于:
- 如果 expr-first 是值为 false 的常量表达式,则位于 expr 之后的 v 的明确赋值状态与位于 expr-first 之后的 v 的明确赋值状态相同。
- 否则,如果在 expr-first 之后,v 的状态是明确赋值的,则在 expr 之后的 v 的状态也是明确赋值的。
- 否则,如果位于 expr-second 之后的 v 的状态是明确赋值的,而且位于 expr-first 之后的 v 的状态为“在 false 表达式后明确赋值”,则位于 expr 之后的 v 的状态是明确赋值的。
- 否则,如果位于 expr-second 之后的 v 的状态是明确赋值的或为“在 true 表达式后明确赋值”,则位于 expr 之后的 v 的状态是“在 true 表达式后明确赋值”。
- 否则,如果位于 expr-first 之后的 v 的状态是“在 false 表达式后明确赋值”,而且位于 expr-second 之后的 v 的状态是“在 false 表达式后明确赋值”,则位于 expr 之后的 v 的状态是“在 false 表达式后明确赋值”。
- 否则,在 expr 之后,v 的状态就不是明确赋值的。
在下面的示例中
class A
{
static void F(int x, int y) {
int i;
if (x >= 0 && (i = y)
>= 0) {
// i definitely assigned
}
else {
// i not definitely assigned
}
// i not definitely assigned
}
}
变量 i 被视为在 if 语句的一个嵌入语句中已明确赋值,而在另一个嵌入语句中未明确赋值。在 F 方法中的 if 语句中,由于总是在第一个嵌入语句执行前执行表达式 (i = y),因此变量 i 在第一个嵌入语句中已明确赋值。相反,变量 i 在第二个嵌入语句中没有明确赋值,因 x >= 0 可能已测试为 false,从而导致变量 i 未赋值。
1.3.3.25 || 表达式
对于形式为 expr-first || expr-second 的表达式 expr:
- 位于 expr-first 之前的 v 的明确赋值状态与位于 expr 之前的 v 的明确赋值状态相同。
- 如果位于 expr-first 之后的 v 的状态是明确赋值的或“在 false 表达式后明确赋值”,则位于 expr-second 之前的 v 的明确赋值状态是明确赋值的。否则,它就不是明确赋值的。
- 位于 expr 之后的 v 的明确赋值状态取决于:
- 如果 expr-first 是值为 true 的常量表达式,则位于 expr 之后的 v 的明确赋值状态与位于 expr-first 之后的 v 的明确赋值状态相同。
- 否则,如果在 expr-first 之后,v 的状态是明确赋值的,则在 expr 之后的 v 的状态也是明确赋值的。
- 否则,如果位于 expr-second 之后的 v 的状态是明确赋值的,而且位于 expr-first 之后的 v 的状态为“在 true 表达式后明确赋值”,则位于 expr 之后的 v 的状态是明确赋值的。
- 否则,如果位于 expr-second 之后的 v 的状态是明确赋值的或是“在 false 表达式后明确赋值”,则位于 expr 之后的 v 的状态是“在 false 表达式后明确赋值”。
- 否则,如果位于 expr-first 之后的 v 的状态是“在 true 表达式后明确赋值”,而且位于 expr-second 之后的 v 的状态是“在 true 表达式后明确赋值”,则位于 expr 之后的 v 的状态是“在 true 表达式后明确赋值”。
- 否则,在 expr 之后,v 的状态就不是明确赋值的。
在下面的示例中
class A
{
static void G(int x, int y) {
int i;
if (x >= 0 || (i = y) >= 0) {
// i not definitely assigned
}
else {
// i definitely assigned
}
// i not definitely assigned
}
}
变量 i 被视为在 if 语句的一个嵌入语句中已明确赋值,而在另一个嵌入语句中未明确赋值。在 G 方法中的 if 语句中,由于总是在第二个嵌入语句执行前执行表达式 (i = y),因此变量 i 在第一个嵌入语句中已明确赋值。相反,在第一个嵌入语句中,变量 i 的状态不是明确赋值的,因为x >= 0 可能已测试为 true,从而导致变量 i 未赋值。
1.3.3.26 !表达式
对于形式为 ! expr-operand 的表达式 expr:
- 位于 expr-operand 之前的 v 的明确赋值状态与位于 expr 之前的 v 的明确赋值状态相同。
- 位于 expr 之后的 v 的明确赋值状态取决于:
- 如果在 expr-operand 之后,v 的状态是明确赋值的,则在 expr 之后,v 的状态也是明确赋值的。
- 如果在 expr-operand 之后,v 的状态不是明确赋值的,则在 expr 之后,v 的状态也不是明确赋值的。
- 如果位于 expr-operand 之后的 v 的状态是“在 false 表达式后明确赋值”,则位于 expr 之后的 v 的状态是“在 true 表达式后明确赋值”。
- 如果位于 expr-operand 之后的 v 的状态是“在 true 表达式后明确赋值”,则位于 expr 之后的 v 的状态是“在 false 表达式后明确赋值”。
1.3.3.27 ?? 表达式
对于形式为 expr-first ?? expr-second 的表达式 expr:
- 位于 expr-first 之前的 v 的明确赋值状态与位于 expr 之前的 v 的明确赋值状态相同。
- 位于 expr-second 之前的 v 的明确赋值状态与位于 expr-first 之后的 v 的明确赋值状态相同。
- 位于 expr 之后的 v 的明确赋值状态取决于:
- 如果 expr-first 是值为 null 的常量表达式(第 7.19 节),则位于 expr 之后的 v 的状态与位于 expr-second 之后的 v 的状态相同。
- 否则,位于 expr 之后的 v 的状态与位于 expr-first 之后的 v 的明确赋值状态相同。
1.3.3.28 ?: 表达式
对于形式为 expr-cond ? expr-true : expr-false 的表达式 expr:
- 位于 expr-cond 之前的 v 的明确赋值状态与位于 expr 之前的 v 的明确赋值状态相同。
- 当且仅当下列条件中有一个符合时,expr-true 之前的 v 的明确赋值状态才是明确赋值的:
- expr-cond 是值为 false 的常量表达式
- 在 expr-cond 之后的 v 的状态是明确赋值的或“在 true 表达式后明确赋值的”。
- 当且仅当下列条件中有一个符合时,expr-false 之前的 v 的明确赋值状态才是明确赋值的:
- expr-cond 是值为 true 的常量表达式
- 在 expr-cond 之后的 v 的状态是明确赋值的或“在 false 表达式后明确赋值的”。
- 位于 expr 之后的 v 的明确赋值状态取决于:
- 如果 expr-cond 是值为 true 的常量表达式(第 7.19 节),则位于 expr 之后的 v 的状态与位于 expr-true 之后的 v 的状态相同。
- 否则,如果 expr-cond 是值为 false 的常量表达式(第 7.19 节),则位于 expr 之后的 v 的状态与位于 expr-false 之后的 v 的状态相同。
- 否则,如果位于 expr-true 之后的 v 的状态是明确赋值的,而且位于 expr-false 之后的 v 的状态也是明确赋值的,则位于 expr 之后的 v 的状态是明确赋值的。
- 否则,在 expr 之后,v 的状态就不是明确赋值的。
1.3.3.29 匿名函数
对于具有体(block 或 expression)body 的 lambda-expression 或 anonymous-method-expression expr:
- 位于 body 之前的外层变量 v 的明确赋值状态与位于 expr 之前的 v 的状态相同。即,外层变量的明确赋值状态是从匿名函数的上下文中继承的。
- 位于 expr 之后的外层变量 v 的明确赋值状态与位于 expr 之前的 v 的明确赋值状态相同。
下面的示例
delegate bool Filter(int i);
void F() {
int max;
//
Error, max is not definitely assigned
Filter f = (int n) => n < max;
max =
5;
DoWork(f);
}
生成一个编译时错误,因为在声明匿名函数的位置,max 尚未明确赋值。下面的示例
delegate void D();
void F() {
int n;
D d = () => { n = 1; };
d();
//
Error, n is not definitely assigned
Console.WriteLine(n);
}
也生成一个编译时错误,因为匿名函数中对 n 的赋值并不影响匿名函数之外的 n 的明确赋值状态。
1.4 变量引用
variable-reference 是一个 expression,它被归类为一个变量。variable-reference 表示一个存储位置,访问它可以获取当前值以及存储新值。
variable-reference:
expression
在 C 和 C++ 中,variable-reference 称为 lvalue。
1.5 变量引用的原子性
下列数据类型的读写是原子形式的:bool、char、byte、sbyte、short、ushort、uint、int、float 和引用类型。除此之外,当枚举类型的基础类型的属于上述类型之一时,对它的读写也是原子的。其他类型的读写,包括 long、ulong、double 和 decimal 以及用户定义的类型,都不一定是原子的。除专为该目的设计的库函数以外,对于增量或减量这类操作也不能保证进行原子的读取、修改和写入。