Movatterモバイル変換


[0]ホーム

URL:


Skip to main content

This browser is no longer supported.

Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.

Download Microsoft EdgeMore info about Internet Explorer and Microsoft Edge
Table of contentsExit focus mode

9 Variables

  • 2024-02-07
Feedback

In this article

9.1 General

Variables represent storage locations. Every variable has a type that determines what values can be stored in the variable. C# is a type-safe language, and the C# compiler guarantees that values stored in variables are always of the appropriate type. The value of a variable can be changed through assignment or through use of the++ and-- operators.

A variable shall bedefinitely assigned (§9.4) before its value can be obtained.

As described in the following subclauses, variables are eitherinitially assigned orinitially unassigned. An initially assigned variable has a well-defined initial value and is always considered definitely assigned. An initially unassigned variable has no initial value. For an initially unassigned variable to be considered definitely assigned at a certain location, an assignment to the variable shall occur in every possible execution path leading to that location.

9.2 Variable categories

9.2.1 General

C# defines eight categories of variables: static variables, instance variables, array elements, value parameters, input parameters, reference parameters, output parameters, and local variables. The subclauses that follow describe each of these categories.

Example: In the following code

class A{    public static int x;    int y;    void F(int[] v, int a, ref int b, out int c, in int d)    {        int i = 1;        c = a + b++ + d;    }}

x is a static variable,y is an instance variable,v[0] is an array element,a is a value parameter,b is a reference parameter,c is an output parameter,d is an input parameter, andi is a local variable.end example

9.2.2 Static variables

A field declared with thestatic modifier is a static variable. A static variable comes into existence before execution of thestatic constructor (§15.12) for its containing type, and ceases to exist when the associated application domain ceases to exist.

The initial value of a static variable is the default value (§9.3) of the variable’s type.

For the purposes of definite-assignment checking, a static variable is considered initially assigned.

9.2.3 Instance variables

9.2.3.1 General

A field declared without thestatic modifier is an instance variable.

9.2.3.2 Instance variables in classes

An instance variable of a class comes into existence when a new instance of that class is created, and ceases to exist when there are no references to that instance and the instance’s finalizer (if any) has executed.

The initial value of an instance variable of a class is the default value (§9.3) of the variable’s type.

For the purpose of definite-assignment checking, an instance variable of a class is considered initially assigned.

9.2.3.3 Instance variables in structs

An instance variable of a struct has exactly the same lifetime as the struct variable to which it belongs. In other words, when a variable of a struct type comes into existence or ceases to exist, so too do the instance variables of the struct.

The initial assignment state of an instance variable of a struct is the same as that of the containingstruct variable. In other words, when a struct variable is considered initially assigned, so too are its instance variables, and when a struct variable is considered initially unassigned, its instance variables are likewise unassigned.

9.2.4 Array elements

The elements of an array come into existence when an array instance is created, and cease to exist when there are no references to that array instance.

The initial value of each of the elements of an array is the default value (§9.3) of the type of the array elements.

For the purpose of definite-assignment checking, an array element is considered initially assigned.

9.2.5 Value parameters

A value parameter comes into existence upon invocation of the function member (method, instance constructor, accessor, or operator) or anonymous function to which the parameter belongs, and is initialized with the value of the argument given in the invocation. A value parameter normally ceases to exist when execution of the function body completes. However, if the value parameter is captured by an anonymous function (§12.19.6.2), its lifetime extends at least until the delegate or expression tree created from that anonymous function is eligible for garbage collection.

For the purpose of definite-assignment checking, a value parameter is considered initially assigned.

Value parameters are discussed further in§15.6.2.2.

9.2.6 Reference parameters

A reference parameter is a reference variable (§9.7) which comes into existence upon invocation of the function member, delegate, anonymous function, or local function and its referent is initialized to the variable given as the argument in that invocation. A reference parameter ceases to exist when execution of the function body completes. Unlike value parameters a reference parameter shall not be captured (§9.7.2.9).

The following definite-assignment rules apply to reference parameters.

Note: The rules for output parameters are different, and are described in (§9.2.7).end note

  • A variable shall be definitely assigned (§9.4) before it can be passed as a reference parameter in a function member or delegate invocation.
  • Within a function member or anonymous function, a reference parameter is considered initially assigned.

Reference parameters are discussed further in§15.6.2.3.3.

9.2.7 Output parameters

An output parameter is a reference variable (§9.7) which comes into existence upon invocation of the function member, delegate, anonymous function, or local function and its referent is initialized to the variable given as the argument in that invocation. An output parameter ceases to exist when execution of the function body completes. Unlike value parameters an output parameter shall not be captured (§9.7.2.9).

The following definite-assignment rules apply to output parameters.

Note: The rules for reference parameters are different, and are described in (§9.2.6).end note

  • A variable need not be definitely assigned before it can be passed as an output parameter in a function member or delegate invocation.
  • Following the normal completion of a function member or delegate invocation, each variable that was passed as an output parameter is considered assigned in that execution path.
  • Within a function member or anonymous function, an output parameter is considered initially unassigned.
  • Every output parameter of a function member, anonymous function, or local function shall be definitely assigned (§9.4) before the function member, anonymous function, or local function returns normally.

Output parameters are discussed further in§15.6.2.3.4.

9.2.8 Input parameters

An input parameter is a reference variable (§9.7) which comes into existence upon invocation of the function member, delegate, anonymous function, or local function and its referent is initialized to thevariable_reference given as the argument in that invocation. An input parameter ceases to exist when execution of the function body completes. Unlike value parameters an input parameter shall not be captured (§9.7.2.9).

The following definite assignment rules apply to input parameters.

  • A variable shall be definitely assigned (§9.4) before it can be passed as an input parameter in a function member or delegate invocation.
  • Within a function member, anonymous function, or local function an input parameter is considered initially assigned.

Input parameters are discussed further in§15.6.2.3.2.

9.2.9 Local variables

9.2.9.1 General

Alocal variable is declared by alocal_variable_declaration,declaration_expression,foreach_statement, orspecific_catch_clause of atry_statement. A local variable can also be declared by certain kinds ofpatterns (§11). For aforeach_statement, the local variable is an iteration variable (§13.9.5). For aspecific_catch_clause, the local variable is an exception variable (§13.11). A local variable declared by aforeach_statement orspecific_catch_clause is considered initially assigned.

