Compartir a través de


9 variables

9.1 General

Las variables representan ubicaciones de almacenamiento. Cada variable tiene un tipo que determina qué valores se pueden almacenar en ella. C# es un lenguaje seguro para tipos y el compilador de C# garantiza que los valores almacenados en variables siempre sean del tipo adecuado. El valor de una variable se puede cambiar a través de la asignación o mediante el uso de los operadores ++ y --.

Se debe asignar definitivamente una variable (§9.4) antes de que se pueda obtener su valor.

Como se describe en las subcláusulas siguientes, las variables se asignan inicialmente o no se asignan inicialmente. Una variable asignada inicialmente tiene un valor inicial definido y siempre se considera asignada definitivamente. Una variable inicialmente sin asignar no tiene ningún valor inicial. Para que una variable inicialmente sin asignar se considere asignada definitivamente en una ubicación determinada, se debe producir una asignación a la variable en cada ruta de ejecución posible que conduzca a esa ubicación.

9.2 Categorías de variable

9.2.1 General

C# define ocho categorías de variables: variables estáticas, variables de instancia, elementos de matriz, parámetros de valor, parámetros de entrada, parámetros de referencia, parámetros de salida y variables locales. Las subcláusulas siguientes describen cada una de estas categorías.

Example: En el código de ejemplo siguiente

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 es una variable estática, y es una variable de instancia, v[0] es un elemento de matriz, a es un parámetro de valor, b es un parámetro de referencia, c es un parámetro de salida, d es un parámetro de entrada y i es una variable local. ejemplo final

9.2.2 Variables estáticas

Un campo declarado con el modificador static es una variable estática. Una variable estática empieza a existir antes de la ejecución del constructor static (§15.12) para su tipo contenedor y deja de existir cuando el dominio de aplicación asociado deja de existir.

El valor inicial de una variable estática es el valor predeterminado (§9.3) del tipo de la variable.

Para los fines de la comprobación de asignaciones definitivas, una variable estática se considera asignada inicialmente.

9.2.3 Variables de instancia

9.2.3.1 General

Un campo declarado sin el modificador static es una variable de instancia.

9.2.3.2 Variables de instancia en clases

Una variable de instancia de una clase empieza a existir cuando se crea una nueva instancia de esa clase y deja de existir cuando no hay referencias a esa instancia y el finalizador de la instancia (si existe) se ha ejecutado.

El valor inicial de una variable de instancia de una clase es el valor predeterminado (§9.3) del tipo de la variable.

Para la comprobación de asignaciones definitivas, una variable de instancia de una clase se considera asignada inicialmente.

9.2.3.3 Variables de instancia en estructuras

Una variable de instancia de una estructura tiene exactamente la misma duración que la variable de estructura a la que pertenece. En otras palabras, cuando una variable de un tipo de estructura empieza a existir o deja de existir, lo hacen también las variables de instancia de la estructura.

El estado de asignación inicial de una variable de instancia de una estructura es el mismo que el de la variable contenedora struct. En otras palabras, cuando una variable de estructura se considera asignada inicialmente, ocurre lo mismo con sus variables de instancia y, cuando una variable de estructura se considera inicialmente sin asignar, sus variables de instancia están igualmente sin asignar.

9.2.4 Elementos de matriz

Los elementos de una matriz empiezan a existir cuando se crea una instancia de matriz y dejan de existir cuando no hay referencias a esa instancia de matriz.

El valor inicial de cada uno de los elementos de una matriz es el valor predeterminado (§9.3) del tipo de los elementos de matriz.

Para la comprobación de la asignación definitiva, un elemento de matriz se considera asignado inicialmente.

9.2.5 Parámetros de valor

Un parámetro de valor empieza a existir con la invocación del miembro de función (método, constructor de instancia, descriptor de acceso u operador) o de la función anónima a la que pertenece el parámetro, y se inicializa con el valor del argumento proporcionado en la invocación. Normalmente, un parámetro de valor deja de existir cuando se completa la ejecución del cuerpo de la función. Sin embargo, si una función anónima captura el parámetro value (§12.21.6.2), su duración se extiende al menos hasta que el árbol delegado o de expresión creado a partir de esa función anónima es apto para la recolección de elementos no utilizados.

Para la comprobación de la asignación definitiva, un parámetro de valor se considera asignado inicialmente.

Los parámetros de valor se describen más adelante en §15.6.2.2.

9.2.6 Parámetros de referencia

Un parámetro de referencia es una variable de referencia (§9.7) que empieza a existir con la invocación del miembro de función, el delegado, la función anónima o la función local, y su referencia se inicializa en la variable dada como el argumento de esa invocación. Un parámetro de referencia deja de existir cuando se completa la ejecución del cuerpo de la función. A diferencia de los parámetros de valor, un parámetro de referencia no se puede capturar (§9.7.2.9).

Las siguientes reglas de asignación definitiva se aplican a los parámetros de referencia.

Nota: Las reglas para los parámetros de salida son diferentes y se describen en (§9.2.7). nota final

  • Una variable se debe asignar definitivamente (§9.4) para que se pueda pasar como parámetro de referencia en una invocación de miembro de función o delegado.
  • Dentro de un miembro de función o una función anónima, un parámetro de referencia se considera asignado inicialmente.

Los parámetros de referencia se describen más adelante en §15.6.2.3.3.

9.2.7 Parámetros de salida

Un parámetro de salida es una variable de referencia (§9.7) que empieza a existir con la invocación del miembro de función, el delegado, la función anónima o la función local, y su referencia se inicializa en la variable dada como el argumento de esa invocación. Un parámetro de salida deja de existir cuando se completa la ejecución del cuerpo de la función. A diferencia de los parámetros de valor, un parámetro de salida no se puede capturar (§9.7.2.9).

Las siguientes reglas de asignación definitiva se aplican a los parámetros de salida.

Nota: Las reglas para los parámetros de referencia son diferentes y se describen en (§9.2.6). nota final

  • No es necesario asignar definitivamente una variable antes de que se pueda pasar como parámetro de salida en una invocación de miembro de función o delegado.
  • Después de la finalización normal de una invocación de miembro de función o delegado, cada variable que se pasó como parámetro de salida se considera asignada en esa ruta de acceso de ejecución.
  • Dentro de un miembro de función o una función anónima, un parámetro de salida se considera sin asignar inicialmente.
  • Todos los parámetros de salida de un miembro de función, una función anónima o una función local se deben asignar definitivamente (§9.4) para que el miembro de función, la función anónima o la función local se devuelvan normalmente.

Los parámetros de salida se describen más adelante en §15.6.2.3.4.

9.2.8 Parámetros de entrada

Un parámetro de entrada es una variable de referencia (§9.7) que empieza a existir con la invocación del miembro de función, el delegado, la función anónima o la función local, y su referencia se inicializa en la variable_reference dada como argumento de esa invocación. Un parámetro de entrada deja de existir cuando se completa la ejecución del cuerpo de la función. A diferencia de los parámetros de valor, un parámetro de entrada no se puede capturar (§9.7.2.9).

