变量代表存储位置。每个变量都有一个类型,用于确定可以在变量中存储的值。C#是一种类型安全的语言,C#编译器保证存储在变量中的值始终是适当的类型。可以通过赋值或使用++
和--
运算符来更改变量的值。
必须_明确赋值_变量(定义赋值)才能获得其值。
如以下部分所述,变量_最初分配_或_最初未分配_。初始分配的变量具有明确定义的初始值,并始终被视为明确分配。最初未分配的变量没有初始值。对于要在某个位置明确赋值的初始未分配变量,必须在通向该位置的每个可能执行路径中进行对变量的赋值。
变量类别
C#定义了七类变量:静态变量,实例变量,数组元素,值参数,引用参数,输出参数和局部变量。以下部分描述了这些类别。
在这个例子中
1 class A
2 {
3 public static int x;
4 int y;
5
6 void F(int[] v, int a, ref int b, out int c) {
7 int i = 1;
8 c = a + b++;
9 }
10 }
x
是一个静态变量,y
是一个实例变量,v[0]
是一个数组元素,a
是一个值参数,b
是一个引用参数,c
是一个输出参数,i
是一个局部变量。
静态变量
使用static
修饰符声明的字段称为_静态变量_。静态变量在执行其包含类型的静态构造函数(静态构造函数)之前就已存在,并且在关联的应用程序域不再存在时不再存在。
静态变量的初始值是变量类型的默认值(默认值)。
出于明确赋值检查的目的,最初分配静态变量。
实例变量
声明没有static
修饰符的字段称为_实例变量_。
类中的实例变量
当创建该类的新实例时,类的实例变量就会存在,并且当没有对该实例的引用并且实例的析构函数(如果有)已执行时,它就不再存在。
类的实例变量的初始值是变量类型的默认值(默认值)。
出于明确赋值检查的目的,最初会将类的实例变量视为已分配。
结构中的实例变量
struct的实例变量与它所属的struct变量具有完全相同的生命周期。换句话说,当结构类型的变量出现或不再存在时,结构的实例变量也是如此。
struct的实例变量的初始赋值状态与包含struct变量的初始赋值状态相同。换句话说,当一个struct变量被认为是最初赋值时,它的实例变量也是如此,并且当一个struct变量被认为是最初未赋值时,它的实例变量同样是未赋值的。
数组元素
数组元素在创建数组实例时就会存在,并且在没有对该数组实例的引用时就不再存在。
数组中每个元素的初始值是数组元素类型的默认值(默认值)。
出于明确赋值检查的目的,最初分配数组元素。
值参数
不带ref
或out
修饰符声明的参数是_值参数_。
值参数在调用函数成员(方法,实例构造函数,访问器或运算符)或参数所属的匿名函数时存在,并使用调用中给定的参数值进行初始化。返回函数成员或匿名函数时,值参数通常不再存在。但是,如果值参数由匿名函数(匿名函数表达式)捕获,则其生命周期至少延长,直到从该匿名函数创建的委托或表达式树符合垃圾回收的条件。
出于明确分配检查的目的,最初分配值参数。
引用参数
使用ref
修饰符声明的参数是_引用参数_。
引用参数不会创建新的存储位置。相反,引用参数表示与作为函数成员或匿名函数调用中的参数给出的变量相同的存储位置。因此,引用参数的值始终与基础变量相同。
以下明确的分配规则适用于引用参数。请注意输出参数中描述的输出参数的不同规则。
- 必须明确赋值变量(定义赋值),然后才能将其作为函数成员或委托调用中的引用参数传递。
- 在函数成员或匿名函数中,最初分配引用参数。
在结构类型的实例方法或实例访问器中,this
关键字的行为与结构类型(此访问)的引用参数完全相同。
输出参数
使用out
修饰符声明的参数是_输出参数_。
输出参数不会创建新的存储位置。相反,输出参数表示与作为函数成员或委托调用中的参数给出的变量相同的存储位置。因此,输出参数的值始终与基础变量相同。
以下明确的赋值规则适用于输出参数。请注意引用参数中描述的引用参数的不同规则。
- 在将变量作为函数成员或委托调用中的输出参数传递之前,无需明确赋值。
- 在正常完成函数成员或委托调用之后,作为输出参数传递的每个变量都被视为在该执行路径中分配。
- 在函数成员或匿名函数中,输出参数最初被视为未分配。
- 在函数成员或匿名函数正常返回之前,必须明确赋值函数成员或匿名函数的每个输出参数(定义赋值)。
在结构类型的实例构造函数中,this
关键字的行为与结构类型(此访问)的输出参数完全相同。
局部变量
局部变量_由声明_local_variable_declaration_,其可以在一个发生_块_,一个_for_statement_,一个_在switch_statement_或_using_statement ; 或者通过_foreach_statement_或_specific_catch_clause_获取_try_statement_。
局部变量的生命周期是程序执行的一部分,在此期间保证为其保留存储。此生命周期至少从进入_块_,for_statement_,_switch_statement_,_using_statement_,_foreach_statement_或与之关联的_specific_catch_clause _延伸_,直到执行该_块_,_for_statement_,_switch_statement_,_using_statement_,_foreach_statement_或_specific_catch_clause_以任何方式结束。(输入一个封闭的_块_或调用方法暂停但不结束当前_块_,_for_statement_,_switch_statement_,_using_statement_,_foreach_statement_或_specific_catch_clause的执行_。)如果局部变量被匿名函数捕获(捕获的外部变量),其生命周期至少延长直到从匿名函数创建的委托或表达式树以及引用捕获变量的任何其他对象都有资格进行垃圾回收。
如果以递归方式输入父_块_,_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_,在这种情况下变量仅初始化表达式(后视为已明确赋值声明语句)。
在_local_variable_declaration_引入的局部变量的范围内,在_local_variable_declarator_之前的文本位置引用该局部变量是编译时错误。如果局部变量声明是隐式的(局部变量声明),则在其_local_variable_declarator中_引用变量也是一个错误。
由_foreach_statement_或_specific_catch_clause_引入的局部变量在其整个范围内被认为是明确赋值的。
局部变量的实际生命周期取决于实现。例如,编译器可能静态地确定块中的局部变量仅用于该块的一小部分。使用此分析,编译器可以生成导致变量存储的生命周期比其包含块短的代码。
本地引用变量引用的存储器的回收与本地引用变量的寿命无关(自动内存管理)。
默认值
以下类别的变量会自动初始化为其默认值:
- 静态变量。
- 类实例的实例变量。
- 数组元素。
变量的默认值取决于变量的类型,并确定如下:
- 对于_value_type_的变量,默认值与_value_type_的默认构造函数(默认构造函数)计算的值相同。
- 对于_reference_type_的变量,默认值为
null
。
初始化为默认值通常通过让内存管理器或垃圾收集器在分配使用之前将内存初始化为所有位为零来完成。因此,使用all-bits-zero来表示空引用是很方便的。
明确赋值
在函数的部件的可执行代码的给定位置,可变是_明确赋值_,如果编译器可以证明由特定静态流分析(精确的规则用于确定明确赋值),该变量被自动初始化或一直是至少一项任务的目标。非正式地说,明确分配的规则是:
- 初始分配的变量(初始分配的变量)始终被认为是明确分配的。
- 如果通往该位置的所有可能执行路径至少包含以下之一,则认为在给定位置明确分配了最初未分配的变量(最初未分配的变量):
- 一个简单的赋值(Simple assignment),其中变量是左操作数。
- 调用表达式(调用表达式)或对象创建表达式(对象创建表达式),它将变量作为输出参数传递。
- 对于局部变量,包含变量初始值设定项的局部变量声明(局部变量声明)。
上述非正式规则的正式规范在最初分配的变量,最初未分配的变量和确定明确赋值的精确规则中描述。
_struct_type_变量的实例变量的明确赋值状态被单独跟踪以及共同跟踪。除上述规则外,以下规则适用于_struct_type_变量及其实例变量:
- 如果实例变量的包含_struct_type_变量被认为是明确赋值的,则认为它是明确赋值的。
- 如果_struct_type_变量的每个实例变量都被认为是明确赋值的,则它被认为是明确赋值的。
在以下情况下,明确赋值是必需的:
- 必须在获得其值的每个位置明确赋值变量。这可确保永远不会发生未定义的值。表达式中变量的出现被认为是获取变量的值,除非
- 变量是简单赋值的左操作数,
- 变量作为输出参数传递,或
- 变量是_struct_type_变量,并作为成员访问的左操作数出现。
- 必须在传递它作为引用参数的每个位置明确赋值变量。这确保了被调用的函数成员可以考虑最初分配的引用参数。
- 必须在函数成员返回的每个位置(通过
return
语句或通过执行到达函数成员体的末尾)明确赋值函数成员的所有输出参数。这可确保函数成员不会在输出参数中返回未定义的值,从而使编译器能够考虑将变量作为输出参数的函数成员调用,该输出参数等同于对变量的赋值。 - 必须在该实例构造函数返回的每个位置明确赋值_struct_type_实例构造函数的
this
变量。
最初分配的变量
以下类别的变量分类为最初分配的:
- 静态变量。
- 类实例的实例变量。
- 最初分配的结构变量的实例变量。
- 数组元素。
- 值参数。
- 引用参数。
- 在
catch
子句或foreach
语句中声明的变量。
最初未分配的变量
以下类别的变量被归类为最初未分配的变量:
- 最初未分配的结构变量的实例变量。
- 输出参数,包括
this
struct实例构造函数的变量。 - 局部变量,除了在
catch
子句或foreach
语句中声明的变量。
确定明确任务的准确规则
为了确定每个使用的变量是明确分配的,编译器必须使用与本节中描述的过程等效的过程。
编译器处理每个函数成员的主体,该成员具有一个或多个最初未分配的变量。对于每个初始未赋值的变量_v_,编译器确定一个_明确赋值状态_为_v_在每个功能部件的以下几点:
- 在每个声明的开头
- 在每个语句的结束点(结束点和可达性)
- 在每个弧上将控制转移到另一个语句或语句的结束点
- 在每个表达式的开头
- 在每个表达结束时
_v_的明确赋值状态可以是:
- 绝对分配。这表示在此点的所有可能控制流上,已为_v_分配了一个值。
- 没有明确分配。对于类型表达式末尾的
bool
变量状态,未明确赋值的变量的状态可能(但不一定)属于以下子状态之一:- 在真实表达后绝对分配。此状态表示如果布尔表达式求值为true,则明确赋值_v_,但如果布尔表达式求值为false,则不一定指定_v_。
- 在假表达后绝对分配。此状态表示如果布尔表达式求值为false,则明确赋值_v_,但如果布尔表达式求值为true,则不一定分配_v_。
以下规则控制如何在每个位置确定变量_v_的状态。
陈述的一般规则
- _v_在函数成员体的开头没有明确赋值。
- _v_绝对是在任何无法访问的语句的开头分配的。
- 任何其他语句开头的_v_的明确赋值状态是通过检查以该语句的开头为目标的所有控制流转移上的_v_的明确赋值状态来确定的。如果(并且仅当)_v_在所有此类控制流转移上明确分配,则在语句的开头明确指定_v_。以与检查语句可达性(端点和可达性)相同的方式确定可能的控制流传输的集合。
- 的明确赋值状态_v_在块的结束点,
checked
,unchecked
,if
,while
,do
,for
,foreach
,lock
,using
,或switch
语句是通过检查的明确赋值状态来确定_v_上靶向该语句的结束点的所有控制流转移。如果_v_肯定是在所有此类控制流转移分配,则_v_绝对是在语句的结束点分配。除此以外; _v_在语句的结束点没有明确赋值。可用控制流转移的集合以与检查语句可达性相同的方式确定(终点和可达性)。
阻止语句,已检查和未检查的语句
控件上的_v_的明确赋值状态转移到块中语句列表的第一个语句(如果语句列表为空,则转到块的结束点)与_v_之前的_v_的明确赋值语句相同块checked
,或unchecked
声明。
表达式陈述
对于由表达式_expr_组成的表达式语句_stmt_:
- _v_在_expr_的开头和_stmt_的开头具有相同的明确赋值状态。
- 如果_v_如果在_expr_的末尾明确赋值,那么它肯定是在_stmt_的结束点分配的; 除此以外; 它没有明确地分配给_stmt_的终点。
声明语句
- 如果_语句_是不带有初始值的声明语句,则_v_具有在终点相同的明确赋值状态_语句_作为初始化语句。
- 如果_stmt_是带有初始值设定项的声明语句,则确定_v_的明确赋值状态,就好像_stmt_是一个语句列表,每个声明都有一个赋值语句,带有初始化程序(按声明顺序)。
if
语句
对于表单的if
语句_stmt_:
if ( expr ) then_stmt else else_stmt
- _v_在_expr_的开头和_stmt_的开头具有相同的明确赋值状态。
- 如果_v_在_expr_的末尾明确赋值,那么它肯定会在控制流转移到_then_stmt_和_else_stmt_或_stmt_的结束点(如果没有else子句)。
- 如果_v_在_expr_结尾处具有“在真实表达式后明确赋值”的状态,则它在控制流转移到_then_stmt时_明确赋值,并且在控制流转移到_else_stmt_或者到端点时没有明确赋值。_stmt_如果没有else子句。
- 如果_v_具有的端部的状态“假表达式后明确赋值” _EXPR_,那么它是明确赋值上的控制流转移到_else_stmt_,并在控制流转移到不明确赋值_then_stmt_。它是在结束点明确赋值_语句_当且仅当它是在结束点明确分配_then_stmt_。
- 否则,如果没有else子句,则认为_v_在控制流转移到_then_stmt_或_else_stmt_或_stmt_的端点时未明确分配。
switch
语句
在带有控制表达式_expr_的switch
语句_stmt中_:
- _expr_开头的_v_的明确赋值状态与_stmt_开头的_v_的状态相同。
- 的明确赋值状态_v_上的控制流转移到一个可到达开关块语句列表是相同的明确赋值状态_v_在年底_EXPR_。
while
语句
对于表单的while
语句_stmt_:
while ( expr ) while_body
- _v_在_expr_的开头和_stmt_的开头具有相同的明确赋值状态。
- 如果_v_在_expr_的末尾明确赋值,那么它肯定会在控制流转移到_while_body_和_stmt_的结束点时分配。
- 如果_v_在_expr_结束时具有“在真实表达式后明确赋值”的状态,则它在控制流转移到_while_body时_明确赋值,但在_stmt_的结束点处没有明确赋值。
- 如果_v_在_expr_结尾处具有“在假表达式后明确赋值”的状态,则它在控制流转移时明确地分配给_stmt_的结束点,但是在控制流转移到_while_body时_没有明确赋值。
do
语句
对于表单的do
语句_stmt_:
do do_body while ( expr ) ;
- _v_对从开始时的控制流转移的相同的明确赋值状态_语句_到_do_body_如在开头_语句_。
- _v_在_expr_的开头和_do_body_的结束点具有相同的明确赋值状态。
- 如果_v_在_expr_的末尾明确赋值,那么它肯定会在控制流转移到_stmt_的结束点时分配。
- 如果_v_在_expr_结尾处具有“在假表达式后明确赋值”的状态,那么它肯定在控制流转移到_stmt_的结束点时被分配。
for语句
for
对表单声明的明确赋值检查:
for ( for_initializer ; for_condition ; for_iterator ) embedded_statement
完成就好像声明写的:
1 {
2 for_initializer ;
3 while ( for_condition ) {
4 embedded_statement ;
5 for_iterator ;
6 }
7 }
如果从语句中省略了_for_condition_for
,那么对明确赋值的评估就像在上面的扩展中替换_for_condition_一样true
。
break
,continue
或goto
语句
的明确赋值状态_v_引起的控制流转移break
,continue
或goto
语句是一样的明确赋值状态_v_在声明的开头。
throw语句
throw对于表单的语句stmt
throw expr ;
_expr_开头的_v_的明确赋值状态与_stmt_开头的_v_的明确赋值状态相同。
reutrn语句
return对于表单的语句_stmt_
return expr ;
- _expr_开头的_v_的明确赋值状态与_stmt_开头的_v_的明确赋值状态相同。
- 如果_v_是输出参数,则必须明确赋值:
- 在_expr_之后
- 或在年底
finally
的块try
-finally
或try
-catch
-finally
包围return
声明。
对于表单的语句stmt:
return ;
- 如果_v_是输出参数,则必须明确赋值:
- 在_stmt_之前
- 或在年底
finally
的块try
-finally
或try
-catch
-finally
包围return
声明。
Try-catch语句
对于表单的语句_stmt_:
1 try try_block
2 catch(...) catch_block_1
3 ...
4 catch(...) catch_block_n
- _try_block_开头的_v_的明确赋值状态与_stmt_开头的_v_的明确赋值状态相同。
- 的明确赋值状态_v_之初_catch_block_i_(对于任何_我_)是一样的明确赋值状态_v_之初_语句_。
- 如果(并且仅当)_v_在_try_block的终点_和每个_catch_block_i_(对于从1到_n的_每个_i_)中明确赋值,则明确赋予_stmt_的结束点处的_v_的明确赋值状态。
try-finally
语句
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_也视为已明确对控制流转移分配如果_v_是在终点明确赋值_finally_block_。(这不仅仅是if-if _v_在此控制流转移中由于其他原因而被明确分配,那么它仍然被认为是明确分配的。)
Try-catch-finally语句
对try
- catch
- finally
语句形式的明确赋值分析:
1 try try_block
2 catch(...) catch_block_1
3 ...
4 catch(...) catch_block_n
5 finally *finally_block*
如果语句是try
- finally
语句包含try
- catch
语句,则完成:
1 try {
2 try try_block
3 catch(...) catch_block_1
4 ...
5 catch(...) catch_block_n
6 }
7 finally finally_block
以下示例演示了try
语句的不同块(try语句)如何影响明确赋值。
1 class A
2 {
3 static void F() {
4 int i, j;
5 try {
6 goto LABEL;
7 // neither i nor j definitely assigned
8 i = 1;
9 // i definitely assigned
10 }
11
12 catch {
13 // neither i nor j definitely assigned
14 i = 3;
15 // i definitely assigned
16 }
17
18 finally {
19 // neither i nor j definitely assigned
20 j = 5;
21 // j definitely assigned
22 }
23 // i and j definitely assigned
24 LABEL:;
25 // j definitely assigned
26
27 }
28 }
foreach语句
对于表单的foreach
语句_stmt_:
foreach ( type identifier in expr ) embedded_statement
- _expr_开头的_v_的明确赋值状态与_stmt_开头的_v_的状态相同。
- 控制流转移到_embedded_statement_或_stmt_的结束点的_v_的明确赋值状态与_expr_末尾的_v_的状态相同。
using语句
对于表单的using
语句_stmt_:
using ( resource_acquisition ) embedded_statement
- _resource_acquisition_开头的_v_的明确赋值状态与_stmt_开头的_v_的状态相同。
- 控制流转移到_embedded_statement_的_v_的明确赋值状态与_resource_acquisition_结束时的_v_的状态相同。
lock语句
对于表单的lock
语句_stmt_:
lock ( expr ) embedded_statement
- _expr_开头的_v_的明确赋值状态与_stmt_开头的_v_的状态相同。
- 控制流转移到_embedded_statement_的_v_的明确赋值状态与_expr_末尾的_v_的状态相同。
yield return
语句
对于表单的yield return
语句_stmt_:
yield return expr ;
- _expr_开头的_v_的明确赋值状态与_stmt_开头的_v_的状态相同。
- _stmt_结束时_v_的明确赋值状态与_expr_末尾_v_的状态相同。
- 一个
yield break
语句对明确赋值状态没有影响。
简单表达式的一般规则
以下规则适用于这些类型的表达式:文字(文字),简单名称(简单名称),成员访问表达式(成员访问),非索引基本访问表达式(基本访问),typeof
表达式(类型操作符),默认值表达式(默认值表达式)和nameof
表达式(Nameof表达式)。
- 明确赋值状态_v_在这种表达的端部是相同的明确赋值状态_v_在表达的开始。
嵌入式表达式的表达式的一般规则
以下规则适用于这些类型的表达式:括号表达式(括号表达式),元素访问表达式(元素访问),带索引的基本访问表达式(基本访问),递增和递减表达式(Postfix递增和递减运算符,前缀递增和递减)运营商),浇铸式(转换表达式),一元+
,-
,~
,*
表情,二进制+
,-
,*
,/
,%
,<<
,>>
,<
,<=
,>
,>=
,==
,!=
,is
,as
,&
,|
,^
式(算术运算符,移位运算符,关系和类型测试操作员,逻辑运算符),化合物赋值表达式(化合物分配),checked
和unchecked
表达式(的选中和未选中运营商),以及阵列和委托创建表达式(新运营商) 。
这些表达式中的每一个都具有一个或多个子表达式,这些子表达式以固定顺序无条件地评估。例如,二元%
运算符计算运算符的左侧,然后是右侧。索引操作评估索引表达式,然后按从左到右的顺序计算每个索引表达式。对于表达式_expr_,它具有子表达式_e1,e2,...,eN_,按以下顺序计算:
- _e1_开头的_v_的明确赋值状态与_expr_开头的明确赋值状态相同。
- _ei_(_i_大于1)开头的_v_的明确赋值状态与前一个子表达式末尾的明确赋值状态相同。
- _expr_末尾的_v_的明确赋值状态与_eN_末尾的明确赋值状态相同
调用表达式和对象创建表达式
对于表单的调用表达式_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_,的明确赋值状态_v_后_位于argi_由正常表达规则确定,忽略任何
ref
或out
改性剂。 - 对于每一个参数_阿尔吉_任何_我_大于一的明确赋值状态_v_之前_阿尔吉_相同的状态_v_以前后_ARG_。
- 如果变量_v_在任何
out
参数中作为参数(即表单的参数out v
)传递,则_expr_之后的_v_的状态是明确赋值的。除此以外; _expr_之后的_v_的状态与_argN_之后的_v_的状态相同。 - 对于数组初始化器(数组创建表达式),对象初始化器(对象初始化器),集合初始化器(集合初始化器)和匿名对象初始化器(匿名对象创建表达式),明确的赋值状态由扩展确定,这些构造是根据。
简单赋值表达式
对于表单的表达式_expr_w = expr_rhs
:
- _expr_rhs_之前的_v_的明确赋值状态与_expr_之前的_v_的明确赋值状态相同。
- _expr_之后_v_的明确赋值状态由下式确定:
- 如果_w_是与_v_相同的变量,那么在_expr_之后的_v_的明确赋值状态是明确赋值的。
- 否则,如果一个结构类型的实例构造中发生的分配,如果_瓦特_为属性访问指定一个自动实现的属性_P_上的实例被构造和_v_是隐藏的支持字段_P_,则明确赋值状态_v_之后_expr_肯定是分配的。
- 否则,_expr_之后的_v_的明确赋值状态与_expr_rhs_之后的_v_的明确赋值状态_相同_。
&&(条件AND)表达式
对于表单的表达式_expr_expr_first && expr_second
:
- _expr_first_之前的_v_的明确赋值状态与_expr_之前的_v_的明确赋值状态相同。
- 如果_expr_first_之后的_v_的状态是明确赋值或“在真实表达式后明确赋值”,那么在_expr_second_之前的_v_的明确赋值状态是明确赋值的。否则,它没有明确分配。
- _expr_之后_v_的明确赋值状态由下式确定:
- 如果_expr_first_是具有该值的常量表达式
false
,则_expr_之后的_v_的明确赋值状态与_expr_first_之后的_v_的明确赋值状态_相同_。 - 否则,如果状态_v_后_expr_first_被明确赋值,然后状态_v_后_EXPR_被明确赋值。
- 否则,如果明确赋值_expr_second_之后的_v_的状态,并且_expr_first_之后的_v_的状态是“在false表达式之后明确赋值”,则明确赋值_expr_之后的_v_的状态。
- 否则,如果_expr_second_之后的_v_的状态被明确赋值或“在真实表达式之后明确赋值”,则_expr_之后的_v_的状态是“在真实表达之后明确赋值”。
- 否则,如果_expr_first_之后的_v_的状态是“在false表达式之后明确赋值”,并且_expr_second_之后的_v_的状态是“在false表达之后明确赋值”,则_expr_之后的_v_的状态是“在false表达之后明确赋值”。
- 否则,_expr_之后的_v_的状态没有明确赋值。
- 如果_expr_first_是具有该值的常量表达式
在这个例子中
1 class A
2 {
3 static void F(int x, int y) {
4 int i;
5 if (x >= 0 && (i = y) >= 0) {
6 // i definitely assigned
7 }
8 else {
9 // i not definitely assigned
10 }
11 // i not definitely assigned
12 }
13 }
该变量i
被认为是在一个语句的嵌入语句中明确赋值而在另一个语句中if
没有。在if
方法中的语句中F
,变量i
在第一个嵌入语句中明确赋值,因为表达式的(i = y)
执行总是在执行此嵌入语句之前。相反,变量i
未在第二个嵌入语句中明确赋值,因为x >= 0
可能已经测试为false,导致变量i
未分配。
|| (条件OR)表达式
对于表单的表达式_expr_expr_first || expr_second
:
- _expr_first_之前的_v_的明确赋值状态与_expr_之前的_v_的明确赋值状态相同。
- 如果_expr_first_之后的_v_的状态是明确赋值或“在假表达后明确赋值” ,则明确赋值为_expr_second_之前的_v_的明确赋值状态。否则,它没有明确分配。
- _expr_之后的_v_的明确赋值语句由以下因素确定:
- 如果_expr_first_是具有该值的常量表达式
true
,则_expr_之后的_v_的明确赋值状态与_expr_first_之后的_v_的明确赋值状态_相同_。 - 否则,如果状态_v_后_expr_first_被明确赋值,然后状态_v_后_EXPR_被明确赋值。
- 否则,如果明确赋值_expr_second_之后的_v_的状态,并且_expr_first_之后的_v_的状态是“在真实表达式之后明确赋值”,则明确赋值_expr_之后的_v_的状态。
- 否则,如果_expr_second_之后的_v_的状态是明确赋值的或“在假表达式之后明确赋值”,则_expr_之后的_v_的状态是“在false表达之后明确赋值”。
- 否则,如果_expr_first_之后的_v_的状态是“在真实表达式之后明确赋值”,并且_expr_second_之后的_v_的状态是“在真实表达式之后明确赋值”,则_expr_之后的_v_的状态是“在真实表达之后明确赋值”。
- 否则,_expr_之后的_v_的状态没有明确赋值。
- 如果_expr_first_是具有该值的常量表达式
在这个例子中
1 class A
2 {
3 static void G(int x, int y) {
4 int i;
5 if (x >= 0 || (i = y) >= 0) {
6 // i not definitely assigned
7 }
8 else {
9 // i definitely assigned
10 }
11 // i not definitely assigned
12 }
13 }
该变量i
被认为是在一个语句的嵌入语句中明确赋值而在另一个语句中if
没有。在if
方法中的语句中G
,变量i
在第二个嵌入语句中明确赋值,因为表达式的(i = y)
执行总是在执行此嵌入语句之前。相反,变量i
未在第一个嵌入语句中明确赋值,因为x >= 0
可能已经测试为true,导致变量i
未分配。
!(逻辑否定)表达式
对于表单的表达式_expr_! expr_operand
:
- _expr_operand_之前的_v_的明确赋值状态与_expr_之前的_v_的明确赋值状态相同。
- _expr_之后_v_的明确赋值状态由下式确定:
- 如果_明确赋值expr_operand *_之后的_v_的状态,_则明确赋值为__expr_之后_的* v的状态_。
- 如果_expr_operand *_之后的_v_的状态_未明确赋值,则__expr_之后_的* v的状态_未明确赋值。
- 如果_expr_operand *_之后的_v_的状态_是“在false表达式后明确赋值”,那么__expr_之后_的* v的状态_是“在真实表达式之后明确赋值”。
- 如果_expr_operand *_之后的_v_的状态_是“在真实表达式之后明确赋值”,则__expr_之后_的* v的状态_是“在false表达式之后明确赋值”。
?? (null合并)表达式
对于表单的表达式_expr_expr_first ?? expr_second
:
- _expr_first_之前的_v_的明确赋值状态与_expr_之前的_v_的明确赋值状态相同。
- _expr_second_之前的_v_的明确赋值状态与_expr_first_之后的_v_的明确赋值状态_相同_。
- _expr_之后的_v_的明确赋值语句由以下因素确定:
- 如果_expr_first_是一个常量表达式(常量表达式)与null值,则所述的状态_v_后_EXPR_是一样的状态_v_后_expr_second_。
- 否则,状态_v_后_EXPR_相同的明确赋值状态_v_后_expr_first_。
?:(条件)表达式
对于表单的表达式_expr_expr_cond ? expr_true : expr_false
:
- _expr_cond_之前的_v_的明确赋值状态与_expr_之前的_v_的状态相同。
- 当且仅当下列之一成立时,才明确赋值_expr_true_之前的_v_的明确赋值状态:
- _expr_cond_是一个带值的常量表达式
false
- _expr_cond_之后的_v_的状态是明确赋值的,或者“在真实表达后明确赋值”。
- _expr_cond_是一个带值的常量表达式
- 当且仅当下列之一成立时,才明确分配_expr_false_之前的_v_的明确赋值状态:
- _expr_cond_是一个带值的常量表达式
true
- _expr_cond_是一个带值的常量表达式
- _expr_cond_之后的_v_的状态是明确赋值的,或者是“在假表达后明确赋值”。
- _expr_之后_v_的明确赋值状态由下式确定:
- 如果_expr_cond_是具有值的常量表达式(常量表达式),
true
那么_expr_之后的_v_的状态与_expr_true_之后的_v_的状态相同。 - 否则,如果_expr_cond_是一个常量表达式(常量表达式)与值
false
则状态_v_后_EXPR_是一样的状态_v_后_expr_false_。 - 否则,如果状态_v_后_expr_true_绝对是分配和状态_v_后_expr_false_被明确赋值,然后状态_v_后_EXPR_被明确赋值。
- 否则,_expr_之后的_v_的状态没有明确赋值。
- 如果_expr_cond_是具有值的常量表达式(常量表达式),
匿名函数
对于具有正文(块_或_表达式_)_主体_的_lambda_expression_或_anonymous_method_expression _expr_:
- 在_body_之前的外部变量_v_的明确赋值状态与在_expr_之前的_v_的状态相同。也就是说,外部变量的明确赋值状态是从匿名函数的上下文继承的。
- 外变量的明确赋值状态_v_后_EXPR_是一样的状态_v_之前_EXPR_。
这个例子
1 delegate bool Filter(int i);
2
3 void F() {
4 int max;
5
6 // Error, max is not definitely assigned
7 Filter f = (int n) => n < max;
8
9 max = 5;
10 DoWork(f);
11 }
生成编译时错误,因为max
在声明匿名函数的位置没有明确赋值。这个例子
1 delegate void D();
2
3 void F() {
4 int n;
5 D d = () => { n = 1; };
6
7 d();
8
9 // Error, n is not definitely assigned
10 Console.WriteLine(n);
11 }
还会生成编译时错误,因为n
匿名函数中的赋值n
对匿名函数外部的明确赋值状态没有影响。
变量引用
_variable_reference_是一个_表达式_被分类为一个变量。甲_variable_reference_表示可被访问既获取的电流值和存储新的值的存储位置。
variable_reference
: expression
;
变量引用的原子性
读取和下列数据类型的写是原子:bool
,char
,byte
,sbyte
,short
,ushort
,uint
,int
,float
,和引用类型。此外,在先前列表中具有基础类型的枚举类型的读取和写入也是原子的。读取和其他类型,包括的写入long
,ulong
,double
,和decimal
,以及用户定义的类型,不能保证是原子的。除了为此目的而设计的库函数之外,不保证原子读 - 修改 - 写,例如在递增或递减的情况下。