Alocal_variable_declaration can occur in ablock, afor_statement, aswitch_block, or ausing_statement. Adeclaration_expression can occur as anoutargument_value, and as atuple_element that is the target of a deconstructing assignment (§12.21.2).

The lifetime of a local variable is the portion of program execution during which storage is guaranteed to be reserved for it. This lifetime extends from entry into the scope with which it is associated, at least until execution of that scope ends in some way. (Entering an enclosedblock, calling a method, or yielding a value from an iterator block suspends, but does not end, execution of the current scope.) If the local variable is captured by an anonymous function (§12.19.6.2), its lifetime extends at least until the delegate or expression tree created from the anonymous function, along with any other objects that come to reference the captured variable, are eligible for garbage collection. If the parent scope is entered recursively or iteratively, a new instance of the local variable is created each time, and its initializer, if any, is evaluated each time.

Note: A local variable is instantiated each time its scope is entered. This behavior is visible to user code containing anonymous methods.end note

Note: The lifetime of aniteration variable (§13.9.5) declared by aforeach_statement is a single iteration of that statement. Each iteration creates a new variable.end note

Note: The actual lifetime of a local variable is implementation-dependent. For example, a compiler might statically determine that a local variable in a block is only used for a small portion of that block. Using this analysis, a compiler could generate code that results in the variable’s storage having a shorter lifetime than its containing block.

The storage referred to by a local reference variable is reclaimed independently of the lifetime of that local reference variable (§7.9).

end note

A local variable introduced by alocal_variable_declaration ordeclaration_expression is not automatically initialized and thus has no default value. Such a local variable is considered initially unassigned.

Note: Alocal_variable_declaration that includes an initializer is still initially unassigned. Execution of the declaration behaves exactly like an assignment to the variable (§9.4.4.5). Using a variable before its initializer has been executed; e.g., within the initializer expression itself or by using agoto_statement which bypasses the initializer; is a compile-time error:

goto L;int x = 1; // never executedL: x += 1; // error: x not definitely assigned

Within the scope of a local variable, it is a compile-time error to refer to that local variable in a textual position that precedes its declarator.

end note

9.2.9.2 Discards

Adiscard is a local variable that has no name. A discard is introduced by a declaration expression (§12.17) with the identifier_; and is either implicitly typed (_ orvar _) or explicitly typed (T _).

Note:_ is a valid identifier in many forms of declarations.end note

Because a discard has no name, the only reference to the variable it represents is the expression that introduces it.

Note: A discard can however be passed as an output argument, allowing the corresponding output parameter to denote its associated storage location.end note

A discard is not initially assigned, so it is always an error to access its value.

Example:

_ = "Hello".Length;(int, int, int) M(out int i1, out int i2, out int i3) { ... }(int _, var _, _) = M(out int _, out var _, out _);

The example assumes that there is no declaration of the name_ in scope.

The assignment to_ shows a simple pattern for ignoring the result of an expression.The call ofM shows the different forms of discards available in tuples and as output parameters.

end example

9.3 Default values

The following categories of variables are automatically initialized to their default values:

  • Static variables.
  • Instance variables of class instances.
  • Array elements.

The default value of a variable depends on the type of the variable and is determined as follows:

  • For a variable of avalue_type, the default value is the same as the value computed by thevalue_type’s default constructor (§8.3.3).
  • For a variable of areference_type, the default value isnull.

Note: Initialization to default values is typically done by having the memory manager or garbage collector initialize memory to all-bits-zero before it is allocated for use. For this reason, it is convenient to use all-bits-zero to represent the null reference.end note

9.4 Definite assignment

9.4.1 General

At a given location in the executable code of a function member or an anonymous function, a variable is said to bedefinitely assigned if a compiler can prove, by a particular static flow analysis (§9.4.4), that the variable has been automatically initialized or has been the target of at least one assignment.

Note: Informally stated, the rules of definite assignment are:

  • An initially assigned variable (§9.4.2) is always considered definitely assigned.
  • An initially unassigned variable (§9.4.3) is considered definitely assigned at a given location if all possible execution paths leading to that location contain at least one of the following:
    • A simple assignment (§12.21.2) in which the variable is the left operand.
    • An invocation expression (§12.8.10) or object creation expression (§12.8.17.2) that passes the variable as an output parameter.
    • For a local variable, a local variable declaration for the variable (§13.6.2) that includes a variable initializer.

The formal specification underlying the above informal rules is described in§9.4.2,§9.4.3, and§9.4.4.

end note

The definite-assignment states of instance variables of astruct_type variable are tracked individually as well as collectively. In additional to the rules described in§9.4.2,§9.4.3, and§9.4.4, the following rules apply tostruct_type variables and their instance variables:

  • An instance variable is considered definitely assigned if its containingstruct_type variable is considered definitely assigned.
  • Astruct_type variable is considered definitely assigned if each of its instance variables is considered definitely assigned.

Definite assignment is a requirement in the following contexts:

  • A variable shall be definitely assigned at each location where its value is obtained.

    Note: This ensures that undefined values never occur.end note

    The occurrence of a variable in an expression is considered to obtain the value of the variable, except when

    • the variable is the left operand of a simple assignment,
    • the variable is passed as an output parameter, or
    • the variable is astruct_type variable and occurs as the left operand of a member access.
  • A variable shall be definitely assigned at each location where it is passed as a reference parameter.

    Note: This ensures that the function member being invoked can consider the reference parameter initially assigned.end note

  • A variable shall be definitely assigned at each location where it is passed as an input parameter.

    Note: This ensures that the function member being invoked can consider the input parameter initially assigned.end note

  • All output parameters of a function member shall be definitely assigned at each location where the function member returns (through a return statement or through execution reaching the end of the function member body).

    Note: This ensures that function members do not return undefined values in output parameters, thus enabling a compiler to consider a function member invocation that takes a variable as an output parameter equivalent to an assignment to the variable.end note

  • Thethis variable of astruct_type instance constructor shall be definitely assigned at each location where that instance constructor returns.

9.4.2 Initially assigned variables

