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 yi
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 deM
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 unaforeach
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 unaforeach
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
block
instrucción , ,while
for
if
lock
do
foreach
unchecked
checked
, , ousing
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 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, checked
o 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 b
de 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 break
instrucción , 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
finally
bloque de ofinally
try
-try
catch
--finally
que incluye lareturn
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
-try
catch
--finally
que incluye lareturn
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
in
modificadores ,out
oref
. - 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ésarr[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 unaif
instrucción, pero no en la otra. En laif
instrucción del métodoF
, la variablei
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 variablei
no está asignada definitivamente en la segunda instrucción insertada, ya quex >= 0
podría haber probado false, lo que da lugar a que la variablei
no 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 unaif
instrucción, pero no en la otra. En la instrucción en elif
métodoG
, la variablei
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 variablei
no está asignada definitivamente en la primera instrucción insertada, ya quex >= 0
podría haber probado true, lo que da lugar a que la variablei
se 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 dev
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.
- Si el estado de después de
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.
- Si expr_first es una expresión constante (§12.23) con el valor
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.
- Si expr_cond es una expresión constante (§12.23) con el valor
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 den
la función anónima.ejemplo final
9.4.4.32 Expresiones throw
Para una expresión expr del formulario:
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 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 lees
sin asignarla. Se trata de un error siF1
se llama a antes des
que se asigne definitivamente.F2
asignai
antes de leerlo. Se puede llamar antesi
de que se asigne definitivamente. Además,F3
se puede llamar a despuésF2
de ques2
se asigne definitivamente enF2
.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
, short
ushort
sbyte
uint
byte
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 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.
- Parámetros de valor (§15.6.2.2) en una declaración de miembro de función, incluida la implícita
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.
- Parámetros de referencia (§9.2.6) distintos del implícito
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ícitothis
se pasa como parámetro de referencia. El contexto ref-safe de los campos de unstruct
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 v
local :
- 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. Sip
es un parámetro de entrada, no se puede devolver como grabableref
, pero se puede devolver comoref 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 elthis
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 dee
.
9.7.2.5 Operadores
El operador condicional (§12.18), c ? ref e1 : ref e2
y 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
ref
expresiones de argumento ,out
yin
(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)
, donded
es de tipodynamic
. 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 unref 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 unasync
método. - Ni un
ref
local ni un local de unref struct
tipo estarán en contexto en el punto de unayield return
instrucción o unaawait
expresión. - Para una reasignación
e1 = ref e2
ref, el contexto de referencia seguro dee2
debe ser al menos tan amplio como el contexto ref-safe-context dee1
. - Para una instrucción
return ref e1
ref return , el contexto ref-safe-context de será el contexto del autor dee1
la llamada.
ECMA C# draft specification