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 la variable. 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 asignará definitivamente una variable (§9.4) antes de que se pueda obtener su valor.

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

9.2 Categorías de variables

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 subclases siguientes describen cada una de estas categorías.

Ejemplo: en el código 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 static modificador es una variable estática. Existe una variable estática antes de la ejecución del static constructor (§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 static modificador es una variable de instancia.

9.2.3.2 Variables de instancia en clases

Una variable de instancia de una clase entra en existencia 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 un struct 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 entra en existencia o deja de existir, también puede hacer las variables de instancia de la estructura.

El estado de asignación inicial de una variable de instancia de un struct es el mismo que el de la variable contenedora struct . En otras palabras, cuando una variable de estructura se considera asignada inicialmente, también son sus variables de instancia y, cuando una variable de estructura se considera inicialmente sin asignar, sus variables de instancia son igualmente sin asignar.

9.2.4 Elementos array

Los elementos de una matriz entran en existencia 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 existe tras la invocación del miembro de función (método, constructor de instancia, descriptor de acceso o operador) o 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 el parámetro value se captura mediante una función anónima (§12.19.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 existe tras 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, no se capturará un parámetro de referencia (§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 asignará definitivamente (§9.4) antes de que se pueda pasar como parámetro de referencia en un miembro de función o invocación de 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 existe tras la invocación del miembro de función, delegado, función anónima o función local y su referencia se inicializa en la variable dada como argumento en 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, no se capturará un parámetro de salida (§9.7.2.9).

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

Nota: Las reglas de 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 un miembro de función o invocación de 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 inicialmente sin asignar.
  • Todos los parámetros de salida de un miembro de función, una función anónima o una función local se asignarán definitivamente (§9.4) antes de que el miembro de función, la función anónima o la función local 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 existe tras la invocación del miembro de función, delegado, función anónima o función local y su referencia se inicializa en el variable_reference dado como argumento en 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, no se capturará un parámetro de entrada (§9.7.2.9).

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

  • Una variable se asignará definitivamente (§9.4) antes de que se pueda pasar como parámetro de entrada en un miembro de función o invocación de delegado.
  • Dentro de un miembro de función, función anónima o 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

Una variable local se declara mediante un local_variable_declaration, declaration_expression, foreach_statement o specific_catch_clause de un 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 un foreach_statement o specific_catch_clause se considera asignada inicialmente.

Un local_variable_declaration puede producirse en un bloque, un for_statement, un switch_block o un using_statement. Un declaration_expression puede producirse como un out argument_value y como un tuple_element que es el destino de una asignación deconstrucción (§12.21.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 reserva 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.19.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 haga 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 un 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, el 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 un 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: Un 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 un 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 para hacer referencia a esa variable local en una posición textual que precede a su declarador.

nota final

9.2.9.1 Descartes

Un descarte es una variable local que no tiene nombre. Un descarte se introduce mediante una expresión de declaración (§12.17) 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 el 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: Se indica 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.21.2) en la que la variable es el operando izquierdo.
    • Expresión de invocación (§12.8.9) o expresión de creación de objetos (§12.8.16.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 de struct_type se realizan un seguimiento individual, así como colectivamente. 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 de 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 los parámetros de salida, lo que permite al 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 this variable de un constructor de instancia de 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 catch cláusula o una foreach instrucción .

9.4.3 Variables sin asignar inicialmente

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

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

9.4.4 Reglas precisas para determinar la asignación definitiva

9.4.4.1 General

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

El compilador procesa el cuerpo de cada miembro de función que tiene una o varias variables sin asignar inicialmente. Para cada variable sin asignar inicialmente v, el compilador determina 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 a este punto, se le ha asignado un valor a v .
  • No está 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 se asigna definitivamente a v 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 se asigna definitivamente a v 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 blockinstrucción , , whileforiflockdoforeachuncheckedchecked, , o usingswitch 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 está asignado definitivamente en todas estas transferencias de flujo de control, v se asigna definitivamente al punto final de la instrucción. De lo contrario, v no está asignado definitivamente al punto 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 statements

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 punto 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, checkedo unchecked instrucción .

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 si definitivamente se asigna al final de expr, definitivamente se asigna al extremo de stmt; de lo contrario, no se asigna definitivamente al final de stmt.

9.4.4.5 Declaraciones de declaración

  • Si stmt es una instrucción de declaración sin inicializadores, v tiene el mismo estado de asignación definitiva en el punto 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 punto 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 switch instrucción stmt 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 guard 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 protecció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 el 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 case 2 when bde modificador inaccesible .

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 default etiqueta 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 guard.
  • 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 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 se asigna definitivamente 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 del formulario:

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 for instrucción que se traduce en instrucciones destinadas a goto la etiqueta LLoop. Si el for_condition se omite de la for instrucción , 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 breakinstrucción , continueo 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 finally bloque de ofinally try-trycatch--finally que incluye la return instrucción .

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 finally bloque de ofinally try-trycatch--finally que incluye la return instrucción .

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 se asigna 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:
    • La versión v está asignada definitivamente en el punto final de try_block
    • La versión v está asignada definitivamente en el punto final de finally_block

Si se realiza una transferencia de flujo de control (por ejemplo, una goto instrucción) 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 solo si, 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 del formulario:

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 try-finally instrucción que incluyera una try-catch instrucción:

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 (§13.11) afectan a la try 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 yield break instrucción 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 secciones 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 la 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.14), expresiones (§12.8.17), typeof expresiones de valor predeterminadas (§12.8.20), nameof expresiones (§12.8.22) y expresiones de declaración (§12.17).

  • 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.11), expresiones de acceso base con indexación (§12.8.14), expresiones de incremento y decremento (§12.8.15, §12.9.6), expresiones de conversión (§12.9.7), unary +, -, ~* expresiones, binary +, -, *, /, %, >>, <=<<< >, >=, ==, !=, is, as&, , , |expresiones ^ (§12.10, §12.11, §12.12, §12.13), expresiones de asignación compuestas (§12.21.4) checked y expresiones (§12.8.19), expresiones de creación de matrices y delegados (§12.8.16) y unchecked await expresiones (§12.9.8).

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 Analizada, ..., 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 la expri (i mayor que uno) es el mismo que el estado de asignación definitiva al final de la expri₋₁.
  • 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 (§22.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. De lo contrario, se aplican las reglas siguientes:

Para una expresión de invocación expr del formulario:

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

o un expr de expresión de creación de objetos con el formato:

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 argi, el estado de asignación definitiva de v después de argi viene determinado por las reglas de expresión normal, omiiendo los inmodificadores , outo ref .
  • Para cada argumento argi para cualquier i mayor que uno, el estado de asignación definitiva de v antes de argi es el mismo que el estado de v después de argi₋₁.
  • Si la variable v se pasa como un out argumento (es decir, un argumento de la forma "out v") en cualquiera de los argumentos, el estado de v después de que expr se asigne 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 inicializadores de matriz (§12.8.16.5), inicializadores de objeto (§12.8.16.3), inicializadores de colección (§12.8.16.4) y inicializadores de objetos anónimos (§12.8.16.7), el estado de asignación definitiva viene determinado por la expansión de que estas construcciones se definen en términos de .

9.4.4.25 Expresiones de asignación simple

Deje que el conjunto de destinos de asignación en una expresión e se defina 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 del formulario:

«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 asignar definitivamente expr. 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 la propiedad designating P es un destino de asigment de expr_lhs, entonces el estado de asignación definitiva de v después de que expr se asigne definitivamente. De lo 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.

Ejemplo: en el código siguiente

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

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

ejemplo final

9.4.4.26 & expresiones

Para una expresión expr del formulario:

«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 se "asigna definitivamente después de una expresión falsa" y el estado de v después de expr_second es "definitivamente asignado después de una expresión falsa", el estado de v después de expr es "definitivamente asignado después de la expresión falsa".
    • De lo contrario, el estado de v después de expr no está asignado definitivamente.

Ejemplo: en el código 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 if instrucción, pero no en la otra. En la if instrucción 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 probado false, lo que da lugar a que la variable ino esté asignada.

ejemplo final

9.4.4.27 || Expresiones

Para una expresión expr del formulario:

«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.
  • 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 la 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 se "asigna definitivamente después de la expresión verdadera", y el estado de v después de expr_ segundo es "definitivamente asignado después de la expresión verdadera", entonces el estado de v después de expr se "asigna definitivamente después de la expresión verdadera".
    • De lo contrario, el estado de v después de expr no está asignado definitivamente.

Ejemplo: en el código 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 if instrucción, pero no en la otra. En la instrucción en el if 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 insertada. Por el contrario, la variable i no está asignada definitivamente en la primera instrucción insertada, ya que x >= 0 podría haber probado true, lo que da lugar a que la variable ise desasigne.

ejemplo final

9.4.4.28 ! expresiones

Para una expresión expr del formulario:

! «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 después de v expr_operand se asigna definitivamente, el estado de después de v expr se asigna definitivamente.
    • De lo contrario, si el estado de después de expr_operand es "definitivamente asignado después de una expresión falsa", el estado de después de v expr se "asigna definitivamente después de la expresión verdadera". v
    • De lo contrario, si el estado de después de expr_operand se "asigna definitivamente 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". v
    • De lo contrario, el estado de después de v expr no está asignado definitivamente.

9.4.4.29 ?? expresiones

Para una expresión expr del formulario:

«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.23) 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 del formulario:

«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 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 el estado de v después de expr_cond está asignado definitivamente o "definitivamente asignado después de una 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.23) 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.23) 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 se asigna definitivamente y el estado de v después de expr_false está asignado definitivamente, el estado de v después de que expr se asigne definitivamente.
    • De lo contrario, el estado de v después de expr no está asignado definitivamente.

9.4.4.31 Funciones anónimas

Para un lambda_expression o anonymous_method_expression expr con un cuerpo (bloque o expresión):

  • 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 fuera de n la función anónima.

ejemplo final

9.4.4.32 Expresiones throw

Para una expresión expr del formulario:

throwthrown_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 que expr se "asigne 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 (return instrucciones, yield instrucciones, await expresiones) 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 F1 local lee s sin asignarla. Se trata de un error si F1 se llama a antes de s que se asigne definitivamente. F2 asigna i antes de leerlo. Se puede llamar antes i de que se asigne definitivamente. Además, F3 se puede llamar a después F2 de que s2 se asigne 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 del formulario:

expr_operand es el patrón

  • 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 el patrón, el estado de asignación definitiva de 'v' después de que expr se "asigne definitivamente cuando es true".
  • De lo 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 a 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++, un variable_reference se conoce como lvalue. nota final

9.6 Atomicidad de referencias de variables

Las lecturas y escrituras de los siguientes tipos de datos serán atómicas: bool, char, shortushortsbyteuintbyteint, 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, doubley 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 de referencia y devoluciones

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 ref modificador .

Una variable de referencia almacena un 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, el variable_reference almacenado 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

Un valor devuelto de referencia es el variable_reference devuelto por un método return-by-ref (§15.6.1). Este variable_reference es el referente de la devolución de referencia.

Ejemplo: en el ejemplo siguiente se muestra una devolución de referencia cuyo referente es un elemento de un campo de 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.12), junto con las restricciones asociadas. nota final

Para cualquier variable, el contexto ref-safe-context de esa variable es el contexto donde un variable_reference (§9.5) a esa variable es válido. 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: El compilador determina el contexto ref-safe a través de un análisis estático del texto del programa. El contexto ref-safe refleja la duración de una variable en tiempo de ejecución. nota final

Hay tres contextos de seguridad ref:

  • declaration-block: el contexto ref-safe-context de un variable_reference a una variable local (§9.2.9) es que el ámbito de la variable local (§13.6.2), incluida cualquier instruccióninsertada anidada en ese ámbito.

    Un 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.

  • miembro de función: dentro de una función, un variable_reference a cualquiera de los siguientes tiene un contexto ref-safe-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
    • Parámetro de referencia implícita () (ref§15.6.2.3.3) this de una función miembro de estructura, junto con sus campos.

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

  • llamador-context: dentro de una función, un variable_reference a cualquiera de los siguientes tiene un contexto ref-safe-context de llamador-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.

Un variable_reference con el contexto ref-safe-context del autor de la llamada puede ser el referente de una devolución de referencia.

Estos valores forman una relación de anidamiento de más estrecha (bloque de declaración) a más ancha (contexto de llamada). Cada bloque anidado representa un contexto diferente.

Ejemplo: en el código siguiente se muestran ejemplos de los distintos contextos de referencia segura. Las declaraciones muestran el contexto ref-safe para que un referente sea la expresión de inicialización de una ref variable. En los ejemplos se muestra el contexto ref-safe 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 struct los tipos, el parámetro implícito this se pasa como parámetro de referencia. El contexto ref-safe de los campos de un struct tipo como miembro de función 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 Contexto seguro de variable local ref

Para una variable vlocal :

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

9.7.2.3 Contexto seguro del parámetro ref

Para un parámetro p:

  • Si p es un parámetro de referencia o entrada, su contexto ref-safe es el contexto del autor de la llamada. 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 contexto ref-safe es el contexto del autor de la llamada.
  • De lo contrario, si p es el this parámetro de un tipo struct, su contexto ref-safe-context es el miembro de función.
  • De lo contrario, el parámetro es un parámetro de valor y su contexto ref-safe-context es el miembro de función.

9.7.2.4 Contexto seguro de referencia de campo

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

  • Si e es de un tipo de referencia, su contexto ref-safe es el contexto del autor de la llamada.
  • De lo contrario, si e es de un tipo de valor, su contexto ref-safe-context es el mismo que el contexto ref-safe-context de e.

9.7.2.5 Operadores

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

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:

  • Contexto del autor de la llamada.
  • Contexto ref-safe-de todas las refexpresiones de argumento , outy 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 contexto ref-safe-context de la variable; de lo contrario, el contexto envolvente más cercano.
  • Contexto seguro (§16.4.12) 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 contexto ref-safe 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 Invocaciones del constructor

Una new expresión 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 construye.

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 parámetro local ni un parámetro o local de un ref struct tipo 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 un ref struct tipo deben ser un argumento para un método iterador o un async método.
  • Ni un ref local ni un local de un ref struct tipo estarán en contexto en el punto de una yield return instrucción o una await expresión.
  • Para una reasignación e1 = ref e2ref, el contexto de referencia seguro de e2 debe ser al menos tan amplio como el contexto ref-safe-context de e1.
  • Para una instrucción return ref e1ref return , el contexto ref-safe-context de será el contexto del autor de e1 la llamada.