The following categories of variables are classified as initially assigned:

  • Static variables.
  • Instance variables of class instances.
  • Instance variables of initially assigned struct variables.
  • Array elements.
  • Value parameters.
  • Reference parameters.
  • Input parameters.
  • Variables declared in acatch clause or aforeach statement.

9.4.3 Initially unassigned variables

The following categories of variables are classified as initially unassigned:

  • Instance variables of initially unassigned struct variables.
  • Output parameters, including thethis variable of struct instance constructors without a constructor initializer.
  • Local variables, except those declared in acatch clause or aforeach statement.

9.4.4 Precise rules for determining definite assignment

9.4.4.1 General

In order to determine that each used variable is definitely assigned, a compiler shall use a process that is equivalent to the one described in this subclause.

The body of a function member may declare one or more initially unassigned variables. For each initially unassigned variablev, a compiler shall determine adefinite-assignment state forv at each of the following points in the function member:

  • At the beginning of each statement
  • At the end point (§13.2) of each statement
  • On each arc which transfers control to another statement or to the end point of a statement
  • At the beginning of each expression
  • At the end of each expression

The definite-assignment state ofv can be either:

  • Definitely assigned. This indicates that on all possible control flows to this point,v has been assigned a value.
  • Not definitely assigned. For the state of a variable at the end of an expression of typebool, the state of a variable that isn’t definitely assigned might (but doesn’t necessarily) fall into one of the following sub-states:
    • Definitely assigned after true expression. This state indicates thatv is definitely assigned if the Boolean expression evaluated as true, but is not necessarily assigned if the Boolean expression evaluated as false.
    • Definitely assigned after false expression. This state indicates thatv is definitely assigned if the Boolean expression evaluated as false, but is not necessarily assigned if the Boolean expression evaluated as true.

The following rules govern how the state of a variablev is determined at each location.

9.4.4.2 General rules for statements

  • v is not definitely assigned at the beginning of a function member body.
  • The definite-assignment state ofv at the beginning of any other statement is determined by checking the definite-assignment state ofv on all control flow transfers that target the beginning of that statement. If (and only if)v is definitely assigned on all such control flow transfers, thenv is definitely assigned at the beginning of the statement. The set of possible control flow transfers is determined in the same way as for checking statement reachability (§13.2).
  • The definite-assignment state ofv at the end point of ablock,checked,unchecked,if,while,do,for,foreach,lock,using, orswitch statement is determined by checking the definite-assignment state ofv on all control flow transfers that target the end point of that statement. Ifv is definitely assigned on all such control flow transfers, thenv is definitely assigned at the end point of the statement. Otherwise,v is not definitely assigned at the end point of the statement. The set of possible control flow transfers is determined in the same way as for checking statement reachability (§13.2).

Note: Because there are no control paths to an unreachable statement,v is definitely assigned at the beginning of any unreachable statement.end note

9.4.4.3 Block statements, checked, and unchecked statements

The definite-assignment state ofv on the control transfer to the first statement of the statement list in the block (or to the end point of the block, if the statement list is empty) is the same as the definite-assignment statement ofv before the block,checked, orunchecked statement.

9.4.4.4 Expression statements

For an expression statementstmt that consists of the expressionexpr:

  • v has the same definite-assignment state at the beginning ofexpr as at the beginning ofstmt.
  • Ifv if definitely assigned at the end ofexpr, it is definitely assigned at the end point ofstmt; otherwise, it is not definitely assigned at the end point ofstmt.

9.4.4.5 Declaration statements

  • Ifstmt is a declaration statement without initializers, thenv has the same definite-assignment state at the end point ofstmt as at the beginning ofstmt.
  • Ifstmt is a declaration statement with initializers, then the definite-assignment state forv is determined as ifstmt were a statement list, with one assignment statement for each declaration with an initializer (in the order of declaration).

9.4.4.6 If statements

For a statementstmt of the form:

if ( «expr» ) «then_stmt» else «else_stmt»
  • v has the same definite-assignment state at the beginning ofexpr as at the beginning ofstmt.
  • Ifv is definitely assigned at the end ofexpr, then it is definitely assigned on the control flow transfer tothen_stmt and to eitherelse_stmt or to the end-point ofstmt if there is no else clause.
  • Ifv has the state “definitely assigned after true expression” at the end ofexpr, then it is definitely assigned on the control flow transfer tothen_stmt, and not definitely assigned on the control flow transfer to eitherelse_stmt or to the end-point ofstmt if there is no else clause.
  • Ifv has the state “definitely assigned after false expression” at the end ofexpr, then it is definitely assigned on the control flow transfer toelse_stmt, and not definitely assigned on the control flow transfer tothen_stmt. It is definitely assigned at the end-point ofstmt if and only if it is definitely assigned at the end-point ofthen_stmt.
  • Otherwise,v is considered not definitely assigned on the control flow transfer to either thethen_stmt orelse_stmt, or to the end-point ofstmt if there is no else clause.

9.4.4.7 Switch statements

For aswitch statementstmt with a controlling expressionexpr:

The definite-assignment state ofv at the beginning ofexpr is the same as the state ofv at the beginning ofstmt.

The definite-assignment state ofv at the beginning of a case’s guard clause is

  • Ifv is a pattern variable declared in theswitch_label: “definitely assigned”.
  • If the switch label containing that guard clause (§13.8.3) is not reachable: “definitely assigned”.
  • Otherwise, the state ofv is the same as the state ofv afterexpr.

Example: The second rule eliminates the need for a compiler to issue an error if an unassigned variable is accessed in unreachable code. The state ofb is “definitely assigned” in the unreachable switch labelcase 2 when b.