Las siguientes reglas de asignación definitiva se aplican a los parámetros de entrada.

  • Una variable se debe asignar definitivamente (§9.4) para que se pueda pasar como parámetro de entrada en una invocación de miembro de función o delegado.
  • Dentro de un miembro de función, una función anónima o una función local, un parámetro de entrada se considera asignado inicialmente.

Los parámetros de entrada se describen más adelante en §15.6.2.3.2.

9.2.9 Variables locales

9.2.9.1 General

Una variable local se declara mediante una local_variable_declaration, declaration_expression, foreach_statement o specific_catch_clause de una try_statement. Una variable local también se puede declarar mediante determinados tipos de patrones(§11). Para una foreach_statement, la variable local es una variable de iteración (§13.9.5). Para una specific_catch_clause, la variable local es una variable de excepción (§13.11). Una variable local declarada por una foreach_statement o specific_catch_clause se considera asignada inicialmente.

Una local_variable_declaration puede producirse en un bloque, una for_statement, un switch_block o una using_statement. Un declaration_expression puede producirse como un outargument_value y como un tuple_element que es el destino de una asignación deconstrucción (§12.23.2).

La duración de una variable local es la parte de la ejecución del programa durante la cual se garantiza que el almacenamiento se reservará para ella. Esta duración se extiende desde la entrada al ámbito con el que está asociado, al menos hasta que la ejecución de ese ámbito termina de alguna manera. (Al escribir un bloque incluido, llamar a un método o producir un valor de un bloque de iterador, se suspende, pero no finaliza, la ejecución del ámbito actual). Si una función anónima captura la variable local (§12.21.6.2), su duración se extiende al menos hasta que el árbol delegado o de expresión creado a partir de la función anónima, junto con cualquier otro objeto que llegue a hacer referencia a la variable capturada, es apto para la recolección de elementos no utilizados. Si el ámbito primario se escribe de forma recursiva o iterativa, se crea una nueva instancia de la variable local cada vez y su inicializador, si existe, se evalúa cada vez.

Nota: Se crea una instancia de una variable local cada vez que se escribe su ámbito. Este comportamiento es visible para el código de usuario que contiene métodos anónimos. nota final

Nota: La duración de una variable de iteración (§13.9.5) declarada por una foreach_statement es una única iteración de esa instrucción. Cada iteración crea una nueva variable. nota final

Nota: La duración real de una variable local depende de la implementación. Por ejemplo, un compilador podría determinar estáticamente que una variable local de un bloque solo se usa para una pequeña parte de ese bloque. Con este análisis, un compilador podría generar código que da como resultado que el almacenamiento de la variable tenga una duración más corta que su bloque contenedor.

El almacenamiento al que hace referencia una variable de referencia local se reclama independientemente de la duración de esa variable de referencia local (§7.9).

nota final

Una variable local introducida por una local_variable_declaration o declaration_expression no se inicializa automáticamente y, por tanto, no tiene ningún valor predeterminado. Esta variable local se considera inicialmente sin asignar.

Nota: Una local_variable_declaration que incluye un inicializador sigue sin asignarse inicialmente. La ejecución de la declaración se comporta exactamente como una asignación a la variable (§9.4.4.5). Usar una variable antes de ejecutar su inicializador, por ejemplo, dentro de la propia expresión del inicializador o mediante una goto_statement que omite el inicializador, es un error en tiempo de compilación:

goto L;

int x = 1; // never executed

L: x += 1; // error: x not definitely assigned

Dentro del ámbito de una variable local, es un error en tiempo de compilación referirse a esa variable local en una posición textual que aparece antes de su declaración.

nota final

9.2.9.2 Descartes

Un descarte es una variable local que no tiene nombre. Un descarte se introduce mediante una expresión de declaración (§12.19) con el identificador _; y se escribe implícitamente (_ o var _) o se escribe explícitamente (T _).

Nota: _ es un identificador válido en muchas formas de declaraciones. nota final

Dado que un descarte no tiene nombre, la única referencia a la variable que representa es la expresión que lo presenta.

Nota: Sin embargo, se puede pasar un descarte como argumento de salida, lo que permite que el parámetro de salida correspondiente indique su ubicación de almacenamiento asociada. nota final

No se asigna inicialmente un descarte, por lo que siempre es un error tener acceso a su valor.

Ejemplo:

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

En el ejemplo se supone que no hay ninguna declaración del nombre _ en el ámbito.

La asignación a _ muestra un patrón simple para omitir el resultado de una expresión. La llamada de M muestra las distintas formas de descartes disponibles en tuplas y como parámetros de salida.

ejemplo final

9.3 Valores predeterminados

Las siguientes categorías de variables se inicializan automáticamente en sus valores predeterminados:

  • Variables estáticas.
  • Variables de instancia de instancias de clase.
  • Elementos de matriz.

El valor predeterminado de una variable depende del tipo de la variable y se determina de la siguiente manera:

  • Para una variable de un value_type, el valor predeterminado es el mismo que el valor calculado por el constructor predeterminado del value_type (§8.3.3).
  • Para una variable de un reference_type, el valor predeterminado es null.

Nota: La inicialización de valores predeterminados normalmente se realiza al hacer que el administrador de memoria o el recolector de elementos no utilizados inicialicen la memoria en todos los bits-cero antes de que se asigne para su uso. Por este motivo, es conveniente usar all-bits-zero para representar la referencia nula. nota final

9.4 Asignación definitiva

9.4.1 General

En una ubicación determinada en el código ejecutable de un miembro de función o una función anónima, se dice que una variable se asigna definitivamente si un compilador puede demostrar, mediante un análisis de flujo estático determinado (§9.4.4), que la variable se ha inicializado automáticamente o ha sido el destino de al menos una asignación.

Nota: Informalmente, las reglas de asignación definitiva son:

  • Una variable asignada inicialmente (§9.4.2) siempre se considera asignada definitivamente.
  • Una variable sin asignar inicialmente (§9.4.3) se considera asignada definitivamente en una ubicación determinada si todas las rutas de acceso de ejecución posibles que conducen a esa ubicación contienen al menos una de las siguientes opciones:
    • Asignación simple (§12.23.2) en la que la variable es el operando izquierdo.
    • Una expresión de invocación (§12.8.10) o expresión de creación de objetos (§12.8.17.2) que pasa la variable como parámetro de salida.
    • Para una variable local, una declaración de variable local para la variable (§13.6.2) que incluye un inicializador de variable.

La especificación formal subyacente a las reglas informales anteriores se describe en §9.4.2, §9.4.3 y §9.4.4.

nota final

Los estados de asignación definitiva de variables de instancia de una variable struct_type se someten a un seguimiento individual, así como colectivo. Además de las reglas descritas en §9.4.2, §9.4.3 y §9.4.4, las siguientes reglas se aplican a las variables struct_type y sus variables de instancia:

  • Una variable de instancia se considera asignada definitivamente si su variable contenedora struct_type se considera asignada definitivamente.
  • Una variable struct_type se considera asignada definitivamente si cada una de sus variables de instancia se considera asignada definitivamente.

