Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
16.1 General
Las estructuras son similares a las clases en que representan estructuras de datos que pueden contener atributos y métodos. Sin embargo, a diferencia de las clases, las estructuras son tipos de valor y no requieren asignación en el montón. Una variable de un struct tipo contiene directamente los datos de struct, mientras que una variable de un tipo de clase contiene una referencia a los datos, el último conocido como objeto .
Nota: Las estructuras son especialmente útiles para estructuras de datos pequeñas que tienen semántica de valor. Los números complejos, los puntos de un sistema de coordenadas o los pares clave-valor de un diccionario son buenos ejemplos de structs. La clave de estas estructuras de datos es que tienen pocos miembros de datos, que no requieren el uso de la semántica de herencia o referencia, sino que se pueden implementar convenientemente mediante la semántica de valores donde la asignación copia el valor en lugar de la referencia. nota final
Como se describe en §8.3.5, los tipos simples proporcionados por C#, como int, doubley bool, son, de hecho, todos los tipos de estructura.
16.2 Declaraciones de estructura
16.2.1 General
Un struct_declaration es un type_declaration (§14.8) que declara una nueva estructura:
struct_declaration
: non_record_struct_declaration
| record_struct_declaration
;
non_record_struct_declaration
: attributes? struct_modifier* 'ref'? 'partial'? 'struct'
identifier type_parameter_list? struct_interfaces?
type_parameter_constraints_clause* struct_body ';'?
;
record_struct_declaration
: attributes? struct_modifier* 'partial'? 'record' 'struct'
identifier type_parameter_list? delimited_parameter_list? struct_interfaces?
type_parameter_constraints_clause* record_struct_body
;
record_struct_body
: struct_body ';'?
| ';'
;
Un struct_declaration es para una estructura que no sea de registro o para una estructura de registro.
Un non_record_struct_declaration consta de un conjunto opcional de atributos (§23), seguido de un conjunto opcional de struct_modifiers (§16.2.2), seguido de un modificador opcional ref (§16.2.3), seguido de un modificador parcial opcional (§15.2.7), seguido de la palabra clave struct y un identificador que asigna el nombre a la estructura, seguido de una especificación de type_parameter_list opcional (§15.2.3), seguido de una especificación opcional de struct_interfaces (§16.2.5), seguida de una especificación opcional de cláusulas type_parameter_constraints (§15.2.5), seguida de un struct_body (§16.2.6), opcionalmente seguido de un punto y coma.
Un record_struct_declaration consta de un conjunto opcional de atributos (§23), seguido de un conjunto opcional de struct_modifiers (§16.2.2), seguido de un modificador parcial opcional (§15.2.7), seguido de la palabra clave , seguido de la palabra clave recordstruct y un identificador que asigna el nombre struct, seguido de una especificación opcional type_parameter_list (§15.2.3), seguida de un delimited_parameter_list opcional especificación (§15.2.1), seguida de una especificación opcional de struct_interfaces (§16.2.5), seguida de una especificación opcional de cláusulas type_parameter_constraints (§15.2.5), seguida de un record_struct_body.
Un struct_declaration no proporcionará type_parameter_constraints_clausea menos que también proporcione una type_parameter_list.
Un struct_declaration que proporciona un type_parameter_list es una declaración de estructura genérica. Además, cualquier estructura anidada dentro de una declaración de clase genérica o una declaración de estructura genérica es una declaración de estructura genérica, ya que se proporcionarán argumentos de tipo para el tipo contenedor para crear un tipo construido (§8.4).
Un non_record_struct_declaration que incluya un ref modificador no tendrá una parte struct_interfaces .
Un record_struct_declaration que tiene un delimited_parameter_list declara una estructura de registro posicional.
Como máximo, solo un record_struct_declaration que contenga partial puede proporcionar un delimited_parameter_list.
Los parámetros de delimited_parameter_list no tendrán refmodificadores , out o this ; sin embargo, in y params se permiten modificadores.
Para un record_struct_declaration, los record_struct_bodys {}, {};y ; son equivalentes. Todos indican que los únicos miembros son los sintetizados por el compilador (§16.4).
16.2.2 Modificadores de estructura
Un struct_declaration puede incluir opcionalmente una secuencia de struct_modifiers:
struct_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| 'readonly'
| unsafe_modifier // unsafe code support
;
unsafe_modifier (§24.2) solo está disponible en código no seguro (§24).
Es un error de compilación que el mismo modificador aparezca varias veces en una declaración de estructura.
Excepto para readonly, los modificadores de una declaración de estructura tienen el mismo significado que los de una declaración de clase (§15.2.2).
El readonly modificador indica que el struct_declaration declara un tipo cuyas instancias son inmutables.
Una estructura de solo lectura tiene las siguientes restricciones:
- Cada uno de sus campos de instancia también se declarará
readonly. - No deberá declarar ningún tipo de evento similar al de campo (§ 15.8.2).
Cuando una instancia de una estructura readonly se pasa a un método, su contenido no puede modificarse. this se trata como un argumento/parámetro de entrada, lo que impide el acceso de escritura a cualquier campo de instancia (excepto mediante constructores).
16.2.3 Modificador Ref
El ref modificador indica que el non_record_struct_declaration declara un tipo cuyas instancias se asignan en la pila de ejecución. Estos tipos se denominan tipos ref struct. El ref modificador declara que las instancias pueden contener campos de tipo ref y no se copiarán fuera de su contexto seguro (§16.5.15). Las reglas para determinar el contexto seguro de una estructura ref se describen en §16.5.15.
Se trata de un error en tiempo de compilación si se usa un tipo de estructura ref en cualquiera de los contextos siguientes:
- Como tipo de elemento de una matriz.
- Como el tipo declarado de un campo de una clase o una estructura que no tiene el
refmodificador . - Como argumento de tipo.
- Como el tipo de un elemento de tupla.
- En un método asincrónico.
- En un iterador.
- Como tipo de receptor para una conversión de grupo de métodos de un método de instancia a un tipo delegado.
- Como una variable capturada en una expresión lambda o una función local.
Además, las restricciones siguientes se aplican a un ref struct tipo:
- Un
ref structtipo no se incluirá enSystem.ValueTypeoSystem.Object. - Un tipo
ref structno deberá ser declarado para implementar ninguna interfaz. - Un método de instancia declarado en
objecto enSystem.ValueTypepero no sobrescrito en unref structEste tipo no se debe llamar con un receptor de ese tiporef structtipo.
Nota: Un
ref structno declararáasyncmétodos de instancia ni usará unayield returninstrucción oyield breakdentro de un método de instancia, ya que el parámetro implícitothisno se puede usar en esos contextos. nota final
Estas restricciones garantizan que una variable de ref struct tipo no haga referencia a la memoria de pila que ya no sea válida o a variables que ya no sean válidas.
16.2.4 Modificador parcial
El partial modificador indica que este struct_declaration es una declaración de tipo parcial. Varias declaraciones de estructura parciales con el mismo nombre dentro de un espacio de nombres o declaración de tipo envolvente se combinan para formar una declaración de estructura, siguiendo las reglas especificadas en §15.2.7.
16.2.5 Interfaces de estructura
Una declaración de estructura puede incluir una especificación struct_interfaces , en cuyo caso se dice que la estructura implementa directamente los tipos de interfaz especificados. Para un tipo de estructura construido, incluido un tipo anidado declarado dentro de una declaración de tipo genérico (§15.3.9.7), cada tipo de interfaz implementado se obtiene sustituyendo, por cada type_parameter en la interfaz especificada, el type_argument correspondiente del tipo construido.
struct_interfaces
: ':' interface_type_list
;
El control de interfaces en varias partes de una declaración de estructura parcial (§15.2.7) se describe más adelante en §15.2.4.3.
Las implementaciones de interfaz se describen más adelante en §19.6.
Estructura de cuerpo 16.2.6
El struct_body de un struct define los miembros de la estructura.
struct_body
: '{' struct_member_declaration* '}'
;
16.3 Miembros de estructuras
16.3.1 General
Los miembros de una estructura constan de los miembros introducidos por sus struct_member_declarations y los miembros heredados del tipo System.ValueType. Para una estructura de registro, el conjunto de miembros también incluye los miembros sintetizados generados por el compilador (§synth-members).
struct_member_declaration
: constant_declaration
| field_declaration
| method_declaration
| property_declaration
| event_declaration
| indexer_declaration
| operator_declaration
| constructor_declaration
| static_constructor_declaration
| type_declaration
| fixed_size_buffer_declaration // unsafe code support
;
fixed_size_buffer_declaration (§24.8.2) solo está disponible en código no seguro (§24).
Nota: Todos los tipos de class_member_declaration, excepto finalizer_declaration, son también struct_member_declarations. nota final
A excepción de las diferencias indicadas en §16.5, las descripciones de los miembros de clase proporcionados en §15.3 a §15.12 también se aplican a los miembros struct.
Se trata de un error para que un campo de instancia de una estructura de registro tenga un tipo no seguro.
16.3.2 Miembros de Readonly
Una definición o descriptor de acceso de miembro de instancia de una propiedad de instancia, indexador o evento que incluye el readonly modificador tiene las restricciones siguientes:
- El
thisparámetro es unaref readonlyreferencia. - El miembro no reasignará el valor de o un campo de
thisinstancia del receptor. - El miembro no reasignará el valor de un evento similar a un campo de instancia (§15.8.2) del receptor.
- Si un miembro readonly invoca a un miembro que no es de solo lectura, la estructura a
thisla que hace referencia debe copiarse para usar una referencia grabable para elthisargumento.
Nota: Los campos de instancia incluyen el campo de respaldo oculto usado para las propiedades implementadas automáticamente (§15.7.4). nota final
Ejemplo: un miembro readonly puede modificar el estado de un objeto al que hace referencia un campo de instancia, aunque el miembro readonly no pueda reasignar ese miembro de instancia. En el código siguiente se muestra la reasignación y modificación de un campo de instancia:
public struct S { private List<string> messages; public S(IEnumerable<string> messages) => this.messages = new List<string>(messages); public void InitializeMessages() => messages = new List<string>(); public readonly void AddMessage(string message) { if (messages == null) { throw new InvalidOperationException("Messages collection is not initialized."); } messages.Add(message); } }El
readonlymétodoAddMessagepuede cambiar el estado de una lista de mensajes. ElInitializeMessagesmiembro puede borrar e inicializar de nuevo la lista de mensajes. En el caso deAddMessage, elreadonlymodificador es válido. En el caso deInitializeMessages, agregar elreadonlymodificador no es válido. ejemplo final
16.4 Miembros de estructura de registro sintetizado
16.4.1 General
En el caso de una estructura de registro, los miembros se sintetizan a menos que un miembro con una firma "coincidente" se declare en el record_struct_body o se herede un miembro concreto no virtual accesible con una firma "coincidente". Dos miembros se consideran coincidentes si tienen la misma firma o se considerarían "ocultar" en un escenario de herencia. (Consulte Signatures and overloading §7.6.)
Los miembros sintetizados se describen en las subclases siguientes.
16.4.2 Miembros de igualdad
Los miembros de igualdad sintetizados son similares a los de una clase de registro (§15.16.2), excepto por la falta de EqualityContract, comprobaciones nulas o herencia.
Una estructura R de registro implementa System.IEquatable<R> e incluye una sobrecarga sintetizada fuertemente tipada de Equals(R other), que es pública, como se indica a continuación:
public readonly bool Equals(R other);
Este método se puede declarar explícitamente. Sin embargo, se produce un error si la declaración explícita no coincide con la firma o accesibilidad esperadas.
Si Equals(R other) está definido por el usuario (es decir, no sintetizado), pero GetHashCode no es así, se producirá una advertencia.
El sintetizado Equals(R) devolverá true si y solo si para cada campo fieldN de instancia del struct de registro el valor de , donde TN es el tipo de System.Collections.Generic.EqualityComparer<TN>.Default.Equals(fieldN, other.fieldN)campo, es true.
La estructura de registro incluye operadores sintetizados == y != equivalentes a operadores declarados de la siguiente manera:
public static bool operator==(R r1, R r2) => r1.Equals(r2);
public static bool operator!=(R r1, R r2) => !(r1 == r2);
El Equals método llamado por el == operador es el Equals(R other) método especificado anteriormente. El != operador delega al == operador . Es un error si los operadores se declaran explícitamente.
La estructura de registro incluye una invalidación sintetizada equivalente a un método declarado de la siguiente manera:
public override readonly bool Equals(object? obj);
Se trata de un error si la invalidación se declara explícitamente. La invalidación sintetizada devolverá other is R temp && Equals(temp) donde R es la estructura de registro.
La estructura de registro incluye una invalidación sintetizada equivalente a un método declarado de la siguiente manera:
public override readonly int GetHashCode();
Este método se puede declarar explícitamente.
Se notificará una advertencia si uno de Equals(R) los elementos y GetHashCode() se declara explícitamente, pero el otro método no lo es.
La invalidación sintetizada de GetHashCode() devolverá un int resultado de combinar los valores de System.Collections.Generic.EqualityComparer<TN>.Default.GetHashCode(fieldN) para cada campo fieldN de instancia con TN el tipo de fieldN.
Ejemplo: Considere la siguiente estructura de registro:
record struct R1(T1 P1, T2 P2);Para ello, los miembros de igualdad sintetizados serían algo parecido a:
struct R1 : IEquatable<R1> { public T1 P1 { get; set; } public T2 P2 { get; set; } public override bool Equals(object? obj) => obj is R1 temp && Equals(temp); public bool Equals(R1 other) { return EqualityComparer<T1>.Default.Equals(P1, other.P1) && EqualityComparer<T2>.Default.Equals(P2, other.P2); } public static bool operator==(R1 r1, R1 r2) => r1.Equals(r2); public static bool operator!=(R1 r1, R1 r2) => !(r1 == r2); public override int GetHashCode() { return HashCode.Combine( EqualityComparer<T1>.Default.GetHashCode(P1), EqualityComparer<T2>.Default.GetHashCode(P2));ejemplo final
16.4.3 Miembros de impresión
Un struct de registro incluye un método sintetizado equivalente a lo siguiente:
private bool PrintMembers(System.Text.StringBuilder builder);
Este método realiza las siguientes tareas:
- Para cada uno de los miembros imprimibles de la estructura de registro (miembros de propiedad que no son públicos no estáticos y miembros de propiedad legibles), anexa el nombre del miembro seguido de "
=" seguido del valor del miembro separado por ", “, - Devuelve true si la estructura de registro tiene miembros imprimibles.
Para un miembro que tiene un tipo de valor, su valor se convertirá en una representación de cadena.
Si los miembros imprimibles del registro no incluyen una propiedad legible con un descriptor de acceso quereadonlyget no es , el sintetizado PrintMembers es readonly. No es necesario que los campos del registro sean readonly para que el PrintMembers método sea readonly.
El PrintMembers método se puede declarar explícitamente. Sin embargo, se produce un error si la declaración explícita no coincide con la firma o accesibilidad esperadas.
La estructura de registro incluye un método sintetizado equivalente a lo siguiente:
public override string ToString();
Si el método del struct de PrintMembers registro es readonly, el método sintetizado ToString() será readonly.
Este método se puede declarar explícitamente. Se trata de un error si la declaración explícita no coincide con la firma o accesibilidad esperadas.
Este método realiza las siguientes tareas:
- Crea una
StringBuilderinstancia de , - Anexa el nombre del struct de registro al generador, seguido de "
{", - Invoca el método del struct de
PrintMembersregistro que le proporciona el generador, seguido de "" si devolvió true, - Anexa "
}", - Devuelve el contenido del generador con
builder.ToString().
Ejemplo: Considere la siguiente estructura de registro:
record struct R1(T1 P1, T2 P2);Para esta estructura de registro, los miembros de impresión sintetizados serían algo parecido a:
struct R1 : IEquatable<R1> { public T1 P1 { get; set; } public T2 P2 { get; set; } private bool PrintMembers(StringBuilder builder) { builder.Append(nameof(P1)); builder.Append(" = "); builder.Append(this.P1); // or builder.Append(this.P1.ToString()); // if P1 has a value type builder.Append(", "); builder.Append(nameof(P2)); builder.Append(" = "); builder.Append(this.P2); // or builder.Append(this.P2.ToString()); // if P2 has a value type return true; } public override string ToString() { var builder = new StringBuilder(); builder.Append(nameof(R1)); builder.Append(" { "); if (PrintMembers(builder)) builder.Append(" "); builder.Append("}"); return builder.ToString(); } }ejemplo final
16.4.4 Miembros de estructura de registro posicional
16.4.4.1 General
Además de proporcionar los miembros descritos en las subclases anteriores, las estructuras de registro posicional (§16.2.1) sintetizan miembros adicionales con las mismas condiciones que los demás miembros, tal como se describe en las subclases siguientes.
16.4.4.2 Constructor principal
Una estructura de registro tiene un constructor público cuya firma corresponde a los parámetros de valor de la declaración de tipo. Esto se denomina constructor principal para el tipo. Es un error tener un constructor principal y un constructor con la misma firma ya presente en la estructura. Si la declaración de tipo no incluye un delimited_parameter_list, no se genera ningún constructor principal.
record struct R1 { public R1() { } // OK } record struct R2() { public R2() { } // error: 'R2' already defines // a constructor with the same parameter types }
Las declaraciones de campo de instancia para una estructura de registro pueden incluir inicializadores de variables. Si no hay ningún constructor principal, los inicializadores de instancia se ejecutan como parte del constructor sin parámetros. De lo contrario, en tiempo de ejecución, el constructor principal ejecuta los inicializadores de instancia que aparecen en el registro-struct-body.
Si un struct de registro tiene un constructor principal, cualquier constructor definido por el usuario tendrá un inicializador de constructor explícito this que llame al constructor principal o a un constructor declarado explícitamente.
Los parámetros del constructor principal, así como los miembros de la estructura de registro están en el ámbito dentro de inicializadores de campos o propiedades de instancia. Los miembros de instancia serían un error en estas ubicaciones, pero los parámetros del constructor principal estarían en el ámbito y se pueden usar y sombrearían miembros. Los miembros estáticos también se pueden usar.
Se producirá una advertencia si no se lee un parámetro del constructor principal.
Las reglas de asignación definitivas para constructores de instancia de estructura se aplican al constructor principal de estructuras de registro. Por ejemplo, lo siguiente es un error:
record struct Pos(int X) // def assignment error in primary constructor { private int x; public int X { get { return x; } set { x = value; } } = X; }
16.4.4.3 Propiedades
Para cada parámetro de un delimited_parameter_list que tenga el mismo nombre y tipo que un campo de instancia declarado explícitamente, el resto de esta subclausa no se aplica.
Para cada parámetro de estructura de registro de un delimited_parameter_list hay un miembro de propiedad pública correspondiente cuyo nombre y tipo se toman de la declaración de parámetro de valor.
Para una estructura de registro:
Se crea una propiedad pública
getyinitautomática si la estructura de registro tiene unreadonlymodificador yset, de lo contrario,get. Ambos tipos de descriptores de acceso set (setyinit) se consideran "coincidentes". Por lo tanto, el usuario puede declarar una propiedad de solo inicialización en lugar de una mutable sintetizada.Se invalida una propiedad heredada
abstractcon el tipo coincidente.No se crea ninguna propiedad automática si la estructura de registro tiene un campo de instancia con el nombre y el tipo esperados.
Se trata de un error si la propiedad heredada no tiene
publicdescriptores de acceso yinitset/.getSe trata de un error si la propiedad o el campo heredados están ocultos.
La propiedad automática se inicializa en el valor del parámetro de constructor principal correspondiente.
Los atributos se pueden aplicar a la propiedad automática sintetizada y su campo de respaldo mediante
property:ofield:destinos para atributos sintácticamente aplicados al parámetro de estructura de registro correspondiente.
16.4.4.4 Deconstruct
Un struct de registro posicional con al menos un parámetro sintetiza un método de instancia público voidque devuelve llamada Deconstruct con una declaración de parámetro out para cada parámetro de la declaración del constructor principal. Cada parámetro de Deconstruct tiene el mismo tipo que el parámetro correspondiente de la declaración del constructor principal. El cuerpo del método asigna cada parámetro del método Deconstruct al valor de un acceso de miembro de instancia a un miembro del mismo nombre.
Si los miembros de instancia a los que se accede en el cuerpo no incluyen una propiedad con un descriptor de acceso que no es ,readonlyget el método sintetizado Deconstruct es readonly.
El método se puede declarar explícitamente. Se trata de un error si la declaración explícita no coincide con la firma o accesibilidad esperadas, o si es estática.
16.5 Diferencias de clase y estructura
16.5.1 General
Las estructuras difieren de las clases de varias maneras importantes:
- Las estructuras son tipos de valor (§16.5.2).
- Todos los tipos de estructura heredan implícitamente de la clase
System.ValueType(§16.5.3). - La asignación a una variable de un tipo de estructura crea una copia del valor que se asigna (§16.5.4).
- El valor predeterminado de un struct es el valor generado estableciendo todos los campos en su valor predeterminado (§16.5.5).
- Las operaciones de conversión boxing y unboxing se usan para convertir entre un tipo de estructura y determinados tipos de referencia (§16.5.6).
- El significado de es diferente dentro de
thislos miembros struct (§16.5.7). - No se permite que un struct declare un finalizador.
- Las declaraciones de eventos, las declaraciones de propiedades, los accesores de propiedad, las declaraciones de indizadores y las declaraciones de métodos pueden tener el modificador
readonly, aunque no se permite normalmente para esos mismos tipos de miembros en las clases.
16.5.2 Semántica de valores
Las estructuras son tipos de valor (§8.3) y se dice que tienen semántica de valor. Las clases, por otro lado, son tipos de referencia (§8.2) y se dice que tienen semántica de referencia.
Una variable de un tipo de estructura contiene directamente los datos de la estructura, mientras que una variable de un tipo de clase contiene una referencia a un objeto que contiene los datos. Cuando una estructura B contiene un campo de instancia de tipo A y A es un tipo de estructura, es un error en tiempo de compilación que A dependa de B o de un tipo construido a partir de B. Un struct Xdepende directamente de un struct Y si X contiene un campo de instancia de tipo Y. Dado esta definición, el conjunto completo de estructuras de las que depende una estructura es el cierre transitivo de la misma. depende directamente de relación.
Ejemplo:
struct Node { int data; Node next; // error, Node directly depends on itself }es un error porque
Nodecontiene un campo de instancia de su propio tipo. Otro ejemplostruct A { B b; } struct B { C c; } struct C { A a; }es un error porque cada uno de los tipos
A,ByCdependen entre sí.ejemplo final
Con las clases, es posible que dos variables hagan referencia al mismo objeto y, por tanto, es posible que las operaciones de una variable afecten al objeto al que hace referencia la otra variable. Con structs, las variables tienen su propia copia de los datos (excepto en el caso de los parámetros by-reference) y no es posible que las operaciones de una afecten a la otra. Además, excepto cuando se aceptan valores NULL explícitamente (§8.3.12), no es posible que los valores de un tipo de estructura sean null.
Nota: Si una estructura contiene un campo de tipo de referencia, otras operaciones pueden modificar el contenido del objeto al que se hace referencia. Sin embargo, el valor del propio campo, es decir, el objeto al que hace referencia, no se puede cambiar a través de una mutación de un valor de estructura diferente. nota final
Ejemplo: dado lo siguiente
struct Point { public int x, y; public Point(int x, int y) { this.x = x; this.y = y; } } class A { static void Main() { Point a = new Point(10, 10); Point b = a; a.x = 100; Console.WriteLine(b.x); } }la salida es
10. La asignación deaparabcrea una copia del valor yb, por tanto, no se ve afectada por la asignación aa.x. En su lugar, se habíaPointdeclarado como una clase, la salida sería100porqueaybharía referencia al mismo objeto.ejemplo final
16.5.3 Herencia
Todos los tipos de estructura heredan implícitamente de la clase System.ValueType, que, a su vez, hereda de la clase object. Una declaración de estructura puede especificar una lista de interfaces implementadas, pero no es posible que una declaración de estructura especifique una clase base.
Los tipos de estructura nunca son abstractos y siempre están sellados implícitamente. Por lo tanto, los modificadores abstract y sealed no se permiten en una declaración de estructura.
Dado que no se admite la herencia para estructuras, la accesibilidad declarada de un miembro de estructura no puede ser protected, private protectedo protected internal.
Los miembros de función de una estructura no pueden ser abstractos o virtuales, y el override modificador solo puede invalidar los métodos heredados de System.ValueType.
16.5.4 Asignación
La asignación a una variable de un tipo de estructura crea una copia del valor que se asigna. Esto difiere de la asignación a una variable de un tipo de clase, que copia la referencia, pero no el objeto identificado por la referencia.
De forma similar a una asignación, cuando se pasa una estructura como parámetro de valor o se devuelve como resultado de un miembro de una función, se crea una copia de la estructura. Una estructura se puede pasar por referencia a un miembro de función utilizando un parámetro por referencia.
Cuando una propiedad o indexador de un struct es el destino de una asignación, la expresión de instancia asociada a la propiedad o el acceso al indexador se clasificará como una variable. Si la expresión de instancia se clasifica como un valor, se produce un error en tiempo de compilación. Esto se describe con más detalle en §12.24.2.
16.5.5 Valores predeterminados
Como se describe en §9.3, varios tipos de variables se inicializan automáticamente en su valor predeterminado cuando se crean. Para variables de tipos de clase y otros tipos de referencia, este valor predeterminado es null. Sin embargo, dado que las estructuras son tipos de valor que no pueden ser null, el valor predeterminado de un struct es el valor generado estableciendo todos los campos de tipo de valor en su valor predeterminado y todos los campos de tipo de referencia en null.
Ejemplo: Hacer referencia a la
Pointestructura declarada anteriormente, el ejemploPoint[] a = new Point[100];inicializa cada
Pointen la matriz al valor producido estableciendo los camposxyyen cero.ejemplo final
El valor predeterminado de una estructura corresponde al valor devuelto por el constructor predeterminado de la estructura (§8.3.3). Cuando un struct no declara un constructor de instancia sin parámetros explícito, el constructor predeterminado se sintetiza y siempre devuelve el valor resultante de establecer todos los campos en sus valores predeterminados. La default expresión siempre genera el valor predeterminado inicializado cero, incluso cuando un struct declara un constructor de instancia sin parámetros explícito (§16.4.9).
Nota: Las estructuras deben diseñarse para considerar el estado de inicialización predeterminado un estado válido. En el ejemplo
struct KeyValuePair { string key; string value; public KeyValuePair(string key, string value) { if (key == null || value == null) { throw new ArgumentException(); } this.key = key; this.value = value; } }El constructor de instancia definido por el usuario solo protege contra los valores
nullcuando se llama explícitamente. En los casos en los que unaKeyValuePairvariable está sujeta a la inicialización del valor predeterminado, loskeycampos yvalueseránnully la estructura debe estar preparada para controlar este estado.nota final
16.5.6 Boxing y unboxing
Un valor de un tipo de clase se puede convertir al tipo object o a un tipo de interfaz implementado por la clase simplemente tratando la referencia como otro tipo en tiempo de compilación. Del mismo modo, un valor de tipo object o un valor de un tipo de interfaz se puede convertir de nuevo a un tipo de clase sin cambiar la referencia (pero, por supuesto, se requiere una comprobación de tipo en tiempo de ejecución en este caso).
Dado que los structs no son tipos de referencia, estas operaciones se implementan de forma diferente para los tipos de estructura. Cuando se convierte un valor de un tipo de estructura en determinados tipos de referencia (como se define en §10.2.9), se realiza una operación de empaquetado. Del mismo modo, cuando se convierte un valor de determinados tipos de referencia (como se define en §10.3.7) a un tipo de estructura, se realiza una operación de desempaquetado. Una diferencia clave con respecto a las mismas operaciones en tipos de clase es que la conversión a objeto y la conversión de objeto a valor no son necesarias. copias el valor de la estructura, ya sea hacia dentro o hacia fuera de la instancia en caja.
Nota: Por lo tanto, después de una operación de conversión a tipo primitivo o de conversión a objeto, los cambios realizados en el objeto convertido a tipo primitivo se reflejan automáticamente en el objeto original.
structno se reflejan en el recuadrostruct. nota final
Para obtener más información sobre boxing y unboxing, consulte §10.2.9 y §10.3.7.
16.5.7 Significado de esto
El significado de this en un struct difiere del significado de this en una clase, como se describe en §12.8.14. Cuando un tipo de estructura sobrescribe un método virtual heredado de System.ValueType (como Equals, GetHashCode, o ToString), la invocación del método virtual a través de una instancia del tipo de estructura no provoca la conversión a objeto. Esto es cierto incluso cuando la estructura se usa como parámetro de tipo y la invocación se produce a través de una instancia del tipo de parámetro de tipo.
Ejemplo:
struct Counter { int value; public override string ToString() { value++; return value.ToString(); } } class Program { static void Test<T>() where T : new() { T x = new T(); Console.WriteLine(x.ToString()); Console.WriteLine(x.ToString()); Console.WriteLine(x.ToString()); } static void Main() => Test<Counter>(); }La salida del programa es:
1 2 3Aunque no es recomendable que
ToStringtenga efectos secundarios, el ejemplo demuestra que no se produjo ninguna conversión a objeto para las tres invocaciones dex.ToString().ejemplo final
De manera similar, la conversión a objeto nunca se produce de forma implícita al acceder a un miembro de un parámetro de tipo restringido cuando el miembro se implementa dentro del tipo de valor. Por ejemplo, supongamos que una interfaz ICounter contiene un método Increment, que se puede usar para modificar un valor. Si ICounter si se utiliza como una restricción, la implementación de la Increment el método se llama con una referencia a la variable que Increment fue solicitado, nunca una copia en caja.
Ejemplo:
interface ICounter { void Increment(); } struct Counter : ICounter { int value; public override string ToString() => value.ToString(); void ICounter.Increment() => value++; } class Program { static void Test<T>() where T : ICounter, new() { T x = new T(); Console.WriteLine(x); x.Increment(); // Modify x Console.WriteLine(x); ((ICounter)x).Increment(); // Modify boxed copy of x Console.WriteLine(x); } static void Main() => Test<Counter>(); }La primera llamada a
Incrementmodifica el valor de la variablex. Esto no equivale a la segunda llamada aIncrement, que modifica el valor en una copia encapsulada dex. Por lo tanto, la salida del programa es:0 1 1ejemplo final
Inicializadores de campo 16.5.8
Como se describe en §16.5.5, el valor predeterminado de un struct consta del valor resultante de establecer todos los campos de tipo de valor en su valor predeterminado y todos los campos de tipo de referencia en null. Los campos estáticos e de instancia de una estructura pueden incluir inicializadores de variables; sin embargo, en el caso de un inicializador de campo de instancia, también se declarará al menos un constructor de instancia, o para una estructura de registro, habrá una delimited_parameter_list .
Ejemplo:
Console.WriteLine($"Point is {new Point()}"); struct Point { public int x = 1; public int y = 1; public Point() { } public override string ToString() { return "(" + x + ", " + y + ")"; } }Point is (1, 1)ejemplo final
Cuando un constructor de instancia de estructura no tiene inicializador de constructor, ese constructor realiza implícitamente las inicializaciones especificadas por los variable_initializerde los campos de instancia declarados en su estructura. Esto corresponde a una secuencia de asignaciones que se ejecutan inmediatamente después de la entrada al constructor.
Cuando un constructor de instancia de estructura tiene un this() inicializador de constructor que representa el constructor sin parámetros predeterminado, el constructor declarado borra implícitamente todos los campos de instancia y realiza las inicializaciones especificadas por los variable_initializers de los campos de instancia declarados en su estructura. Inmediatamente después de la entrada al constructor, todos los campos de tipo de valor se establecen en su valor predeterminado y todos los campos de tipo de referencia se establecen en null. Inmediatamente después de eso, se ejecuta una secuencia de asignaciones correspondientes a los variable_initializers.
Una field_declaration declarada directamente dentro de un struct_declaration que tenga el struct_modifierreadonly tendrá el field_modifierreadonly.
16.5.9 Constructores
Un struct puede declarar constructores de instancia, con cero o más parámetros. Si un struct no tiene ningún constructor de instancia sin parámetros declarado explícitamente, se sintetiza uno, con accesibilidad pública, que siempre devuelve el valor resultante de establecer todos los campos de tipo de valor en su valor predeterminado y todos los campos de tipo de referencia en null (§8.3.3). En tal caso, se omite cualquier inicializador de campo de instancia cuando se ejecuta ese constructor.
Un constructor de instancia sin parámetros declarado explícitamente tendrá accesibilidad pública.
Ejemplo: dado lo siguiente:
using System; struct Point { int x = -1, y = -2; public Point(int x, int y) { this.x = x; this.y = y; } public override string ToString() { return "(" + x + ", " + y + ")"; } } class A { static void Main() { Console.WriteLine($"Point is {new Point()}"); Console.WriteLine($"Point is {new Point(0,0)}"); } }Point is (0, 0) Point is (0, 0)las instrucciones crean un
Pointconxeyinicializado en cero, que en el caso de la llamada al constructor de instancia sin parámetros, puede ser sorprendente, ya que ambos campos de instancia tienen inicializadores, pero no se ejecutan.ejemplo final
No se permite que un constructor de instancia de estructura incluya un inicializador de constructor del formulario base(argument_list), donde argument_list es opcional. La ejecución de un constructor de instancia no dará lugar a la ejecución de un constructor en el tipo System.ValueTypebase de la estructura .
El this parámetro de un constructor de instancia de estructura corresponde a un parámetro de salida del tipo de estructura. Por lo tanto, this deberán asignarse definitivamente (§ 9.4) en cada ubicación donde regrese el constructor. De manera similar, no se puede leer (ni siquiera de forma implícita) en el cuerpo del constructor antes de que se le asigne un valor definitivamente.
Si el constructor de la instancia de la estructura especifica un inicializador de constructor, ese inicializador se considera una asignación definitiva a this que ocurre antes del cuerpo del constructor. Por lo tanto, el propio cuerpo no tiene requisitos de inicialización.
Los campos de instancia (distintos fixed de los campos) se asignarán definitivamente en constructores de instancia de estructura que no tengan un this() inicializador.
Ejemplo: considere la implementación del constructor de instancia siguiente:
struct Point { int x, y; public int X { set { x = value; } } public int Y { set { y = value; } } public Point(int x, int y) { X = x; // error, this is not yet definitely assigned Y = y; // error, this is not yet definitely assigned } }No se puede llamar a ningún miembro de función de instancia (incluidos los descriptores de acceso set para las propiedades
XyY) hasta que se hayan asignado definitivamente todos los campos de la estructura que se construye. Sin embargo, tenga en cuenta que, siPointfuera una clase en lugar de una estructura, se permitiría la implementación del constructor de instancia. Hay una excepción a esto y eso implica las propiedades implementadas automáticamente (§15.7.4). Las reglas de asignación definitivas (§12.24.2) excluyen específicamente la asignación a una propiedad automática de un tipo de estructura dentro de un constructor de instancia de ese tipo de estructura: dicha asignación se considera una asignación definitiva del campo de respaldo oculto de la propiedad automática. Por lo tanto, se permite lo siguiente:struct Point { public int X { get; set; } public int Y { get; set; } public Point(int x, int y) { X = x; // allowed, definitely assigns backing field Y = y; // allowed, definitely assigns backing field } }ejemplo final]
16.5.10 Constructores estáticos
Los constructores estáticos para estructuras siguen la mayoría de las mismas reglas que para las clases. La ejecución de un constructor estático para un tipo de estructura se desencadena mediante el primero de los siguientes eventos que se producirán dentro de un dominio de aplicación:
- Se hace referencia a un miembro estático del tipo de estructura.
- Se llama a un constructor declarado explícitamente del tipo de estructura.
Nota: La creación de valores predeterminados (§16.5.5) de tipos de estructura no desencadena el constructor estático. (Un ejemplo de esto es el valor inicial de los elementos de una matriz). nota final
16.5.11 Propiedades
Un property_declaration (§15.7.1) para una propiedad de instancia de un struct_declaration puede contener el property_modifierreadonly. Sin embargo, una propiedad estática no contendrá ese modificador.
Es un error en tiempo de compilación intentar modificar el estado de una variable de estructura de instancia a través de una propiedad de solo lectura declarada en esa estructura.
Es un error en tiempo de compilación que una propiedad implementada automáticamente tenga un modificador readonly y también un descriptor de acceso set.
Es un error de tiempo de compilación que una propiedad implementada automáticamente en un readonly struct tenga un set accesor.
Una propiedad implementada automáticamente declarada dentro de un readonly struct no necesita tener un readonly modificador, ya que se supone que su get descriptor de acceso es de solo lectura.
Es un error en tiempo de compilación tener un modificador readonly en la propiedad misma, así como en cualquiera de sus descriptores de acceso get o set.
Es un error en tiempo de compilación que una propiedad tenga un modificador de solo lectura en todos sus accesores.
Nota: Para corregir el error, mueva el modificador de los accesores a la propia propiedad. nota final
Para una expresión de descriptor de acceso de propiedad, s.P:
- Se trata de un error en tiempo de compilación si
s.Pinvoca el descriptor de accesoMset de tipoTcuando el proceso de §12.6.6.1 crearía una copia temporal des. - Si
s.Pinvoca el descriptor de acceso get de tipoT, se sigue el proceso de §12.6.6.1 , incluida la creación de una copia temporal dessi es necesario.
Las propiedades implementadas automáticamente (§15.7.4) usan campos de respaldo ocultos, que solo son accesibles para los descriptores de acceso de propiedad.
Nota: Esta restricción de acceso significa que los constructores de estructuras que contienen propiedades implementadas automáticamente a menudo necesitan un inicializador de constructor explícito donde, de lo contrario, no necesitarían uno, para satisfacer el requisito de que se asignen definitivamente todos los campos antes de que se invoque a cualquier miembro de función o el constructor devuelva. nota final
Métodos 16.5.12
Un method_declaration (§15.6.1) para un método de instancia de un struct_declaration puede contener el method_modifierreadonly. Sin embargo, un método estático no contendrá ese modificador.
Se trata de un error en tiempo de compilación intentar modificar el estado de una variable de instancia de estructura a través de un método de solo lectura declarado en esa estructura.
Aunque un método readonly puede llamar a un método hermano no readonly, o a un accesor get de propiedad o indizador, esto da como resultado la creación de una copia implícita de this como medida defensiva.
Un método readonly puede llamar a una propiedad del mismo nivel o un descriptor de acceso del conjunto de indizadores de solo lectura. Si un accesor de un miembro hermano no es explícita o implícitamente de solo lectura, se produce un error de compilación.
Todas las method_declaration de un método parcial deben tener un readonly modificador, o ninguna de ellas debe tenerlo.
Indexadores 16.5.13
Un indexer_declaration (§15.9) para un indexador de instancia de un struct_declaration puede contener el indexer_modifierreadonly.
Se trata de un error en tiempo de compilación para intentar modificar el estado de una variable de estructura de instancia a través de un indizador de solo lectura declarado en ese struct.
Es un error en tiempo de compilación tener un readonly modificador en un indexador, así como en cualquiera de sus accesores get o set.
Es un error en tiempo de compilación que un indexador tenga un modificador de solo lectura en todos sus accesores.
Nota: Para corregir el error, mueva el modificador de los accesores al propio indexador. nota final
16.5.14 Eventos
Un event_declaration (§15.8.1) para un evento que no sea de campo en un struct_declaration puede contener el event_modifierreadonly. Sin embargo, un evento estático no contendrá ese modificador.
16.5.15 Restricción de contexto seguro
16.5.15.1 General
En tiempo de compilación, cada expresión está asociada a un contexto en el que se puede acceder a esa instancia y a todos sus campos de forma segura, su contexto seguro. El contexto seguro es un contexto que encierra una expresión y al cual es seguro que el valor pueda escapar.
Cualquier expresión cuyo tipo de compilación no sea una estructura de referencia tiene un contexto seguro de contexto del llamante.
A default La expresión, para cualquier tipo, tiene un contexto seguro del contexto del llamante.
Para cualquier expresión que no sea una expresión predeterminada cuyo tipo de compilación sea una estructura de referencia, se define un contexto seguro según las siguientes secciones.
Los registros de contexto seguro indican en qué contexto se puede copiar un valor. Dada una asignación de una expresión E1 con un contexto S1seguro , a una expresión E2 con contexto S2seguro , se produce un error si S2 es un contexto más amplio que S1.
Hay tres valores de contexto seguro diferentes, los mismos que los valores ref-safe-context definidos para las variables de referencia (§9.7.2): declaration-block, function-member y caller-context. El contexto seguro de una expresión restringe su uso de la siguiente manera:
- Para una declaración de devolución
return e1, el contexto seguro dee1deberá ser caller-context. - Para una asignación
e1 = e2, el contexto seguro dee2debe ser al menos tan amplio como contexto seguro dee1.
Para la invocación de un método, si existe un argumento ref o out de un tipo ref struct (incluido el receptor a menos que el tipo sea readonly) con contexto seguro S1, ningún argumento (incluido el receptor) puede tener un contexto seguro más estrecho que S1.
16.5.15.2 Contexto seguro del parámetro
Un parámetro de un tipo de estructura de referencia, incluido el this parámetro de un método de instancia, tiene un contexto seguro de caller-context.
16.5.15.3 Contexto seguro de variables locales
Una variable local de un tipo de estructura ref tiene un contexto seguro como se indica a continuación:
- Si la variable es una variable de iteración de un
foreachbucle, el contexto seguro de la variable es el mismo que el contexto seguro de laforeachexpresión del bucle. - De lo contrario, si la declaración de la variable tiene un inicializador, el contexto seguro de la variable es el mismo que el contexto seguro de ese inicializador.
- De lo contrario, la variable no está inicializada en el punto de declaración y tiene un contexto seguro de caller-context.
16.5.15.4 Contexto seguro del campo
Una referencia a un campo e.F, donde el tipo de F es un tipo de estructura ref, tiene un contexto seguro que es el mismo que el contexto seguro de e.
16.5.15.5 Operadores
La aplicación de un operador definido por el usuario se trata como una invocación de método (§16.5.15.6).
Para un operador que produce un valor, como e1 + e2 o c ? e1 : e2, el contexto seguro del resultado es el contexto más estrecho entre los contextos seguros de los operandos del operador. Como consecuencia, para un operador unario que produce un valor, como +e, el contexto seguro del resultado es el contexto seguro del operando.
Nota: El primer operando de un operador condicional es ,
boolpor lo que su contexto seguro es el contexto del autor de la llamada. De ello se deduce que el contexto seguro resultante es el contexto seguro más restringido del segundo y tercer operando. nota final
16.5.15.6 Invocación de método y propiedad
Un valor resultante de una invocación e1.M(e2, ...) de método o invocación e.P de propiedad tiene un contexto seguro correspondiente al menor de los siguientes contextos:
- contexto del autor de la llamada.
- Contexto seguro de todas las expresiones de argumento (incluido el receptor).
Una invocación de propiedad (ya sea get o set) se trata como una invocación de método del método subyacente mediante las reglas anteriores.
16.5.15.7 stackalloc
El resultado de una expresión stackalloc tiene un contexto seguro de función miembro.
16.5.15.8 Invocaciones del constructor
Una new expresión que invoca a un constructor cumple las mismas reglas que una invocación de método que se considera que devuelve el tipo que se va a construir.
Además, el contexto seguro es el mínimo entre los contextos seguros de todos los argumentos y operandos de todas las expresiones de inicialización de objeto, de manera recursiva, si hay presente algún inicializador.
Nota: Estas reglas se basan en
Span<T>no tener un constructor de la forma siguiente:public Span<T>(ref T p)Este constructor hace que las instancias de
Span<T>se usen como campos indistinguibles de unrefcampo. Las reglas de seguridad descritas en este documento dependen derefcampos que no sean una construcción válida en C# o .NET. nota final
ECMA C# draft specification