C#6.0语言规范(五) 变量
变量代表存储位置。每个变量都有一个类型,用于确定可以在变量中存储的值。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_conditionfor
,那么对明确赋值的评估就像在上面的扩展中替换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的状态相同。 - 对于数组初始化器(数组创建表达式),对象初始化器(对象初始化器),集合初始化器(集合初始化器)和匿名对象初始化器(匿名对象创建表达式),明确的赋值状态由扩展确定,这些构造是根据。
简单赋值表达式
对于表单的表达式exprw = expr_rhs
:
- expr_rhs之前的v的明确赋值状态与expr之前的v的明确赋值状态相同。
- expr之后v的明确赋值状态由下式确定:
- 如果w是与v相同的变量,那么在expr之后的v的明确赋值状态是明确赋值的。
- 否则,如果一个结构类型的实例构造中发生的分配,如果瓦特为属性访问指定一个自动实现的属性P上的实例被构造和v是隐藏的支持字段P,则明确赋值状态v之后expr肯定是分配的。
- 否则,expr之后的v的明确赋值状态与expr_rhs之后的v的明确赋值状态相同。
&&(条件AND)表达式
对于表单的表达式exprexpr_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)表达式
对于表单的表达式exprexpr_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合并)表达式
对于表单的表达式exprexpr_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。
?:(条件)表达式
对于表单的表达式exprexpr_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
,以及用户定义的类型,不能保证是原子的。除了为此目的而设计的库函数之外,不保证原子读 - 修改 - 写,例如在递增或递减的情况下。