bool b;switch (1) {    case 2 when b: // b is definitely assigned here.    break;}

end example

The definite-assignment state ofv on the control flow transfer to a reachable switch block statement list is

  • If the control transfer was due to a ‘goto case’ or ‘goto default’ statement, then the state ofv is the same as the state at the beginning of that ‘goto’ statement.
  • If the control transfer was due to thedefault label of the switch, then the state ofv is the same as the state ofv afterexpr.
  • If the control transfer was due to an unreachable switch label, then the state ofv is “definitely assigned”.
  • If the control transfer was due to a reachable switch label with a guard clause, then the state ofv is the same as the state ofv after the guard clause.
  • If the control transfer was due to a reachable switch label without a guard clause, then the state ofv is
    • Ifv is a pattern variable declared in theswitch_label: “definitely assigned”.
    • Otherwise, the state ofv is the same as the stat ofv afterexpr.

A consequence of these rules is that a pattern variable declared in aswitch_label will be “not definitely assigned” in the statements of its switch section if it is not the only reachable switch label in its section.

Example:

public static double ComputeArea(object shape){    switch (shape)    {        case Square s when s.Side == 0:        case Circle c when c.Radius == 0:        case Triangle t when t.Base == 0 || t.Height == 0:        case Rectangle r when r.Length == 0 || r.Height == 0:            // none of s, c, t, or r is definitely assigned            return 0;        case Square s:            // s is definitely assigned            return s.Side * s.Side;        case Circle c:            // c is definitely assigned            return c.Radius * c.Radius * Math.PI;           …    }}

end example

9.4.4.8 While statements

For a statementstmt of the form:

while ( «expr» ) «while_body»
  • v has the same definite-assignment state at the beginning ofexpr as at the beginning ofstmt.
  • Ifv is definitely assigned at the end ofexpr, then it is definitely assigned on the control flow transfer towhile_body and to the end point ofstmt.
  • Ifv has the state “definitely assigned after true expression” at the end ofexpr, then it is definitely assigned on the control flow transfer towhile_body, but not definitely assigned at the end-point ofstmt.
  • Ifv has the state “definitely assigned after false expression” at the end ofexpr, then it is definitely assigned on the control flow transfer to the end point ofstmt, but not definitely assigned on the control flow transfer towhile_body.

9.4.4.9 Do statements

For a statementstmt of the form:

do «do_body» while ( «expr» ) ;
  • v has the same definite-assignment state on the control flow transfer from the beginning ofstmt todo_body as at the beginning ofstmt.
  • v has the same definite-assignment state at the beginning ofexpr as at the end point ofdo_body.
  • Ifv is definitely assigned at the end ofexpr, then it is definitely assigned on the control flow transfer to the end point ofstmt.
  • Ifv has the state “definitely assigned after false expression” at the end ofexpr, then it is definitely assigned on the control flow transfer to the end point ofstmt, but not definitely assigned on the control flow transfer todo_body.

9.4.4.10 For statements

For a statement of the form:

for ( «for_initializer» ; «for_condition» ; «for_iterator» )    «embedded_statement»

definite-assignment checking is done as if the statement were written:

{    «for_initializer» ;    while ( «for_condition» )    {        «embedded_statement» ;        LLoop: «for_iterator» ;    }}

withcontinue statements that target thefor statement being translated togoto statements targeting the labelLLoop. If thefor_condition is omitted from thefor statement, then evaluation of definite-assignment proceeds as iffor_condition were replaced with true in the above expansion.

9.4.4.11 Break, continue, and goto statements

The definite-assignment state ofv on the control flow transfer caused by abreak,continue, orgoto statement is the same as the definite-assignment state ofv at the beginning of the statement.

9.4.4.12 Throw statements

For a statementstmt of the form:

throw «expr» ;

the definite-assignment state ofv at the beginning ofexpr is the same as the definite-assignment state ofv at the beginning ofstmt.

9.4.4.13 Return statements

For a statementstmt of the form:

return «expr» ;
  • The definite-assignment state ofv at the beginning ofexpr is the same as the definite-assignment state ofv at the beginning ofstmt.
  • Ifv is an output parameter, then it shall be definitely assigned either:
    • afterexpr
    • or at the end of thefinally block of atry-finally ortry-catch-finally that encloses thereturn statement.

For a statementstmt of the form:

return ;
  • Ifv is an output parameter, then it shall be definitely assigned either:
    • beforestmt
    • or at the end of thefinally block of atry-finally ortry-catch-finally that encloses thereturn statement.

9.4.4.14 Try-catch statements

For a statementstmt of the form:

try «try_block»catch ( ... ) «catch_block_1»...catch ( ... ) «catch_block_n»
  • The definite-assignment state ofv at the beginning oftry_block is the same as the definite-assignment state ofv at the beginning ofstmt.
  • The definite-assignment state ofv at the beginning ofcatch_block_i (for anyi) is the same as the definite-assignment state ofv at the beginning ofstmt.
  • The definite-assignment state ofv at the end-point ofstmt is definitely assigned if (and only if)v is definitely assigned at the end-point oftry_block and everycatch_block_i (for everyi from 1 ton).

9.4.4.15 Try-finally statements

For a statementstmt of the form:

try «try_block» finally «finally_block»
  • The definite-assignment state ofv at the beginning oftry_block is the same as the definite-assignment state ofv at the beginning ofstmt.
  • The definite-assignment state ofv at the beginning offinally_block is the same as the definite-assignment state ofv at the beginning ofstmt.
  • The definite-assignment state ofv at the end-point ofstmt is definitely assigned if (and only if) at least one of the following is true:
    • v is definitely assigned at the end-point oftry_block
    • v is definitely assigned at the end-point offinally_block

If a control flow transfer (such as agoto statement) is made that begins withintry_block, and ends outside oftry_block, thenv is also considered definitely assigned on that control flow transfer ifv is definitely assigned at the end-point offinally_block. (This is not an only if—ifv is definitely assigned for another reason on this control flow transfer, then it is still considered definitely assigned.)

9.4.4.16 Try-catch-finally statements

For a statement of the form:

try «try_block»catch ( ... ) «catch_block_1»...catch ( ... ) «catch_block_n»finally «finally_block»

definite-assignment analysis is done as if the statement were atry-finally statement enclosing atry-catch statement:

try{    try «try_block»    catch ( ... ) «catch_block_1»    ...    catch ( ... ) «catch_block_n»}finally «finally_block»

Example: The following example demonstrates how the different blocks of atry statement (§13.11) affect definite assignment.

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    }}

end example

9.4.4.17 Foreach statements

For a statementstmt of the form:

foreach ( «type» «identifier» in «expr» ) «embedded_statement»
  • The definite-assignment state ofv at the beginning ofexpr is the same as the state ofv at the beginning ofstmt.
  • The definite-assignment state ofv on the control flow transfer toembedded_statement or to the end point ofstmt is the same as the state ofv at the end ofexpr.

9.4.4.18 Using statements

For a statementstmt of the form:

using ( «resource_acquisition» ) «embedded_statement»
  • The definite-assignment state ofv at the beginning ofresource_acquisition is the same as the state ofv at the beginning ofstmt.
  • The definite-assignment state ofv on the control flow transfer toembedded_statement is the same as the state ofv at the end ofresource_acquisition.

9.4.4.19 Lock statements

For a statementstmt of the form:

lock ( «expr» ) «embedded_statement»
  • The definite-assignment state ofv at the beginning ofexpr is the same as the state ofv at the beginning ofstmt.
  • The definite-assignment state ofv on the control flow transfer toembedded_statement is the same as the state ofv at the end ofexpr.

9.4.4.20 Yield statements

For a statementstmt of the form:

yield return «expr» ;
  • The definite-assignment state ofv at the beginning ofexpr is the same as the state ofv at the beginning ofstmt.
  • The definite-assignment state ofv at the end ofstmt is the same as the state ofv at the end ofexpr.

Ayield break statement has no effect on the definite-assignment state.

9.4.4.21 General rules for constant expressions

The following applies to any constant expression, and takes priority over any rules from the following subclauses that might apply:

For a constant expression with valuetrue:

  • Ifv is definitely assigned before the expression, thenv is definitely assigned after the expression.
  • Otherwisev is “definitely assigned after false expression” after the expression.

Example:

int x;if (true) {}else{    Console.WriteLine(x);}

end example

For a constant expression with valuefalse:

  • Ifv is definitely assigned before the expression, thenv is definitely assigned after the expression.
  • Otherwisev is “definitely assigned after true expression” after the expression.

Example:

int x;if (false){    Console.WriteLine(x);}

end example

For all other constant expressions, the definite-assignment state ofv after the expression is the same as the definite-assignment state ofv before the expression.

9.4.4.22 General rules for simple expressions

The following rule applies to these kinds of expressions: literals (§12.8.2), simple names (§12.8.4), member access expressions (§12.8.7), non-indexed base access expressions (§12.8.15),typeof expressions (§12.8.18), default value expressions (§12.8.21),nameof expressions (§12.8.23), and declaration expressions (§12.17).

  • The definite-assignment state ofv at the end of such an expression is the same as the definite-assignment state ofv at the beginning of the expression.

9.4.4.23 General rules for expressions with embedded expressions

The following rules apply to these kinds of expressions: parenthesized expressions (§12.8.5), tuple expressions (§12.8.6), element access expressions (§12.8.12), base access expressions with indexing (§12.8.15), increment and decrement expressions (§12.8.16,§12.9.6), cast expressions (§12.9.7), unary+,-,~,* expressions, binary+,-,*,/,%,<<,>>,<,<=,>,>=,==,!=,is,as,&,|,^ expressions (§12.10,§12.11,§12.12,§12.13), compound assignment expressions (§12.21.4),checked andunchecked expressions (§12.8.20), array and delegate creation expressions (§12.8.17) , andawait expressions (§12.9.8).

Each of these expressions has one or more subexpressions that are unconditionally evaluated in a fixed order.

Example: The binary% operator evaluates the left hand side of the operator, then the right hand side. An indexing operation evaluates the indexed expression, and then evaluates each of the index expressions, in order from left to right.end example

For an expressionexpr, which has subexpressionsexpr₁,expr₂, …,exprₓ, evaluated in that order:

  • The definite-assignment state ofv at the beginning ofexpr₁ is the same as the definite-assignment state at the beginning ofexpr.
  • The definite-assignment state ofv at the beginning ofexprᵢ (i greater than one) is the same as the definite-assignment state at the end ofexprᵢ₋₁.
  • The definite-assignment state ofv at the end ofexpr is the same as the definite-assignment state at the end ofexprₓ.

9.4.4.24 Invocation expressions and object creation expressions

If the method to be invoked is a partial method that has no implementing partial method declaration, or is a conditional method for which the call is omitted (§22.5.3.2), then the definite-assignment state ofv after the invocation is the same as the definite-assignment state ofv before the invocation. Otherwise the following rules apply:

For an invocation expressionexpr of the form:

«primary_expression» ( «arg₁», «arg₂», … , «argₓ» )

or an object-creation expressionexpr of the form:

new «type» ( «arg₁», «arg₂», … , «argₓ» )
  • For an invocation expression, the definite assignment state ofv beforeprimary_expression is the same as the state ofv beforeexpr.
  • For an invocation expression, the definite assignment state ofv beforearg₁ is the same as the state ofv afterprimary_expression.
  • For an object creation expression, the definite assignment state ofv beforearg₁ is the same as the state ofv beforeexpr.
  • For each argumentargᵢ, the definite assignment state ofv afterargᵢ is determined by the normal expression rules, ignoring anyin,out, orref modifiers.
  • For each argumentargᵢ for anyi greater than one, the definite assignment state ofv beforeargᵢ is the same as the state ofv afterargᵢ₋₁.
  • If the variablev is passed as anout argument (i.e., an argument of the form “outv”) in any of the arguments, then the state ofv afterexpr is definitely assigned. Otherwise, the state ofv afterexpr is the same as the state ofv afterargₓ.
  • For array initializers (§12.8.17.4), object initializers (§12.8.17.2.2), collection initializers (§12.8.17.2.3) and anonymous object initializers (§12.8.17.3), the definite-assignment state is determined by the expansion that these constructs are defined in terms of.

9.4.4.25 Simple assignment expressions

Let the set ofassignment targets in an expressione be defined as follows:

  • Ife is a tuple expression, then the assignment targets ine are the union of the assignment targets of the elements ofe.
  • Otherwise, the assignment targets ine aree.

For an expressionexpr of the form:

«expr_lhs» = «expr_rhs»
  • The definite-assignment state ofv beforeexpr_lhs is the same as the definite-assignment state ofv beforeexpr.
  • The definite-assignment state ofv beforeexpr_rhs is the same as the definite-assignment state ofv afterexpr_lhs.
  • Ifv is an assignment target ofexpr_lhs, then the definite-assignment state ofv afterexpr is definitely assigned. Otherwise, if the assignment occurs within the instance constructor of a struct type, andv is the hidden backing field of an automatically implemented propertyP on the instance being constructed, and a property access designatingP is an assigment target ofexpr_lhs, then the definite-assignment state ofv afterexpr is definitely assigned. Otherwise, the definite-assignment state ofv afterexpr is the same as the definite-assignment state ofv afterexpr_rhs.

Example: In the following code

class A{    static void F(int[] arr)    {        int x;        arr[x = 1] = x; // ok    }}

the variablex is considered definitely assigned afterarr[x = 1] is evaluated as the left hand side of the second simple assignment.

end example

9.4.4.26 && expressions

For an expressionexpr of the form:

«expr_first» && «expr_second»
  • The definite-assignment state ofv beforeexpr_first is the same as the definite-assignment state ofv beforeexpr.
  • The definite-assignment state ofv beforeexpr_second is definitely assigned if and only if the state ofv afterexpr_first is either definitely assigned or “definitely assigned after true expression”. Otherwise, it is not definitely assigned.
  • The definite-assignment state ofv afterexpr is determined by:
    • If the state ofv afterexpr_first is definitely assigned, then the state ofv afterexpr is definitely assigned.
    • Otherwise, if the state ofv afterexpr_second is definitely assigned, and the state ofv afterexpr_first is “definitely assigned after false expression”, then the state ofv afterexpr is definitely assigned.
    • Otherwise, if the state ofv afterexpr_second is definitely assigned or “definitely assigned after true expression”, then the state ofv afterexpr is “definitely assigned after true expression”.
    • Otherwise, if the state ofv afterexpr_first is “definitely assigned after false expression”, and the state ofv afterexpr_second is “definitely assigned after false expression”, then the state ofv afterexpr is “definitely assigned after false expression”.
    • Otherwise, the state ofv afterexpr is not definitely assigned.

Example: In the following code

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    }}

the variablei is considered definitely assigned in one of the embedded statements of anif statement but not in the other. In theif statement in methodF, the variablei is definitely assigned in the first embedded statement because execution of the expression(i = y) always precedes execution of this embedded statement. In contrast, the variablei is not definitely assigned in the second embedded statement, sincex >= 0 might have tested false, resulting in the variablei’s being unassigned.

end example

9.4.4.27 || expressions

For an expressionexpr of the form:

«expr_first» || «expr_second»
  • The definite-assignment state ofv beforeexpr_first is the same as the definite-assignment state ofv beforeexpr.
  • The definite-assignment state ofv beforeexpr_second is definitely assigned if and only if the state ofv afterexpr_first is either definitely assigned or “definitely assigned after true expression”. Otherwise, it is not definitely assigned.
  • The definite-assignment statement ofv afterexpr is determined by:
    • If the state ofv afterexpr_first is definitely assigned, then the state ofv afterexpr is definitely assigned.
    • Otherwise, if the state ofv afterexpr_second is definitely assigned, and the state ofv afterexpr_first is “definitely assigned after true expression”, then the state ofv afterexpr is definitely assigned.
    • Otherwise, if the state ofv afterexpr_second is definitely assigned or “definitely assigned after false expression”, then the state ofv afterexpr is “definitely assigned after false expression”.
    • Otherwise, if the state ofv afterexpr_first is “definitely assigned after true expression”, and the state ofv afterexpr_ second is “definitely assigned after true expression”, then the state ofv afterexpr is “definitely assigned after true expression”.
    • Otherwise, the state ofv afterexpr is not definitely assigned.

Example: In the following code

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    }}