La asignación definitiva es un requisito en los contextos siguientes:

  • Una variable se asignará definitivamente en cada ubicación donde se obtiene su valor.

    Nota: Esto garantiza que nunca se produzcan valores indefinidos. nota final

    La aparición de una variable en una expresión se considera para obtener el valor de la variable, excepto cuando

    • la variable es el operando izquierdo de una asignación simple,
    • la variable se pasa como un parámetro de salida o
    • la variable es una variable struct_type y se produce como el operando izquierdo de un acceso de miembro.
  • Una variable se asignará definitivamente en cada ubicación donde se pasa como parámetro de referencia.

    Nota: Esto garantiza que el miembro de función que se invoca pueda considerar el parámetro de referencia asignado inicialmente. nota final

  • Una variable se asignará definitivamente en cada ubicación donde se pasa como parámetro de entrada.

    Nota: Esto garantiza que el miembro de función que se invoca pueda considerar el parámetro de entrada asignado inicialmente. nota final

  • Todos los parámetros de salida de un miembro de función se asignarán definitivamente en cada ubicación donde el miembro de función devuelva (a través de una instrucción return o a través de la ejecución que llegue al final del cuerpo del miembro de función).

    Nota: Esto garantiza que los miembros de la función no devuelvan valores indefinidos en parámetros de salida, lo que permite a un compilador considerar una invocación de miembro de función que toma una variable como un parámetro de salida equivalente a una asignación a la variable. nota final

  • La variable this de un constructor de instancia struct_type se asignará definitivamente en cada ubicación donde vuelva ese constructor de instancia.

9.4.2 Variables asignadas inicialmente

Las siguientes categorías de variables se clasifican como asignadas inicialmente:

  • Variables estáticas.
  • Variables de instancia de instancias de clase.
  • Variables de instancia de variables de estructura asignadas inicialmente.
  • Elementos de matriz.
  • Parámetros de valor.
  • Parámetros de referencia.
  • Parámetros de entrada.
  • Variables declaradas en una cláusula catch o una instrucción foreach.

9.4.3 Variables no asignadas inicialmente

Las siguientes categorías de variables se clasifican como no asignadas inicialmente:

  • Variables de instancia de variables de estructura no asignadas inicialmente.
  • Parámetros de salida, incluida la variable this de constructores de instancia de estructura sin inicializador de constructor.
  • Variables locales, excepto las declaradas en una cláusula catch o una instrucción foreach.

9.4.4 Reglas precisas para determinar la asignación definitiva

9.4.4.1 General

Para determinar que definitivamente se asigna cada variable usada, un compilador usará un proceso equivalente al descrito en esta subcláusula.

El cuerpo de un miembro de función puede declarar una o varias variables inicialmente sin asignar. Para cada variable sin asignar inicialmente v, un compilador determinará un estado de asignación definitiva para v en cada uno de los puntos siguientes del miembro de función:

  • Al principio de cada instrucción
  • Al final (§13.2) de cada instrucción
  • En cada arco que transfiere el control a otra instrucción o al punto final de una instrucción
  • Al principio de cada expresión
  • Al final de cada expresión

El estado de asignación definitiva de v puede ser:

  • Definitivamente asignado. Esto indica que, en todos los flujos de control posibles en este punto, se le ha asignado un valor a v.
  • No asignado definitivamente. Para el estado de una variable al final de una expresión de tipo bool, el estado de una variable que no está asignada definitivamente podría (pero no necesariamente) caer en uno de los siguientes subes estados:
    • Definitivamente asignado después de la expresión verdadera. Este estado indica que v se asigna definitivamente si la expresión booleana se evalúa como true, pero no se asigna necesariamente si la expresión booleana se evalúa como false.
    • Definitivamente asignado después de una expresión falsa. Este estado indica que v se asigna definitivamente si la expresión booleana se evalúa como false, pero no se asigna necesariamente si la expresión booleana se evalúa como true.

Las reglas siguientes rigen cómo se determina el estado de una variable v en cada ubicación.

9.4.4.2 Reglas generales para las instrucciones

  • v no se asigna definitivamente al principio de un cuerpo miembro de función.
  • El estado de asignación definitiva de v al principio de cualquier otra instrucción se determina comprobando el estado de asignación definitiva de v en todas las transferencias de flujo de control que tienen como destino el principio de esa instrucción. Si (y solo si) v se asigna definitivamente en todas estas transferencias de flujo de control, v se asigna definitivamente al principio de la instrucción. El conjunto de posibles transferencias de flujo de control se determina de la misma manera que para comprobar la accesibilidad de las instrucciones (§13.2).
  • El estado de asignación definitiva de v en el punto final de una instrucción block, checked, unchecked, if, while, do, for, foreach, lock, using o switch se determina comprobando el estado de asignación definitiva de v en todas las transferencias de flujo de control que tienen como destino el punto final de esa instrucción. Si v se asigna definitivamente en todas estas transferencias de flujo de control, v se asigna definitivamente al final de la instrucción. De lo contrario, v no está asignado definitivamente al final de la instrucción. El conjunto de posibles transferencias de flujo de control se determina de la misma manera que para comprobar la accesibilidad de las instrucciones (§13.2).

Nota: Dado que no hay rutas de acceso de control a una instrucción inaccesible, v se asigna definitivamente al principio de cualquier instrucción inaccesible. nota final

9.4.4.3 Instrucciones block, checked y unchecked

El estado de asignación definitiva de v en la transferencia de control a la primera instrucción de la lista de instrucciones del bloque (o al final del bloque, si la lista de instrucciones está vacía) es la misma que la instrucción de asignación definitiva de v antes del bloque, instrucción checked o unchecked.

9.4.4.4 Instrucciones de expresión

Para una instrucción de expresión stmt que consta de la expresión expr:

  • v tiene el mismo estado de asignación definitiva al principio de expr que al principio de stmt.
  • Si v está asignado definitivamente al final de expr, se asigna definitivamente al final de stmt; de lo contrario, no se asigna definitivamente al final de stmt.

9.4.4.5 Instrucciones de declaración

  • Si stmt es una instrucción de declaración sin inicializadores, v tiene el mismo estado de asignación definitiva al final de stmt que al principio de stmt.
  • Si stmt es una instrucción de declaración con inicializadores, el estado de asignación definitiva para v se determina como si stmt fuera una lista de instrucciones, con una instrucción de asignación para cada declaración con un inicializador (en el orden de declaración).

9.4.4.6 Instrucciones If

Para una instrucción stmt del formulario:

