变量(variable)表示存储的位置。每个变量都有类型,类型决定变量保存的值的类型。C# 是一门类型安全的语言,C# 编译器会确保变量中保存一个适合类型的值。变量的值可通过赋值或通过使用 ++
与 --
操作符改变。
变量必须在获得(obtained)前被明确赋值(definitely assigned)(第五章第三节)。
如以下部分所述,变量要么初始已赋值(initially assigned),要么初始未赋值(initially unassigned)。初始已赋值的变量有非常明确(well-defined)的初始值,且视己为被明确赋值(definitely assigned)的。初始未赋值的变量没有初始值(initial value)。对于初始未赋值变量来说,为了在某个位置能明确赋值,必须在通往该位置的每一个可能执行到的分支对该变量赋值。
变量种类
C# 定义了七种变量:静态变量(static variables)、实例变量(instance variables)、数组元素(array elements)、值参数(value parameters)、引用参数(reference parameters)、输出参数(output parameters)和局部变量(local variables)。本节将对上述类型逐一介绍。
下例中
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
是静态变量,y
是实例变量,v[0]
是数组元素,a
是值参数,b
是引用参数,c
是输出参数,i
是局部变量。
静态变量
以静态修饰符 static
声明的字段被称为静态变量(static variable)。静态变量在其所在类型的静态构造函数(第十章第十二节)执行之前便已存在,当其所相关的应用域(application domain)退出时不再存在。
静态变量的初始值是其类型的默认值(第五章第二节)。
为了明确赋值检查,静态变量被视为初始已赋值的。
实例变量
不以静态修饰符 static
声明的字段叫做实例变量(instance variable)。
类中的实例变量
类的实例变量在该类被创建实例时开始存在,当所有对该实例的引用都终止、实例析构函数(若有)执行过后,该类的实例变量不再存在。
类实例变量的初始值是该变量类型的默认值(第五章第二节)。
为了明确赋值检查,类的实例变量被认为是初始已赋值的。
结构中的实例变量
结构实例变量的生命周期与其所属的结构变量是一样的。换句话说,当结构类型的变量存在或终止时,它的实例变量也随之存在或消失。
结构的实例变量的初始赋值状态与其所在的结构变量一样。换句话说,当结构变量被视为初始化已赋值时,其实例变量也被视为初始化已赋值;当结构变量被视为初始化未赋值时,其实例变量也为初始化未赋值。
数组元素
当数组实例被创建时,数组元素开始存在;当没有任何引用指向该数组实例时,数组元素消失。
数组的每一个元素的初始值均为该数组元素类型的默认值(第五章第二节)。
为了明确赋值检查,数组元素被认为是初始已赋值的。
值参数
不使用 ref
或 out
修饰符的参数声明叫做值参数(value parameter)。
值形参再调用该参数所属的函数成员(方法、实例构造函数、访问器或操作符)或匿名函数时开始存在,并由调用者提供的实参的值初始化。一般来说,值形参的存在直到函数成员或匿名函数返回结果为止。然而,如果值形参如果被匿名函数(第七章第十五节)捕获(capture),则其生命周期将被至少延长到由该匿名函数创建的委托或表达式树可被垃圾回收为止。
为了明确赋值检查,值参数被认为是初始已赋值的。
引用参数
使用 ref
修饰符的参数声明叫做引用参数(reference parameter)。
引用形参不会创建新的本地存储位置。相反,引用形参表示其存储位置与函数成员或匿名函数调用所给定的实参的存储位置是一样的。因此,引用形参的值总是与基础变量相同。
下面的明确赋值规则适用于引用形参。注意,对输出形参(output parameters)规则与第五章第 1.6 节中所描述的规则不同。
- 变量必须在它被以引用参数形式传入函数成员或委托调用之前被明确赋值(第五章第三节)。
- 在函数成员或匿名函数内,引用形参被视为已被赋值。
在结构类型的实例方法或实例访问器中,this
关键字的行为与该结构类型所引用的形参相同(第七章第 6.7 节)。
输出参数
使用 out
修饰符的参数声明叫做输出参数(output parameter)。
输出形参不会创建新的本地存储位置。相反,输出形参表示与函数成员或委托调用所给定的存储位置是一样的。因此,输出形参的值总是与基础变量相同。
下面的明确赋值规则适用于输出形参。注意,对引用形参(reference parameters)规则与第五章第 1.5 节中所描述的规则不同。
- 变量不必在以输出形参的方式传入函数成员或委托调用之前明确初始化。
- 在正常完成函数成员或委托调用之后,每一个以输出形参的形式传入的变量都被认为在执行路径(execution path)中赋值。
- 在函数成员或匿名函数内部,输出形参被视为初始未赋值。
- 每一个函数成员或匿名函数的输出形参必须在函数成员或匿名函数正常返回结果之前被明确赋值(第五章第三节)。
在结构类型的实例方法中,this
关键字的行为与该结构类型所输出的形参相同(第七章第 6.7 节)。
局部变量
局部变量(local variable)由 local-variable-declaration
所声明,可出现在 block
、for-statement
、switch-statement
或 using-statement
内;或由 foreach-statement
或 specific-catch-clause
的 try-statement
声明。
局部变量的生命周期是程序执行期间的一部分,在此期间定会为其保留存储。这个生命周期至少从进入相关的 block
、for-statement
、switch-statement
、using-statement
、foreach-statement
或 specific-catch-clause
开始,到该 block
、for-statement
、switch-statement
、using-statement
、foreach-statement
或 specific-catch-clause
以任何方式结束为止。(进入闭包的 block
或方法调用会挂起(suspends)——但不会结束——当前的 block
、for-statement
、switch-statement
、using-statement
、foreach-statement
或 specific-catch-clause
。)如果局部变量被匿名函数(第七章第 15.5.1 节)捕获,其生命周期至少延长到从该匿名函数创建的委托或表达式树,以及其它引用该捕获变量的对象可被垃圾回收为止。
如果递归地进入(entered recursively)父 block
、for-statement
、switch-statement
、using-statement
、foreach-statement
或 specific-catch-clause
,每次都会创建该局部变量的新实例,并重新计算其 local-variable-initializer
(若有)。
由 local-variable-declaration
引入的局部变量不会自动初始化,因此没有默认值。为了明确赋值检查的目的,由 local-variable-declaration
引入的局部变量被认为是初始未赋值(initially unassigned)的。local-variable-declaration
可以包括 local-variable-initializer
,在此情况下,位于初始化表达式(第五章第 3.3.4 节)之后的变量被视作明确赋值的。
由 local-variable-declaration
引入的局部变量的范围内,在 local-variable-declarator
文本位置之前引用该局部变量会导致「编译时错误」。如果局部变量的声明是隐式的(第八章第 5.1 节),那么在 local-variable-declarator
内引用该变量同样会报错。
由 foreach-statement
或 specific-catch-clause
引入的局部变量在整个范围内被视作明确赋值的。
局部变量的实际生命周期是依赖于具体实现的(implementation-dependent)。比方说,编译器可能静态地(statically)决定某个块中的局部变量只用于该块的一小部分(a small portion of that block)。基于此分析,编译器生成的代码可能会使该变量的存储生命周期短于其所在的块。
局部引用变量所引用的存储(storage)的回收(reclaimed)独立于该局部引用变量(第三章第九节)的生命周期
默认值
下列分类的变量将自动初始化其默认值:
- 静态变量;
- 类实例的实例变量;
- 数组元素。
变量默认值依赖于其变量类型,并由以下规定确定:
- 对于值类型的变量,默认值与该值类型的默认构造函数(第四章第 1.2 节)所计算出的值相同;
- 对于引用类型的变量,默认值为空 null。
默认值初始化是一般是内存管理器(memory manager)或垃圾回收器(garbage collector)在分配内存给其使用之前,将内存的「所有位置零(all-bits-zero)」。基于此,很方便使用「所有位置零」表示空引用(null reference)。
明确赋值
在函数成员可执行代码中的给定位置,如果编译器能够通过特别的静态流程分析(particular static flow analysis,第五章第 3.3 节)证明变量已被自动初始化(automatically initialized)或至少一次被作为赋值的目标,那么称变量已被明确赋值。非正式地来讲,明确赋值的规则如下:
- 初始已赋值变量(第五章第 3.1 节)被视作明确赋值的。
- 如果所有可能的通向指定位置的执行路径至少包含以下一条,那么称初始未赋值变量(第五章第 3.2 节)可被视为明确赋值的:
- 将变量作为左操作数(left operand)进行简单赋值(simple assignment,第七章第 17.1 节)。
- 将变量传输到输出参数作为调用表达式(invocation expression,第七章第 6.5 节)或对象创建表达式(object creation expression,第七章第 6.10.1 节)。
- 对于局部变量,包含变量初始化器(variable initializer)的局部变量声明(第八章第 5.1 节)。
对于以上这些非正式的规则的正式规范在第五章第 3.1 节、第五章第 3.2 节以及第五章第 3.3 节中介绍。
对于 struct-type
变量之实例变量的明确赋值状态,既可以单独跟踪(tracked individually),也可以整体跟踪(tracked collectively)。另外除上述规则外,下列规则也将应用于 struct-type
变量及其实例变量:
- 当实例变量包含已被视作明确赋值的
struct-type
变量时,被视作已被明确赋值; - 当
struct-type
变量的每个实例变量都视作明确赋值时,它也被视作明确赋值。
下列上下文是必须明确赋值的:
- 变量必须在被获得之前的每一个地方明确赋值,以确保不会出现未定义值。表达式中出现的变量被视作获取变量的值,除非当:
- 变量时简单赋值的左操作数,
- 变量传递为一个输出参数,或者
- 变量是
struct-type
变量且作为成员访问的左操作数出现。
- 变量必须在作为引用参数(reference parameter)传递的每个位置明确赋值,以确保被调用的函数成员能够调用将之视作初始已赋值。
- 所有函数成员的输出参数必须在函数成员返回的每一个位置上明确赋值(通过
return
语句或通过执行到函数成员体的尾部并返回),以确保函数成员不会在输出参数中返回未定义的值(undefined values),因此允许编译器把将变量作为输出参数的函数成员的调用视作等价于对变量的赋值。 -
struct-type
实例构造函数的this
变量必须在实例构造函数所返回的每一个位置上明确赋值。
初始已赋值变量
以下变量分类被归入初始已赋值(initially assigned):
- 静态变量
- 类实例的实例变量
- 初始已赋值的结构变量的实例变量
- 数组元素
- 值参数
- 引用参数
- 在 catch 子句 或 foreach 语句中的变量声明
初始未赋值变量
以下变量分类被归入初始未赋值(initially unassigned):
- 初始未赋值的结构变量的实例变量
- 输出参数,包括结构实例构造函数的
this
变量 - 局部变量,除了那些声明于 catch 子句或 foreach 语句的变量
明确赋值的详细规则
为了确定每个所用变量是否已明确赋值,编译器必须使用与本节所述之一等价的处理过程。
编译器会处理每一个具有至少一个初始未赋值变量的函数成员的主体。对于每一个初始未赋值的变量 v
,编译器在以下函数成员内节点确定其明确赋值状态:
- 位于每句语句的开头位置
- 位于每句语句的结束点(end point,第八章第一节)
- 在每个将控制转移到另一句语句或语句结束点的 arc(Automatic Reference Counting)[1] 上
- 位于每个表达式的开头位置
- 位于每个表达式的结尾位置
v
的明确赋值状态可以是:
- 明确赋值的(definitely assigned)。这表明在所有可以到达该点的控制流中,
v
都已经赋值。 - 未明确赋值的(not definitely assigned)。对于位于 bool 类型表达式结尾处变量状态,其变量状态为未明确赋值的可能(但不一定)分成下列子状态(sub-states):
- 在 true 表达式后明确赋值。这个状态表明如果 bool 表达式运算结果为 true 时
v
将明确赋值,但当其运算结果为 false 时则不一定。 - 在 false 表达式时候明确赋值。这个状态表明如果 bool 表达式运算结果为 false 时
v
将明确赋值,但当其运算结果为 true 时则不一定。
- 在 true 表达式后明确赋值。这个状态表明如果 bool 表达式运算结果为 true 时
下列规则控制变量 v
在每一个位置上如何决定明确赋值状态的。
一般语句规则
- 位于函数成员主体开头处的 v 是未明确赋值的;
- 在任何无法访问的语句的开头处的 v 都是明确赋值的;
- 在任何其它语句开头处 v 的明确赋值状态由检查 v 在所有指向该语句开始处的控制流转移上的明确赋值状态所决定的。当且仅当 v 在所有此种控制流转移上是明确赋值的,那么 v 才在语句开头处是明确赋值的。确定所有可能的控制流转移集的方法与检查语句可访问性的方法(第八章第一节)相同。
- 在 block、checked、unchecked、if、while、do、for、foreach、lock、using 或 switch 语句结尾点上的 v 的明确赋值状态由检查所有指向该语句结尾点的控制流转移上 v 明确服装状态所决定。如果 v 在所有此种控制流转移上是明确赋值的,那么 v 才在语句结尾点上是明确赋值的。不然的话,v 在语句结尾点上是未明确赋值的。确定所有可能的控制流转移集的方法与检查语句可访问性的方法(第八章第一节)相同。
块、checked 与 unchecked 语句
在指向某块(block)语句列表(statement list)中第一句语句(如果语句列表为空(empty)则指向该块结束点)的控制转移上 v 的明确赋值状态与语句、checked 或 unchecked 语句之前的 v 的明确赋值状态一致。
表达式语句
对于由表达式 expr
组成的表达式语句 stmt
:
- 位于
expr
开头位置的 v 的明确赋值状态与stmt
开头处的一致; - 如果 v 在
expr
结尾处是明确赋值的,那么在stmt
结尾点也是明确赋值的,不然相反。
声明语句
- 如果
stmt
是不带初始化器的声明语句,则 v 在stmt
结束点与在stmt
开头处具有相同的明确赋值状态; - 如果
stmt
是一个带初始化器的声明语句,则确定 v 的明确赋值状态时可将之视为语句列表,每个带初始化器的声明对应一句赋值语句(按声明的顺序)。
if 语句
对于形如以下的 if 语句 stmt
:
if ( expr ) then-stmt else else-stmt
- 位于
expr
开头位置的 v 的明确赋值状态与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
结尾点是明确赋值的)上是明确赋值的。 - 否则的话,位于指向任意一个
then-stmt
、else-stmt
或stmt
结尾点(如果没有 else 子句)的控制流转移上的 v 被视作未明确赋值。
switch 语句
在带有控制表达式 expr
的 switch 语句 stmt
:
- 位于
expr
开始位置的 v 的明确赋值状态与位于stmt
开头位置的 v 的状态是一致的; - 在指向一个可访问(reachable)的 switch 语句块列表的控制流转移上 v 明确赋值状态就是
expr
结尾处的明确赋值状态。
while 语句
对于形如以下的 while 语句 stmt
:
while ( expr ) while-body
- 位于
expr
开头位置的 v 的明确赋值状态与stmt
开头处的一致; - 如果 v 在
expr
结尾处是明确赋值的,那么在指向while-body
的控制流转移上以及在stmt
结尾点上的状态是明确赋值的; - 如果 v 在
expr
结尾点的状态是「在 true 表达式之后明确赋值」,那么在指向while-body
的控制流转移上它是明确赋值的,但在stmt
结尾点上是未明确赋值的; - 如果 v 在
expr
结尾点的状态是「在 false 表达式之后明确赋值」,那么在指向stmt
结尾点的控制流转移上它是明确赋值的,单位指向while-body
的控制流转移上它未明确赋值的。
do 语句
对 do
语句的明确赋值检查有以下形式:
do do-body while ( expr ) ;
- 位于
stmt
开头处到do-body
的控制流转移(control flow transfer)上的 v 的明确赋值状态与stmt
开头处的一致。 - 位于
expr
开头处的 v 的明确赋值状态与do-body
结尾处的一致。 - 如果位于
expr
结尾处 v 是明确赋值的,那么在stmt
结尾处的控制硫转移上的 v 也是明确赋值的。 - 如果位于
expr
结尾处 v 具有「在 false 表达式之后明确赋值」,那么在stmt
结尾处的控制硫转移上的 v 也是明确赋值的。
for 语句
对 for
语句的明确赋值检查有以下形式:
for ( for-initializer ; for-condition ; for-iterator ) embedded-statement
如下面所写语句一样:
{
for-initializer ;
while ( for-condition ) {
embedded-statement ;
for-iterator ;
}
}
如果 for-condition
从语句中省略,则当评估明确赋值状态时,可将上述展开语句中的 for-condition
当做 true
。
break、continue 与 goto 语句
由 break
、continue
或 goto
语句导致的控制流转移(control flow transfer)上 v 的明确赋值状态与语句开始处 v 的明确赋值状态一致。
throw 语句
对于以下形式的语句 stmt
:
throw expr ;
位于 expr
开头处 v 的明确赋值状态与 stmt
开头处 v 的状态一致。
return 语句
对于以下形式的语句 stmt
:
return expr ;
- 位于
expr
开头 v 的明确赋值状态与stmt
开头 v 的状态是一致的。 - 如果 v 是输出形参,则其必须明确赋值(二选一):
- 要么在
expr
之后; - 要么在闭包于 return 语句的
try-finally
或try-catch-finally
的finally
块的结尾处。
- 要么在
对于以下形式的语句 stmt
:
return ;
- 如果 v 是输出形参,则其必须明确赋值(二选一):
- 要么在
stmt
之前; - 要么在闭包于
return
语句的try-catch
或try-catch-finally
的finally
块的结尾处。
- 要么在
try-catch 语句
对于以下形式的语句 stmt
:
try try-block
catch(...) catch-block-1
...
catch(...) catch-block-n
- 位于
try-block
开始处的 v 的明确赋值状态与stmt
开头处一致; - 位于
catch-block-i
开始处(对于任意 i)的 v 的明确赋值状态与stmt
开头处一致; - 当且仅当 v 位于
try-block
结尾点和每一个catch-block-i
(i ∈ [1, n])的结尾点是明确赋值的,则位于stmt
结尾点的 v 的明确赋值状态是明确赋值的。
try-finally 语句
对于以下形式的 try
语句 stmt
:
try try-block finally finally-block
- 位于
try-block
开始处的 v 的明确赋值状态与stmt
开头处一致; - 位于
finally-block
开始处的 v 的明确赋值状态与stmt
开头处一致; - 当且仅当至少满足以下一条条件时,位于
stmt
结尾点的 v 的明确赋值状态是明确赋值的:- v 在
try-block
结束点是明确赋值的; - v 在
finally-bolck
结束点是明确赋值的。
- v 在
如果控制流转移(比方说,一个 goto
语句)从 try-block
内开始,结束于 try-block
外,那么如果 v 在 finally-block
的结束点上明确赋值,在控制流转移上 v 同样被视作明确赋值的(这不是必要条件,如果 v 在 finally-block
结束点上是明确赋值的,那么它任被视作明确赋值)。
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 语句块(第八章第十节)中的效果。
class A
{
static void F() {
int i, j;
try {
goto LABEL;
// i 和 j 都没有明确赋值
i = 1;
// i 明确赋值
}
catch {
// i 和 j 都没有明确赋值
i = 3;
// i 明确赋值
}
finally {
// i 和 j 都没有明确赋值
j = 5;
// j 明确赋值
}
// i and j 明确赋值
LABEL:;
// j 明确赋值
}
}
foreach 语句
对于以下形式的 foreach
语句 stmt
:
foreach ( type identifier in expr ) embedded-statement
- 位于
expr
开头处的 v 的明确赋值状态与stmt
开头处的一致; - 位于指向
embedded-statement
的控制流转移上的 v 的明确赋值状态与expr
结尾处的一致。
using 语句
对于以下形式的 using
语句 stmt
:
using ( resource-acquisition ) embedded-statement
- 位于
resource-acquisition
开头处的 v 的明确赋值状态与stmt
开头处的一致; - 位于指向
embedded-statement
的控制流转移上的 v 的明确赋值状态与resource-acquisition
结尾处的一致。
lock 语句
对于以下形式的 lock
语句 stmt
:
lock ( expr ) embedded-statement
- 位于
expr
开头处的 v 的明确赋值状态与stmt
开头处的一致; - 位于指向
embedded-statement
的控制流转移上的 v 的明确赋值状态与expr
结尾处的一致。
yield 语句
对于以下形式的 yield return
语句 stmt
:
yield return expr ;
- 位于
expr
开头 v 的明确赋值状态与stmt
开头 v 的状态是一致的。 - 位于
stmt
结尾 v 的明确赋值状态与expr
结尾 v 的状态是一致的。
yield break
语句对于明确定义状态没有影响。
简单表达式的一般规则
以下规则可应用于下诸类型之表达式:文本(literals,第七章第 6.1 节)、简单名称(simple names,第七章第 6.2 节)、成员访问表达式(member access expressions,第七章第 6.4 节)、非索引基访问表达式(non-indexed base access expressions,第七章第 6.8 节)、typeof 表达式(typeof expressions,第七章第 6.11 节)以及默认值表达式(default value expressions,第七章第 6.13 节)。
- 位于上述表达式结尾处 v 的明确赋值状态与位于表达式开头的 v 的状态一致。
带嵌入表达式的表达式一般规则
以下规则可应用于下诸类型之表达式:带括号的表达式(parenthesized expressions,第七章第 6.3 节)、元素访问表达式(element access expressions,第七章第 6.6 节)、带索引的基访问表达式(base access expressions with indexing,第七章第 6.8 节)、增量与减量表达式(increment and decrement expressions,第七章第 6.9 节)、强制转换表达式(cast expressions,第七章第 7.6 节)、一元 +
, -
, ~
, *
表达式、二元 +
, -
, *
, /
, %
, <<
, >>
, <
, <=
, >
, >=
, ==
, !=
, is
, as
, &
, |
, ^
表达式(第七章第 8、9、10、11 节)、复合赋值表达式(compound assignment expressions, 第七章第 17.2 节)、checked
与 unchecked
表达式(第七章第 6.12 节)以及数组与委托创建表达式(array and delegate creation expressions,第七章第 6.10 节)。
这些表达式包含一个或多个固定顺序无条件计算(unconditionally evaluated in a fixed order)的子表达式(sub-expressions)。比方说,二元运算符 % 先运算左边的值,然后运算右边的。索引操作先计算索引表达式(indexed expression),然后从左到右运算每个索引表达式(index expressions)。对于具有子表达式 expr1、expr2、……、exprn 的表达式 expr
,按下列顺序执行:
- 位于 expr1 开头的 v 的明确赋值状态与
expr
开头的状态一致; - 位于 expri(i 大于 1) 开头的 v 的明确赋值状态与 exprn 结尾处 v 的状态一致;
- 位于 expr` 结尾处 v 的明确赋值状态与 exprn 结尾处 v 的状态一致。
调用表达式与对象创建表达式
对于以下形式的调用表达式(invocation expression)expr
:
primary-expression ( arg1 , arg2 , … , argn )
或以下形式的对象创建表达式(object creation expression):
new type ( arg1 , arg2 , … , argn )
- 对于调用表达式,位于
primary-expression
前 v 的明确赋值状态与expr
前 v 的状态一致。 - 对于调用表达式,位于 arg1 之前 v 的明确赋值状态与
primary-expression
后 v 的状态是一致的。 - 对于对象创建表达式,位于 arg1 之前 v 的明确赋值状态与
expr
后 v 的状态是一致的。 - 对于每个 argi 实参,位于 argi 之后 v 的明确赋值状态是由标准表达式规则(normal expression rules)所决定的,其中忽略所有的
ref
与out
修饰符。 - 对于每个 argi 实参(i 大于 1),位于 argi 之前 v 的明确赋值状态与 argi-1 后 v 的状态是一致的。
- 如果变量 v 以输出参数(形如
out v
)的方式传入实参,则expr
后 v 的状态是明确赋值的。否则的话,expr
后 v 的状态与 argn 后 v 的状态一致。 - 对于数组初始化器(array initializers,第七章第 6.10.4 节)、对象初始化器(object initializers,第七章第 6.10.2 节)、集合初始化器(collection initializers,第七章第 6.10.3 节)以及匿名对象初始化器(anonymous object initializers,第七章第 6.10.6 节),它们的明确赋值状态取决于定义这些构造所依据的扩展所决定。
简单赋值表达式
对于形如 w = expr-rhs
的表达式 expr
:
- 在
expr-rhs
之前的 v 的明确赋值状态与在expr
前 v 的明确赋值状态是一样的。 - 如果 w 是与 v 是同一变量,则
expr
之后的 v 的明确赋值状态为「已被明确扶植」。否则expr
之后的 v 的明确赋值状态与expr-rhs
之后的 v 的明确赋值状态是一样的。
&& 表达式
对于形如 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 的状态也是明确赋值的。 - 再不然,如果
expt-second
之后的 v 的状态是明确赋值的、并且expr-first
之后的状态是「在 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 明确赋值
}
else {
// i 没有明确赋值
}
// i 没有明确赋值
}
}
变量 i 在 if
语句的其中一个嵌入语句中被视作是明确赋值的,但在另一个则不是。在方法 F 的 if
语句中,变量 i 在第一个嵌入语句中被明确赋值,因为在表达式 (i = y)
执行的时间先于这段嵌入语句的执行时间。相反,变量 i 在第二个嵌入语句中是未明确赋值的,因为 x >= 0
为 false 时的结果是变量 i 未被赋值。
|| 表达式
对于形如 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 的状态也是明确赋值的。 - 再不然,如果
expt-second
之后的 v 的状态是明确赋值的、并且expr-first
之后的状态是「在 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 没有明确赋值
}
else {
// i 明确赋值
}
// i 没有明确赋值
}
}
变量 i 在其中一个嵌入语句中被视作明确赋值,而在另一个中则不是。在方法 G 的 if
语句中,变量 i 在第二个嵌入语句中被视作明确赋值,因为表达式 (i = y)
的执行将先于这段嵌入语句。与此相反,变量 i 在第一个嵌入语句中被视作未明确赋值,因为 x >= 0
可能为 true,那么作为其结果变量 i 没有被赋值。
! 表达式
对于形如 ! 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 表达式后明确赋值」。
- 如果
?? 表达式
对于形如 expr-first ?? expr-second
的表达式 expr
:
- 在
expr-first
前 v 的明确赋值状态与expr
前 v 的状态是一样的。 - 在
expr-second
前 v 的明确赋值状态与expr-first
后 v 的状态是一样的。 - 在
expr
后 v 的明确赋值状态取决于:- 若
expr-first
是值为 null 的常量表达式(第七章第十九节),则expr
后 v 的状态与expr-second
后 v 的状态是一致的。 - 否则的话,
expr
后 v 的状态与expr-first
后 v 的明确赋值状态一致。
- 若
?: 表达式
对于形如 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 的常量表达式(第七章第十九节),则expr
后 v 的状态与expr-true
后 v 的状态一致。 - 不然,若
expr-cond
是值为 false 的常量表达式(第七章第十九节),则expr
后 v 的状态与expr-false
后 v 的状态一致。 - 再不然,若
expr-true
后 v 的状态是明确赋值,且expr-false
后 v 的状态也是明确赋值的,则expr
后 v 的状态同样是明确赋值的。 - 否则,
expr
之后的 v 的状态就是未明确赋值了。
- 若
匿名函数
对于具有主体(块或表达式)的 Lambda 表达式(lambda-expression)或匿名方法表达式(anonymous-method-expression)expr
的主体(body):
- 在
body
之前的外部变量v
的明确赋值状态与expr
之前的v
的状态是一样的。也就是说,外部变量的明确赋值状态继承自匿名函数上下文。 - 在
expr
之后的外部变量v
的明确赋值状态与expr
之前的v
的状态是一样的。
举例
delegate bool Filter(int i);
void F() {
int max;
// 错误,max 没有明确赋值
Filter f = (int n) => n < max;
max = 5;
DoWork(f);
}
由于在匿名函数被声明的时候 max
没有明确赋值,所以这将产生一个「编译时错误」。举个例子。
delegate void D();
void F() {
int n;
D d = () => { n = 1; };
d();
//错误,n 没有明确赋值
Console.WriteLine(n);
}
由于在匿名函数之外对匿名函数内部的 n
进行明确赋值是没有效果的,所以这同样会产生一个「编译时错误」。
变量引用
变量引用(variable-reference)是一个被归类到变量(variable)的表达式(expression)。变量引用表示一个存储空间,通过访问它可以获取当前值、保存新值。
variable-reference:
expression
在 C 和 C++ 中,变量引用(variable-reference)中称为 lvalue
。
变量引用的原子性
读写以下数据类型是原子性的(atomic):bool
、 char
、 byte
、 sbyte
、 short
、 ushort
、 uint
、 int
、 float
以及引用类型。另外,如果枚举的基础类型属于上述列表内的,则读写该枚举值也是原子性的。读写其它类型,包括 long
、 ulong
、 double
、 decimal
以及用户自定义的类型时,不能保证一定是原子的。除专门为该墓地设计的库函数以外,对于增量(increment)或减量(decrement)这种情况,依旧不能保证原子性的读取、修改与写入(read-modify-write)。
[1]ARC:Automatic Reference Counting,自动引用计数,是开发程序时的一个编译级别的特性,用于自动内存管理。更多请访问此处以及此处。
__EOF__