the variablei is considered definitely assigned in one of the embedded statements of anif statement but not in the other. In theif statement in methodG, the variablei is definitely assigned in the second embedded statement because execution of the expression(i = y) always precedes execution of this embedded statement. In contrast, the variablei is not definitely assigned in the first embedded statement, sincex >= 0 might have tested true, resulting in the variablei’s being unassigned.

end example

9.4.4.28 ! expressions

For an expressionexpr of the form:

! «expr_operand»
  • The definite-assignment state ofv beforeexpr_operand is the same as the definite-assignment state ofv beforeexpr.
  • The definite-assignment state ofv afterexpr is determined by:
    • If the state ofv afterexpr_operand is definitely assigned, then the state ofv afterexpr is definitely assigned.
    • Otherwise, if the state ofv afterexpr_operand is “definitely assigned after false expression”, then the state ofv afterexpr is “definitely assigned after true expression”.
    • Otherwise, if the state ofv afterexpr_operand is “definitely assigned after true expression”, then the state of v afterexpr is “definitely assigned after false expression”.
    • Otherwise, the state ofv afterexpr is not definitely assigned.

9.4.4.29 ?? expressions

For an expressionexpr of the form:

«expr_first» ?? «expr_second»
  • The definite-assignment state ofv beforeexpr_first is the same as the definite-assignment state ofv beforeexpr.
  • The definite-assignment state ofv beforeexpr_second is the same as the definite-assignment state ofv afterexpr_first.
  • The definite-assignment statement ofv afterexpr is determined by:
    • Ifexpr_first is a constant expression (§12.23) with valuenull, then the state ofv afterexpr is the same as the state ofv afterexpr_second.
    • Otherwise, the state ofv afterexpr is the same as the definite-assignment state ofv afterexpr_first.