if ( «expr» ) «then_stmt» else «else_stmt»
  • v tiene el mismo estado de asignación definitiva al principio de expr que al principio de stmt.
  • Si v se asigna definitivamente al final de expr, definitivamente se asigna en la transferencia de flujo de control a then_stmt y a else_stmt o al final de stmt si no hay ninguna otra cláusula.
  • Si v tiene el estado "definitivamente asignado después de la expresión verdadera" al final de expr, definitivamente se asigna en la transferencia de flujo de control a then_stmt, y no se asigna definitivamente en la transferencia de flujo de control a else_stmt o al punto final de stmt si no hay ninguna otra cláusula.
  • Si v tiene el estado "definitivamente asignado después de una expresión falsa" al final de expr, definitivamente se asigna en la transferencia de flujo de control a else_stmt, y no definitivamente asignado en la transferencia de flujo de control a then_stmt. Definitivamente se asigna en el punto final de stmt si y solo si definitivamente se asigna en el punto final de then_stmt.
  • De lo contrario, v se considera que no se asigna definitivamente a la transferencia de flujo de control a la then_stmt o else_stmt, o al punto final de stmt si no hay ninguna otra cláusula.

9.4.4.7 Instrucciones switch

Para una instrucción switchstmt con una expresión de control expr:

El estado de asignación definitiva de v al principio de expr es el mismo que el estado de v al principio de stmt.

El estado de asignación definitiva de v al principio de la cláusula de restricción de un caso es

  • Si v es una variable de patrón declarada en el switch_label: "definitivamente asignada".
  • Si la etiqueta del modificador que contiene esa cláusula de restricción (§13.8.3) no es accesible: "definitivamente asignada".
  • De lo contrario, el estado de v es el mismo que el estado de v después de expr.

Ejemplo: la segunda regla elimina la necesidad de que un compilador emita un error si se tiene acceso a una variable sin asignar en código inaccesible. El estado de b se "asigna definitivamente" en la etiqueta de modificador inaccesible case 2 when b.

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

ejemplo final

El estado de asignación definitiva de v en la transferencia de flujo de control a una lista de instrucciones de bloque de conmutador accesible es

  • Si la transferencia de control se debe a una instrucción 'goto case' o 'goto default', el estado de v es el mismo que el estado al principio de esa instrucción 'goto'.
  • Si la transferencia de control se debe a la etiqueta default del modificador, el estado de v es el mismo que el estado de v después de expr.
  • Si la transferencia de control se debe a una etiqueta de conmutador inaccesible, el estado de v es "definitivamente asignado".
  • Si la transferencia de control se debe a una etiqueta de conmutador accesible con una cláusula guard, el estado de v es el mismo que el estado de v después de la cláusula de restricción.
  • Si la transferencia de control se debe a una etiqueta de conmutador accesible sin una cláusula guard, el estado de v es
    • Si v es una variable de patrón declarada en el switch_label: "definitivamente asignada".
    • De lo contrario, el estado de v es el mismo que el estado de v después de expr.

Una consecuencia de estas reglas es que una variable de patrón declarada en un switch_label se "no asignará definitivamente" en las instrucciones de su sección switch si no es la única etiqueta de conmutador accesible en su sección.

Ejemplo:

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

ejemplo final

9.4.4.8 Instrucciones While

Para una instrucción stmt del formulario:

while ( «expr» ) «while_body»
  • v tiene el mismo estado de asignación definitiva al principio de expr que al principio de stmt.
  • Si v se asigna definitivamente al final de expr, definitivamente se asigna en la transferencia de flujo de control a while_body y al punto final de stmt.
  • Si v tiene el estado "definitivamente asignado después de la expresión verdadera" al final de expr, definitivamente se asigna en la transferencia de flujo de control a while_body, pero no definitivamente asignado en el punto final de stmt.
  • Si v tiene el estado "definitivamente asignado después de una expresión falsa" al final de expr, definitivamente se asigna en la transferencia de flujo de control al punto final de stmt, pero no definitivamente asignado en la transferencia de flujo de control a while_body.

9.4.4.9 Instrucciones Do

Para una instrucción stmt del formulario:

do «do_body» while ( «expr» ) ;
  • v tiene el mismo estado de asignación definitiva en la transferencia de flujo de control desde el principio de stmt hasta do_body que al principio de stmt.
  • v tiene el mismo estado de asignación definitiva al principio de expr que en el punto final de do_body.
  • Si v se asigna definitivamente al final de expr, definitivamente se asigna en la transferencia de flujo de control al punto final de stmt.
  • Si v tiene el estado "definitivamente asignado después de una expresión falsa" al final de expr, definitivamente se asigna en la transferencia de flujo de control al punto final de stmt, pero no definitivamente asignado en la transferencia de flujo de control a do_body.

9.4.4.10 Instrucciones For

Para una instrucción de forma:

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

La comprobación de la asignación definitiva se realiza como si la instrucción estuviera escrita:

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

con continue instrucciones que tienen como destino la instrucción for que se traduce en instrucciones goto destinadas a la etiqueta LLoop. Si for_condition se omite en la instrucción for, la evaluación de la asignación definitiva continúa como si for_condition se reemplazara por true en la expansión anterior.

9.4.4.11 Instrucciones break, continue y goto

El estado de asignación definitiva de v en la transferencia de flujo de control causada por una instrucción break, continue o goto es el mismo que el estado de asignación definitiva de v al principio de la instrucción.

9.4.4.12 Instrucciones throw

Para una instrucción stmt del formulario:

throw «expr» ;

el estado de asignación definitiva de v al principio de expr es el mismo que el estado de asignación definitiva de v al principio de stmt.

9.4.4.13 Instrucciones return

Para una instrucción stmt del formulario:

return «expr» ;
  • El estado de asignación definitiva de v al principio de expr es el mismo que el estado de asignación definitiva de v al principio de stmt.
  • Si v es un parámetro de salida, se le asignará definitivamente:
    • después de expr
    • o al final del bloque finally de try-finally otry-catch-finally que incluye la instrucción return.

Para una instrucción stmt del formulario:

return ;
  • Si v es un parámetro de salida, se le asignará definitivamente:
    • antes de stmt
    • o al final del bloque finally de try-finally otry-catch-finally que incluye la instrucción return.

9.4.4.14 Instrucciones try-catch

Para una instrucción stmt del formulario:

try «try_block»
catch ( ... ) «catch_block_1»
...
catch ( ... ) «catch_block_n»
  • El estado de asignación definitiva de v al principio de try_block es el mismo que el estado de asignación definitiva de v al principio de stmt.
  • El estado de asignación definitiva de v al principio de catch_block_i (para cualquier i) es el mismo que el estado de asignación definitiva de v al principio de stmt.
  • El estado de asignación definitiva de v en el punto final de stmt está asignado definitivamente si (y solo si) v se asigna definitivamente en el punto final de try_block y cada catch_block_i (para cada i de 1 a n).

9.4.4.15 Instrucciones try-finally

Para una instrucción stmt del formulario:

try «try_block» finally «finally_block»
  • El estado de asignación definitiva de v al principio de try_block es el mismo que el estado de asignación definitiva de v al principio de stmt.
  • El estado de asignación definitiva de v al principio de finally_block es el mismo que el estado de asignación definitiva de v al principio de stmt.
  • El estado de asignación definitiva de v en el punto final de stmt se asigna definitivamente si (y solo si) al menos uno de los siguientes es true:
    • v está asignada definitivamente en el punto final de try_block
    • v está asignada definitivamente en el punto final de finally_block