9.4.4.30 ?: expressions

For an expressionexpr of the form:

«expr_cond» ? «expr_true» : «expr_false»
  • The definite-assignment state ofv beforeexpr_cond is the same as the state ofv beforeexpr.
  • The definite-assignment state ofv beforeexpr_true is definitely assigned if the state ofv afterexpr_cond is definitely assigned or “definitely assigned after true expression”.
  • The definite-assignment state ofv beforeexpr_false is definitely assigned if the state ofv afterexpr_cond is definitely assigned or “definitely assigned after false expression”.
  • The definite-assignment state ofv afterexpr is determined by:
    • Ifexpr_cond is a constant expression (§12.23) with valuetrue then the state ofv afterexpr is the same as the state ofv afterexpr_true.
    • Otherwise, ifexpr_cond is a constant expression (§12.23) with valuefalse then the state ofv afterexpr is the same as the state ofv afterexpr_false.
    • Otherwise, if the state ofv afterexpr_true is definitely assigned and the state ofv afterexpr_false is definitely assigned, then the state ofv afterexpr is definitely assigned.
    • Otherwise, the state ofv afterexpr is not definitely assigned.

9.4.4.31 Anonymous functions

For alambda_expression oranonymous_method_expressionexpr with a body (eitherblock orexpression)body:

  • The definite assignment state of a parameter is the same as for a parameter of a named method (§9.2.6,§9.2.7,§9.2.8).
  • The definite assignment state of an outer variablev beforebody is the same as the state ofv beforeexpr. That is, definite assignment state of outer variables is inherited from the context of the anonymous function.
  • The definite assignment state of an outer variablev afterexpr is the same as the state ofv beforeexpr.