Si se realiza una transferencia de flujo de control (por ejemplo, una instrucción goto) que comienza dentro de try_block y termina fuera de try_block, v también se considera asignada definitivamente en esa transferencia de flujo de control si v se asigna definitivamente al punto final de finally_block. (Esto no es un only if, si v está asignado definitivamente por otra razón en esta transferencia de flujo de control, todavía se considera definitivamente asignado).

9.4.4.16 Instrucciones try-catch-finally

Para una instrucción de forma:

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

el análisis de asignación definitiva se realiza como si la instrucción fuera una instrucción try-finally que incluyera una instrucción try-catch:

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

Ejemplo: en el ejemplo siguiente se muestra cómo los distintos bloques de una instrucción try (§13.11) afectan a la asignación definitiva.

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

ejemplo final

9.4.4.17 Instrucciones foreach

Para una instrucción stmt del formulario:

foreach ( «type» «identifier» in «expr» ) «embedded_statement»
  • El estado de asignación definitiva de v al principio de expr es el mismo que el estado de v al principio de stmt.
  • El estado de asignación definitiva de v en la transferencia de flujo de control a embedded_statement o al punto final de stmt es el mismo que el estado de v al final de expr.

9.4.4.18 Instrucciones using

Para una instrucción stmt del formulario:

using ( «resource_acquisition» ) «embedded_statement»
  • El estado de asignación definitiva de v al principio de resource_acquisition es el mismo que el estado de v al principio de stmt.
  • El estado de asignación definitiva de v en la transferencia de flujo de control a embedded_statement es el mismo que el estado de v al final de resource_acquisition.

9.4.4.19 Instrucciones lock

Para una instrucción stmt del formulario:

lock ( «expr» ) «embedded_statement»
  • El estado de asignación definitiva de v al principio de expr es el mismo que el estado de v al principio de stmt.
  • El estado de asignación definitiva de v en la transferencia de flujo de control a embedded_statement es el mismo que el estado de v al final de expr.

9.4.4.20 Instrucciones yield

Para una instrucción stmt del formulario:

yield return «expr» ;
  • El estado de asignación definitiva de v al principio de expr es el mismo que el estado de v al principio de stmt.
  • El estado de asignación definitiva de v al final de stmt es el mismo que el estado de v al final de expr.

Una instrucción yield break no tiene ningún efecto en el estado de asignación definitiva.

9.4.4.21 Reglas generales para expresiones constantes

Lo siguiente se aplica a cualquier expresión constante y tiene prioridad sobre las reglas de las subclases siguientes que podrían aplicarse:

Para una expresión constante con el valor true:

  • Si v se asigna definitivamente antes de la expresión, v se asigna definitivamente después de la expresión.
  • De lo contrario, v se "asigna definitivamente después de una expresión falsa" después de la expresión.

Ejemplo:

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

ejemplo final

Para una expresión constante con el valor false:

  • Si v se asigna definitivamente antes de la expresión, v se asigna definitivamente después de la expresión.
  • De lo contrario, v se "asigna definitivamente después de una expresión verdadera" después de la expresión.

Ejemplo:

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

ejemplo final

Para todas las demás expresiones constantes, el estado de asignación definitiva de v después de la expresión es el mismo que el estado de asignación definitiva de v antes de la expresión.

9.4.4.22 Reglas generales para expresiones simples

La siguiente regla se aplica a estos tipos de expresiones: literales (§12.8.2), nombres simples (§12.8.4), expresiones de acceso a miembros (§12.8.7), expresiones de acceso base no indexadas (§12.4). 8.15), expresiones (§12.8.18), expresiones de valor predeterminadas (§12.8.21), nameoftypeof expresiones (§12.8.23) y expresiones de declaración (§12.19).

  • El estado de asignación definitiva de v al final de dicha expresión es el mismo que el estado de asignación definitiva de v al principio de la expresión.

9.4.4.23 Reglas generales para expresiones con expresiones insertadas

Las reglas siguientes se aplican a estos tipos de expresiones: expresiones entre paréntesis (§12.8.5), expresiones de tupla (§12.8.6), expresiones de acceso a elementos (§12.8.12), expresiones de acceso base con indexación (§12.8.15), expresiones de incremento y decremento (§12.8.16, §12.9.7), expresiones de conversión (§12.9.8), unary +, -, ~* expresiones, binary +, -, *, /, , %, <<>><, <=, , >>=, ==, !=, is, as, &, , |expresiones ^ (§12.12, §12.13, §12.14, §12.15), expresiones de asignación compuestas (§12.23.4) checked y expresiones (§12.8.20), expresiones de creación de matrices y delegados (§12.8.17) y awaitunchecked expresiones (§12.9.9).

Cada una de estas expresiones tiene una o varias subexpresiones que se evalúan incondicionalmente en un orden fijo.

Ejemplo: el operador binario % evalúa el lado izquierdo del operador y, a continuación, el lado derecho. Una operación de indexación evalúa la expresión indizada y, a continuación, evalúa cada una de las expresiones de índice, en orden de izquierda a derecha. ejemplo final

En el caso de una expresión expr, que tiene subexpressions expr₁, expr₂, ..., exprₓ, evaluada en ese orden:

  • El estado de asignación definitiva de v al principio de expr₁ es el mismo que el estado de asignación definitiva al principio de expr.
  • El estado de asignación definitiva de v al principio de exprᵢ (i mayor que uno) es el mismo que el estado de asignación definitiva al final de exprᵢ₋₁.
  • El estado de asignación definitiva de v al final de expr₁ es el mismo que el estado de asignación definitiva al final de exprₓ.

9.4.4.24 Expresiones de invocación y expresiones de creación de objetos

Si el método que se va a invocar es un método parcial que no tiene ninguna declaración de método parcial de implementación o es un método condicional para el que se omite la llamada (§23.5.3.2), el estado de asignación definitiva de v después de la invocación es el mismo que el estado de asignación definitiva de v antes de la invocación. En caso contrario, se aplican las reglas siguientes:

Para una expresión de invocación expr de la forma:

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

o una expresión de creación de objetos expr con la forma:

new «type» ( «arg₁», «arg₂», … , «argₓ» )
  • Para una expresión de invocación, el estado de asignación definitiva de v antes de primary_expression es el mismo que el estado de v antes de expr.
  • Para una expresión de invocación, el estado de asignación definitiva de v antes de arg₁ es el mismo que el estado de v después de primary_expression.
  • Para una expresión de creación de objetos, el estado de asignación definitiva de v antes de arg₁ es el mismo que el estado de v antes de expr.
  • Para cada argumento argᵢ, el estado de asignación definitiva de v después de argᵢ viene determinado por las reglas de expresión normal, omitiendo los modificadores in, out o ref.
  • Para cada argumento argᵢ para cualquier i mayor que uno, el estado de asignación definitiva de v antes de argᵢ es el mismo que el estado de v después de argᵢ₋₁.
  • Si la variable v se pasa como un argumento out (es decir, un argumento de la forma "out v") en cualquiera de los argumentos, el estado de v después de expr se asigna definitivamente. De lo contrario, el estado de v después de expr es el mismo que el estado de v después de argₓ.
  • Para los inicializadores de matriz (§12.8.17.4), iniciadores de objeto (§12.8.17.2.2), inicializadores de colección (§12.8.17.2.3) y inicializadores de objetos anónimos (§12.8.17.3), el estado de asignación definitiva se determina por la expansión en la que estas construcciones se definen.