Example: The example

class A{    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);    }    void DoWork(Filter f) { ... }}

generates a compile-time error since max is not definitely assigned where the anonymous function is declared.

end example

Example: The example

class A{    delegate void D();    void F()    {        int n;        D d = () => { n = 1; };        d();        // Error, n is not definitely assigned        Console.WriteLine(n);    }}

also generates a compile-time error since the assignment ton in the anonymous function has no affect on the definite-assignment state ofn outside the anonymous function.

end example

9.4.4.32 Throw expressions

For an expressionexpr of the form:

throwthrown_expr

  • The definite assignment state ofv beforethrown_expr is the same as the state ofv beforeexpr.
  • The definite assignment state ofv afterexpr is “definitely assigned”.

9.4.4.33 Rules for variables in local functions

Local functions are analyzed in the context of their parent method. There are two control flow paths that matter for local functions: function calls and delegate conversions.

Definite assignment for the body of each local function is defined separately for each call site. At each invocation, variables captured by the local function are considered definitely assigned if they were definitely assigned at the point of call. A control flow path also exists to the local function body at this point and is considered reachable. After a call to the local function, captured variables that were definitely assigned at every control point leaving the function (return statements,yield statements,await expressions) are considered definitely assigned after the call location.

Delegate conversions have a control flow path to the local function body. Captured variables are definitely assigned for the body if they are definitely assigned before the conversion. Variables assigned by the local function are not considered assigned after the conversion.

Note: the above implies that bodies are re-analyzed for definite assignment at every local function invocation or delegate conversion. Compilers are not required to re-analyze the body of a local function at each invocation or delegate conversion. The implementation must produce results equivalent to that description.end note

Example: The following example demonstrates definite assignment for captured variables in local functions. If a local function reads a captured variable before writing it, the captured variable must be definitely assigned before calling the local function. The local functionF1 readss without assigning it. It is an error ifF1 is called befores is definitely assigned.F2 assignsi before reading it. It may be called beforei is definitely assigned. Furthermore,F3 may be called afterF2 becauses2 is definitely assigned inF2.

void M(){    string s;    int i;    string s2;       // Error: Use of unassigned local variable s:    F1();    // OK, F2 assigns i before reading it.    F2();        // OK, i is definitely assigned in the body of F2:    s = i.ToString();        // OK. s is now definitely assigned.    F1();    // OK, F3 reads s2, which is definitely assigned in F2.    F3();    void F1()    {        Console.WriteLine(s);    }        void F2()    {        i = 5;        // OK. i is definitely assigned.        Console.WriteLine(i);        s2 = i.ToString();    }    void F3()    {        Console.WriteLine(s2);    }}

end example

9.4.4.34 is-pattern expressions

For an expressionexpr of the form:

expr_operand ispattern

  • The definite-assignment state ofv beforeexpr_operand is the same as the definite-assignment state ofv beforeexpr.
  • If the variable ‘v’ is declared inpattern, then the definite-assignment state of ‘v’ afterexpr is “definitely assigned when true”.
  • Otherwise the definite assignment state of ‘v’ afterexpr is the same as the definite assignment state of ‘v’ afterexpr_operand.

9.5 Variable references

Avariable_reference is anexpression that is classified as a variable. Avariable_reference denotes a storage location that can be accessed both to fetch the current value and to store a new value.

variable_reference    : expression    ;

Note: In C and C++, avariable_reference is known as anlvalue.end note

9.6 Atomicity of variable references

Reads and writes of the following data types shall be atomic:bool,char,byte,sbyte,short,ushort,uint,int,float, and reference types. In addition, reads and writes of enum types with an underlying type in the previous list shall also be atomic. Reads and writes of other types, includinglong,ulong,double, anddecimal, as well as user-defined types, need not be atomic. Aside from the library functions designed for that purpose, there is no guarantee of atomic read-modify-write, such as in the case of increment or decrement.

9.7 Reference variables and returns

9.7.1 General

Areference variable is a variable that refers to another variable, called the referent (§9.2.6). A reference variable is a local variable declared with theref modifier.

A reference variable stores avariable_reference (§9.5) to its referent and not the value of its referent. When a reference variable is used where a value is required its referent’s value is returned; similarly when a reference variable is the target of an assignment it is the referent which is assigned to. The variable to which a reference variable refers, i.e. the storedvariable_reference for its referent, can be changed using a ref assignment (= ref).

Example: The following example demonstrates a local reference variable whose referent is an element of an array:

public class C{    public void M()    {        int[] arr = new int[10];        // element is a reference variable that refers to arr[5]        ref int element = ref arr[5];        element += 5; // arr[5] has been incremented by 5    }     }

end example

Areference return is thevariable_reference returned from a returns-by-ref method (§15.6.1). Thisvariable_reference is the referent of the reference return.

Example: The following example demonstrates a reference return whose referent is an element of an array field:

public class C{    private int[] arr = new int[10];    public ref readonly int M()    {        // element is a reference variable that refers to arr[5]        ref int element = ref arr[5];        return ref element; // return reference to arr[5];    }     }

end example

9.7.2 Ref safe contexts

9.7.2.1 General

All reference variables obey safety rules that ensure the ref-safe-context of the reference variable is not greater than the ref-safe-context of its referent.

Note: The related notion of asafe-context is defined in (§16.4.15), along with associated constraints.end note

For any variable, theref-safe-context of that variable is the context where avariable_reference (§9.5) to that variable is valid. The referent of a reference variable shall have a ref-safe-context that is at least as wide as the ref-safe-context of the reference variable itself.

Note: A compiler determines the ref-safe-context through a static analysis of the program text. The ref-safe-context reflects the lifetime of a variable at runtime.end note

There are three ref-safe-contexts:

  • declaration-block: The ref-safe-context of avariable_reference to a local variable (§9.2.9.1) is that local variable’s scope (§13.6.2), including any nestedembedded-statements in that scope.

    Avariable_reference to a local variable is a valid referent for a reference variable only if the reference variable is declared within the ref-safe-context of that variable.

  • function-member: Within a function avariable_reference to any of the following has a ref-safe-context of function-member:

    • Value parameters (§15.6.2.2) on a function member declaration, including the implicitthis of class member functions; and
    • The implicit reference (ref) parameter (§15.6.2.3.3)this of a struct member function, along with its fields.

    Avariable_reference with ref-safe-context of function-member is a valid referent only if the reference variable is declared in the same function member.

  • caller-context: Within a function avariable_reference to any of the following has a ref-safe-context of caller-context:

    • Reference parameters (§9.2.6) other than the implicitthis of a struct member function;
    • Member fields and elements of such parameters;
    • Member fields of parameters of class type; and
    • Elements of parameters of array type.

Avariable_reference with ref-safe-context of caller-context can be the referent of a reference return.

These values form a nesting relationship from narrowest (declaration-block) to widest (caller-context). Each nested block represents a different context.

Example: The following code shows examples of the different ref-safe-contexts. The declarations show the ref-safe-context for a referent to be the initializing expression for aref variable. The examples show the ref-safe-context for a reference return:

public class C{    // ref safe context of arr is "caller-context".     // ref safe context of arr[i] is "caller-context".    private int[] arr = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };     // ref safe context is "caller-context"    public ref int M1(ref int r1)    {        return ref r1; // r1 is safe to ref return    }    // ref safe context is "function-member"    public ref int M2(int v1)    {        return ref v1; // error: v1 isn't safe to ref return    }    public ref int M3()    {        int v2 = 5;        return ref arr[v2]; // arr[v2] is safe to ref return    }    public void M4(int p)     {        int v3 = 6;        // context of r2 is declaration-block,        // ref safe context of p is function-member        ref int r2 = ref p;        // context of r3 is declaration-block,        // ref safe context of v3 is declaration-block        ref int r3 = ref v3;        // context of r4 is declaration-block,        // ref safe context of arr[v3] is caller-context        ref int r4 = ref arr[v3];     }}

end example.

Example: Forstruct types, the implicitthis parameter is passed as a reference parameter. The ref-safe-context of the fields of astruct type as function-member prevents returning those fields by reference return. This rule prevents the following code:

public struct S{     private int n;     // Disallowed: returning ref of a field.     public ref int GetN() => ref n;}class Test{    public ref int M()    {        S s = new S();        ref int numRef = ref s.GetN();        return ref numRef; // reference to local variable 'numRef' returned    }}

end example.

9.7.2.2 Local variable ref safe context

For a local variablev:

  • Ifv is a reference variable, its ref-safe-context is the same as the ref-safe-context of its initializing expression.
  • Otherwise its ref-safe-context is declaration-block.

9.7.2.3 Parameter ref safe context

For a parameterp:

  • Ifp is a reference or input parameter, its ref-safe-context is the caller-context. Ifp is an input parameter, it can’t be returned as a writableref but can be returned asref readonly.
  • Ifp is an output parameter, its ref-safe-context is the caller-context.
  • Otherwise, ifp is thethis parameter of a struct type, its ref-safe-context is the function-member.
  • Otherwise, the parameter is a value parameter, and its ref-safe-context is the function-member.

9.7.2.4 Field ref safe context

For a variable designating a reference to a field,e.F:

  • Ife is of a reference type, its ref-safe-context is the caller-context.
  • Otherwise, ife is of a value type, its ref-safe-context is the same as the ref-safe-context ofe.

9.7.2.5 Operators

The conditional operator (§12.18),c ? ref e1 : ref e2, and reference assignment operator,= ref e (§12.21.1) have reference variables as operands and yield a reference variable. For those operators, the ref-safe-context of the result is the narrowest context among the ref-safe-contexts of allref operands.

9.7.2.6 Function invocation

For a variablec resulting from a ref-returning function invocation, its ref-safe-context is the narrowest of the following contexts:

  • The caller-context.
  • The ref-safe-context of allref,out, andin argument expressions (excluding the receiver).
  • For each input parameter, if there is a corresponding expression that is a variable and there exists an identity conversion between the type of the variable and the type of the parameter, the variable’s ref-safe-context, otherwise the nearest enclosing context.
  • The safe-context (§16.4.15) of all argument expressions (including the receiver).

Example: the last bullet is necessary to handle code such as

ref int M2(){    int v = 5;    // Not valid.    // ref safe context of "v" is block.    // Therefore, ref safe context of the return value of M() is block.    return ref M(ref v);}ref int M(ref int p){    return ref p;}

end example

A property invocation and an indexer invocation (eitherget orset) is treated as a function invocation of the underlying accessor by the above rules. A local function invocation is a function invocation.

9.7.2.7 Values

A value’s ref-safe-context is the nearest enclosing context.

Note: This occurs in an invocation such asM(ref d.Length) whered is of typedynamic. It is also consistent with arguments corresponding to input parameters.end note

9.7.2.8 Constructor invocations

Anew expression that invokes a constructor obeys the same rules as a method invocation (§9.7.2.6) that is considered to return the type being constructed.

9.7.2.9 Limitations on reference variables

  • Neither a reference parameter, nor an output parameter, nor an input parameter, nor aref local, nor a parameter or local of aref struct type shall be captured by lambda expression or local function.
  • Neither a reference parameter, nor an output parameter, nor an input parameter, nor a parameter of aref struct type shall be an argument for an iterator method or anasync method.
  • Neither aref local, nor a local of aref struct type shall be in context at the point of ayield return statement or anawait expression.
  • For a ref reassignmente1 = ref e2, the ref-safe-context ofe2 shall be at least as wide a context as theref-safe-context ofe1.
  • For a ref return statementreturn ref e1, the ref-safe-context ofe1 shall be the caller-context.
Collaborate with us on GitHub
The source for this content can be found on GitHub, where you can also create and review issues and pull requests. For more information, seeour contributor guide.

Feedback

Was this page helpful?

YesNo

In this article

Was this page helpful?

YesNo