9.4.4.25 Expresiones de asignación simple

Supongamos que el conjunto de destinos de asignación en una expresión e se define de la siguiente manera:

  • Si e es una expresión de tupla, los destinos de asignación de e son la unión de los destinos de asignación de los elementos de e.
  • De lo contrario, los destinos de asignación de e son e.

Para una expresión expr de la forma:

«expr_lhs» = «expr_rhs»
  • El estado de asignación definitiva de v antes de expr_lhs es el mismo que el estado de asignación definitiva de v antes de expr.
  • El estado de asignación definitiva de v antes de expr_rhs es el mismo que el estado de asignación definitiva de v después de expr_lhs.
  • Si v es un destino de asignación de expr_lhs, el estado de asignación definitiva de v después de expr se asigna definitivamente. De lo contrario, si la asignación se produce dentro del constructor de instancia de un tipo de estructura, y v es el campo de respaldo oculto de una propiedad P implementada automáticamente en la instancia que se está construyendo, y un acceso a propiedades designando P es un destino de asignación de expr_lhs, entonces el estado de asignación definitiva de v después de que expr se asigne definitivamente. En caso contrario, el estado de asignación definitiva de v después de expr es el mismo que el estado de asignación definitiva de v después de expr_rhs.

Example: En el código de ejemplo siguiente

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

la variable x se considera definitivamente asignada después de que se evalúe arr[x = 1] como el lado izquierdo de la segunda asignación simple.

ejemplo final

9.4.4.26 Expresiones &&

Para una expresión expr de la forma:

«expr_first» && «expr_second»
  • El estado de asignación definitiva de v antes de expr_first es el mismo que el estado de asignación definitiva de v antes de expr.
  • El estado de asignación definitiva de v antes de expr_second se asigna definitivamente si y solo si el estado de v después de expr_first está asignado definitivamente o "definitivamente asignado después de la expresión verdadera". De lo contrario, no está asignado definitivamente.
  • El estado de asignación definitiva de v después de expr viene determinado por:
    • Si el estado de v después de expr_first está asignado definitivamente, el estado de v después de expr se asigna definitivamente.
    • De lo contrario, si el estado de v después de expr_second está asignado definitivamente, y el estado de v después de expr_first es "definitivamente asignado después de una expresión falsa", entonces el estado de v después de expr se asigna definitivamente.
    • De lo contrario, si el estado de v después de expr_second se asigna definitivamente o "definitivamente asignado después de una expresión verdadera", el estado de v después de expr se "asigna definitivamente después de la expresión verdadera".
    • De lo contrario, si el estado de v después de expr_first está "asignado definitivamente después de una expresión false", y el estado de v después de expr_second es "definitivamente asignado después de una expresión falsa", entonces el estado de v después de expr se "asigna definitivamente después de una expresión false".
    • De lo contrario, el estado de v después de expr no se asigna definitivamente.

Example: En el código de ejemplo siguiente

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

la variable i se considera asignada definitivamente en una de las instrucciones incrustadas de una instrucción if, pero no en la otra. En la instrucción if del método F, la variable i se asigna definitivamente en la primera instrucción incrustada porque la ejecución de la expresión (i = y) siempre precede a la ejecución de esta instrucción incrustada. Por el contrario, la variable i no está asignada definitivamente en la segunda instrucción insertada, ya que x >= 0 podría haber sido false, lo que da lugar a que la variable ino esté asignada.

ejemplo final

9.4.4.27 Expresiones ||

Para una expresión expr de la forma:

«expr_first» || «expr_second»
  • El estado de asignación definitiva de v antes de expr_first es el mismo que el estado de asignación definitiva de v antes de expr.
  • El estado de asignación definitiva de v antes de expr_second se asigna definitivamente si y solo si el estado de v después de expr_first está asignado definitivamente o "definitivamente asignado después de la expresión falsa". De lo contrario, no está asignado definitivamente.
  • La instrucción de asignación definitiva de v después de expr viene determinada por:
    • Si el estado de v después de expr_first está asignado definitivamente, el estado de v después de expr se asigna definitivamente.
    • De lo contrario, si el estado de v después de expr_second está asignado definitivamente, y el estado de v después de expr_first es "definitivamente asignado después de una expresión verdadera", entonces el estado de v después de expr se asigna definitivamente.
    • De lo contrario, si el estado de v después de expr_second se asigna definitivamente o "definitivamente asignado después de una expresión falsa", el estado de v después de expr se "asigna definitivamente después de la expresión falsa".
    • De lo contrario, si el estado de v después de expr_first está "asignado definitivamente después de una expresión true", y el estado de v después de expr_ second es "definitivamente asignado después de una expresión true", entonces el estado de v después de expr se "asigna definitivamente después de una expresión true".
    • De lo contrario, el estado de v después de expr no se asigna definitivamente.

Example: En el código de ejemplo siguiente

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

la variable i se considera asignada definitivamente en una de las instrucciones incrustadas de una instrucción if, pero no en la otra. En la instrucción if del método G, la variable i se asigna definitivamente en la segunda instrucción incrustada porque la ejecución de la expresión (i = y) siempre precede a la ejecución de esta instrucción incrustada. Por el contrario, la variable i no está asignada definitivamente en la primera instrucción insertada, ya que x >= 0 podría haber sido true, lo que da lugar a que la variable ino esté asignada.

ejemplo final

9.4.4.28 ! expresiones

Para una expresión expr de la forma:

! «expr_operand»
  • El estado de asignación definitiva de v antes de expr_operand es el mismo que el estado de asignación definitiva de v antes de expr.
  • El estado de asignación definitiva de v después de expr viene determinado por:
    • Si el estado de v después de expr_operand está asignado definitivamente, el estado de v después de expr se asigna definitivamente.
    • De lo contrario, si el estado de v después de expr_operand es "definitivamente asignado después de una expresión falsa", el estado de v después de expr se "asigna definitivamente después de la expresión verdadera".
    • De lo contrario, si el estado de v después de expr_operand es "definitivamente asignado después de una expresión verdadera", el estado de v después de expr se "asigna definitivamente después de la expresión falsa".
    • De lo contrario, el estado de v después de expr no se asigna definitivamente.

9.4.4.29 ?? expresiones

Para una expresión expr de la forma:

«expr_first» ?? «expr_second»
  • El estado de asignación definitiva de v antes de expr_first es el mismo que el estado de asignación definitiva de v antes de expr.
  • El estado de asignación definitiva de v antes de expr_second es el mismo que el estado de asignación definitiva de v después de expr_first.
  • La instrucción de asignación definitiva de v después de expr viene determinada por:
    • Si expr_first es una expresión constante (§12.25) con el valor null, el estado de v después de expr es el mismo que el estado de v después de expr_second.
    • De lo contrario, el estado de v después de expr es el mismo que el estado de asignación definitiva de v después de expr_first.

9.4.4.30 Expresiones ?:

Para una expresión expr de la forma:

«expr_cond» ? «expr_true» : «expr_false»
  • El estado de asignación definitiva de v antes de expr_cond es el mismo que el estado de v antes de expr.
  • El estado de asignación definitiva de v antes de expr_true se asigna definitivamente si y solo si el estado de v después de expr_cond está asignado definitivamente o "definitivamente asignado después de la expresión verdadera".
  • El estado de asignación definitiva de v antes de expr_false se asigna definitivamente si y solo si el estado de v después de expr_cond está asignado definitivamente o "definitivamente asignado después de la expresión falsa".
  • El estado de asignación definitiva de v después de expr viene determinado por:
    • Si expr_cond es una expresión constante (§12.25) con el valor true , el estado de v después de expr es el mismo que el estado de v después de expr_true.
    • De lo contrario, si expr_cond es una expresión constante (§12.25) con el valor false , el estado de v después de expr es el mismo que el estado de v después de expr_false.
    • De lo contrario, si el estado de v después de expr_true está asignado definitivamente y el estado de v después de expr_false es definitivamente asignado, entonces el estado de v después de expr se asigna definitivamente.
    • De lo contrario, el estado de v después de expr no se asigna definitivamente.

9.4.4.31 Funciones anónimas

Para una lambda_expression o anonymous_method_expressionexpr con un cuerpo (block o expression):

  • El estado de asignación definitiva de un parámetro es el mismo que para un parámetro de un método con nombre (§9.2.6, §9.2.7, §9.2.8).
  • El estado de asignación definitiva de una variable externa v antes del cuerpo es el mismo que el estado de v antes de expr. Es decir, el estado de asignación definitiva de variables externas se hereda del contexto de la función anónima.
  • El estado de asignación definitiva de una variable externa v después de expr es el mismo que el estado de v antes de expr.

Ejemplo: el ejemplo

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) { ... }
}

genera un error en tiempo de compilación, ya que el valor máximo no está asignado definitivamente donde se declara la función anónima.

ejemplo final

Ejemplo: el ejemplo

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

también genera un error en tiempo de compilación, ya que la asignación a n en la función anónima no afecta al estado de asignación definitiva de n fuera de la función anónima.

ejemplo final

9.4.4.32 Expresiones throw

Para una expresión expr de la forma:

throw thrown_expr

  • El estado de asignación definitiva de v antes de thrown_expr es el mismo que el estado de v antes de expr.
  • El estado de asignación definitiva de v después de expr es "asignado definitivamente".

9.4.4.33 Reglas para variables en funciones locales

Las funciones locales se analizan en el contexto de su método primario. Hay dos rutas de flujo de control que importan para las funciones locales: llamadas de función y conversiones de delegados.

La asignación definitiva para el cuerpo de cada función local se define por separado para cada sitio de llamada. En cada invocación, las variables capturadas por la función local se consideran asignadas definitivamente si definitivamente se asignaron en el punto de llamada. También existe una ruta de acceso de flujo de control al cuerpo de la función local en este punto y se considera accesible. Después de una llamada a la función local, las variables capturadas que se asignaron definitivamente en cada punto de control que sale de la función (instrucciones return, instrucciones yield, expresiones await) se consideran definitivamente asignadas después de la ubicación de llamada.

Las conversiones de delegados tienen una ruta de acceso de flujo de control al cuerpo de la función local. Las variables capturadas se asignan definitivamente para el cuerpo si definitivamente se asignan antes de la conversión. Las variables asignadas por la función local no se consideran asignadas después de la conversión.

Nota: Lo anterior implica que los cuerpos se vuelven a analizar para la asignación definitiva en cada invocación de función local o conversión de delegado. Los compiladores no son necesarios para volver a analizar el cuerpo de una función local en cada invocación o conversión de delegado. La implementación debe generar resultados equivalentes a esa descripción. nota final

Ejemplo: en el ejemplo siguiente se muestra una asignación definitiva para variables capturadas en funciones locales. Si una función local lee una variable capturada antes de escribirla, la variable capturada debe asignarse definitivamente antes de llamar a la función local. La función local F1 lee s sin asignarla. Se trata de un error si F1 se llama a antes de que s se asigne definitivamente. F2 asigna i antes de leerlo. Se puede llamar antes de que i se asigne definitivamente. Además, F3 se puede llamar a después de F2 porque s2 se asigna definitivamente en F2.

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

ejemplo final

9.4.4.34 Expresiones is-pattern

Para una expresión expr de la forma:

expr_operand es pattern

  • El estado de asignación definitiva de v antes de expr_operand es el mismo que el estado de asignación definitiva de v antes de expr.
  • Si la variable 'v' se declara en pattern, el estado de asignación definitiva de 'v' después de expr es "asignado definitivamente cuando es true".
  • En caso contrario, el estado de asignación definitiva de "v" después de expr es el mismo que el estado de asignación definitiva de "v" después de expr_operand.

9.5 Referencias de variables

Un variable_reference es una expresión que se clasifica como una variable. Un variable_reference denota una ubicación de almacenamiento a la que se puede tener acceso tanto para capturar el valor actual como para almacenar un nuevo valor.

variable_reference
    : expression
    ;

Nota: En C y C++, una variable_reference se conoce como lvalue. nota final

9.6 Atomicidad de las referencias de variables

Las lecturas y escrituras de los siguientes tipos de datos serán atómicas: bool, char, byte, sbyte, short, ushort, uint, int, float, y tipos de referencia. Además, las lecturas y escrituras de tipos de enumeración con un tipo subyacente en la lista anterior también serán atómicas. Las lecturas y escrituras de otros tipos, incluidos long, ulong, double y decimal, así como los tipos definidos por el usuario, no deben ser atómicas. Además de las funciones de biblioteca diseñadas para ese propósito, no hay ninguna garantía de lectura-modificación-escritura atómica, como en el caso de incremento o decremento.

9.7 Variables y devoluciones de referencia

9.7.1 General

Una variable de referencia es una variable que hace referencia a otra variable, denominada referencia (§9.2.6). Una variable de referencia es una variable local declarada con el modificador ref.

Una variable de referencia almacena una variable_reference (§9.5) en su referencia y no en el valor de su referente. Cuando se usa una variable de referencia donde se requiere un valor, se devuelve su valor de referencia; de forma similar cuando una variable de referencia es el destino de una asignación, es el referente al que se asigna. La variable a la que hace referencia una variable de referencia, es decir, la variable_reference almacenada para su referencia, se puede cambiar mediante una asignación de referencia (= ref).

Ejemplo: en el ejemplo siguiente se muestra una variable de referencia local cuyo referente es un elemento de una matriz:

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

ejemplo final

Una devolución de referencia es la variable_reference devuelta por un método returns-by-ref (§15.6.1). Esta variable_reference es la referente de la devolución de referencia.

Ejemplo: en el ejemplo siguiente se muestra una devolución de referencia local cuyo referente es un campo de una matriz:

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

ejemplo final

9.7.2 Contextos seguros ref

9.7.2.1 General

Todas las variables de referencia cumplen las reglas de seguridad que garantizan que el contexto ref-safe-context de la variable de referencia no sea mayor que el contexto ref-safe-context de su referente.

Nota: La noción relacionada de un contexto seguro se define en (§16.4.15), junto con las restricciones asociadas. nota final

Para cualquier variable, el ref-safe-context de esa variable es el contexto donde una variable_reference (§9.5) a esa variable es válida. El referente de una variable de referencia tendrá un contexto ref-safe que sea al menos tan amplio como el contexto ref-safe-de la propia variable de referencia.

Nota: Un compilador determina el contexto ref-safe a través de un análisis estático del texto del programa. El ref-safe-context refleja la duración de una variable en tiempo de ejecución. nota final

Hay tres ref-safe-contexts:

  • declaration-block: el contexto ref-safe-context de una variable_reference a una variable local (§9.2.9.1) es el alcance de la variable local (§13.6.2), incluidas las embedded-statement anidadas en ese alcance.

    Una variable_reference a una variable local es un referente válido para una variable de referencia solo si la variable de referencia se declara dentro del contexto ref-safe-context de esa variable.

  • function-member: dentro de una función, una variable_reference a cualquiera de los siguientes tiene un ref-safe-context de function-member:

    • Parámetros de valor (§15.6.2.2) en una declaración de miembro de función, incluida la implícita this de las funciones miembro de clase; y
    • El parámetro de referencia implícita (ref) (§15.6.2.3.3) this de una función miembro de estructura, junto con sus campos.

    Una variable_reference con el ref-safe-context de function-member es un referente válido solo si la variable de referencia se declara en el mismo miembro de función.

  • caller-context: dentro de una función, una variable_reference a cualquiera de los siguientes tiene un ref-safe-context de caller-context:

    • Parámetros de referencia (§9.2.6) distintos del implícito this de una función miembro de estructura;
    • Campos de miembro y elementos de estos parámetros;
    • Campos miembros de parámetros de tipo de clase; y
    • Elementos de parámetros del tipo de matriz.

Una variable_reference con el ref-safe-context de caller-context puede ser el referente de una devolución de referencia.

Estos valores forman una relación de anidamiento de más estrecha (declaration-block) a más ancha (caller-context). Cada bloque anidado representa un contexto diferente.

Ejemplo: en el código siguiente se muestran ejemplos de los distintos ref-safe-context. Las declaraciones muestran el ref-safe-context para que un referente sea la expresión de inicialización de una variable ref. En los ejemplos se muestra el ref-safe-context para obtener una devolución de referencia:

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

ejemplo final.

Ejemplo: Para los tipos struct, el parámetro implícito this se pasa como parámetro de referencia. El ref-safe-context de los campos de un tipo struct como function-member impide devolver esos campos por referencia. Esta regla impide el código siguiente:

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

ejemplo final.

9.7.2.2 Ref-safe-context de variable local

Para una variable local v:

  • Si v es una variable de referencia, su ref-safe-context es el mismo que el ref-safe-context de su expresión de inicialización.
  • De lo contrario, su ref-safe-context es declaration-block.

9.7.2.3 ref-safe-context de parámetro

Para un parámetro p:

  • Si p es un parámetro de referencia o entrada, su ref-safe-context es el caller-context. Si p es un parámetro de entrada, no se puede devolver como grabable ref, pero se puede devolver como ref readonly.
  • Si p es un parámetro de salida, su ref-safe-context es el caller-context.
  • De lo contrario, si p es el parámetro this de un tipo struct, su ref-safe-context es function-member.
  • De lo contrario, el parámetro es un parámetro de valor y su ref-safe-context es function-member.

9.7.2.4 ref-safe-context de campo

Para una variable que designa una referencia a un campo, e.F:

  • Si e es de un tipo de referencia, su ref-safe-context es caller-context.
  • De lo contrario, si e es de un tipo de valor, su ref-safe-context es el mismo que el ref-safe-context de e.

9.7.2.5 Operadores

El operador condicional (§12.20), c ? ref e1 : ref e2y el operador de asignación de referencia (= ref e§12.23.1) tienen variables de referencia como operandos y producen una variable de referencia. Para esos operadores, el ref-safe-context del resultado es el contexto más estrecho entre los ref-safe-contexts de todos los operandos ref.

9.7.2.6 Invocación de función

Para una variable c resultante de una invocación de función de devolución de referencia, su contexto de referencia segura es el más estrecho de los contextos siguientes:

  • El caller-context.
  • El ref-safe-context de todas las expresiones de argumento ref, out y in (excepto el receptor).
  • Para cada parámetro de entrada, si hay una expresión correspondiente que es una variable y existe una conversión de identidad entre el tipo de la variable y el tipo del parámetro, el ref-safe-context de la variable; de lo contrario, el contexto envolvente más cercano.
  • Contexto seguro (§16.4.15) de todas las expresiones de argumento (incluido el receptor).

Ejemplo: la última viñeta es necesaria para controlar código como

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

ejemplo final

Una invocación de propiedad y una invocación de indizador (ya sea get o set) se trata como una invocación de función del descriptor de acceso subyacente mediante las reglas anteriores. Una invocación de función local es una invocación de función.

9.7.2.7 Valores

El ref-safe-context de un valor es el contexto envolvente más cercano.

Nota: Esto ocurre en una invocación como M(ref d.Length) , donde d es de tipo dynamic. También es coherente con los argumentos correspondientes a los parámetros de entrada. nota final

9.7.2.8 Llamadas de constructores

Una expresión new que invoca a un constructor cumple las mismas reglas que una invocación de método (§9.7.2.6) que se considera que devuelve el tipo que se va a construir.

9.7.2.9 Limitaciones de las variables de referencia

  • Ni un parámetro de referencia, ni un parámetro de salida, ni un parámetro de entrada, ni un ref local, ni un parámetro o local de un tipo ref struct se capturarán mediante expresión lambda o función local.
  • Ni un parámetro de referencia, ni un parámetro de salida, ni un parámetro de entrada, ni un parámetro de tipo ref struct deben ser un argumento para un método iterador o un método async.
  • Ni un ref local ni un local de un tipo ref struct estarán en contexto en el punto de una instrucción yield return o una expresión await.
  • Para una reasignación ref e1 = ref e2, el ref-safe-context de e2 debe ser al menos tan amplio como el ref-safe-context de e1.
  • Para una instrucción de devolución ref return ref e1, el ref-safe-context de e1 será caller-context.