Compartir a través de


16 Estructuras

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.7) que declara una nueva estructura:

struct_declaration
    : attributes? struct_modifier* 'ref'? 'partial'? 'struct'
      identifier type_parameter_list? struct_interfaces?
      type_parameter_constraints_clause* struct_body ';'?
    ;

Una declaración de estructura consiste en un conjunto opcional de atributos (§22), seguido de un conjunto opcional de modificadores de estructura(§ 16.2.2), seguido de una opción opcional ref modificador (§ 16.2.3), seguido de un modificador parcial opcional (§ 15.2.7), seguido de la palabra clave struct y un identificador el nombre de la estructura, seguido de una opción opcional lista_de_parámetros_de_tipo especificación (§ 15.2.3), seguido de una opción opcional struct_interfaces especificación (§ 16.2.5), seguido de una opción opcional cláusulas de restricciones de parámetros de tipo especificación (§15.2.5), seguido de un struct_body (§16.2.6), opcionalmente seguido de un punto y coma.

Una declaración de estructura no debe proporcionar type_parameter_constraints_clausea menos que también proporcione una type_parameter_list.

Una declaración de estructura 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).

Una declaración de estructura que incluye un ref la palabra clave no debe tener un struct_interfaces parte.

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 (§23.2) solo está disponible en código no seguro (§23).

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 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.4.15). Las reglas para determinar el contexto seguro de una estructura ref se describen en §16.4.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 ref modificador .
  • Al ser encapsulado en System.ValueType o System.Object.
  • Como argumento de tipo.
  • Como el tipo de un elemento de tupla.
  • Un método asincrónico.
  • Iterador.
  • No hay ninguna conversión de un ref struct tipo al tipo object o al tipo System.ValueType.
  • Un tipo ref struct no deberá ser declarado para implementar ninguna interfaz.
  • Un método de instancia declarado en object o en System.ValueType pero no sobrescrito en un ref struct Este tipo no se debe llamar con un receptor de ese tipo ref struct tipo.
  • Un método de instancia de un ref struct tipo no se capturará mediante la conversión de grupo de métodos a un tipo delegado.
  • Una estructura ref no se capturará mediante una expresión lambda ni una función local.

Nota: Un ref struct no declarará async métodos de instancia ni usará una yield return instrucción o yield break dentro de un método de instancia, ya que el parámetro implícito this no 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 §18.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

Los miembros de una estructura constan de los miembros introducidos por sus struct_member_declarations y los miembros heredados del tipo System.ValueType.

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 (§23.8.2) solo está disponible en código no seguro (§23).

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.4, las descripciones de los miembros de clase proporcionados en §15.3 a §15.12 también se aplican a los miembros struct.

16.4 Diferencias de clase y estructura

16.4.1 General

Las estructuras difieren de las clases de varias maneras importantes:

  • Las estructuras son tipos de valor (§16.4.2).
  • Todos los tipos de estructura heredan implícitamente de la clase System.ValueType (§16.4.3).
  • La asignación a una variable de un tipo de estructura crea una copia del valor que se asigna (§16.4.4).
  • El valor predeterminado de un struct es el valor generado estableciendo todos los campos en su valor predeterminado (§16.4.5).
  • Las operaciones de conversión a y desde una estructura se utilizan para realizar conversiones entre un tipo de estructura y ciertos tipos de referencia (§ 16.4.6).
  • El significado de this es diferente dentro de los miembros de estructura (§16.4.7).
  • Las declaraciones de campo de instancia de un struct no pueden incluir inicializadores de variables (§16.4.8).
  • No se permite que un struct declare un constructor de instancia sin parámetros (§16.4.9).
  • 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.4.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 Node contiene un campo de instancia de su propio tipo. Otro ejemplo

struct A { B b; }
struct B { C c; }
struct C { A a; }

es un error porque cada uno de los tipos A, By C dependen 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 de a para b crea una copia del valor y b , por tanto, no se ve afectada por la asignación a a.x. En su lugar, se había Point declarado como una clase, la salida sería 100 porque a y b haría referencia al mismo objeto.

ejemplo final

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

Puesto 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.4.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.21.2.

16.4.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 Point estructura declarada anteriormente, el ejemplo

Point[] a = new Point[100];

inicializa cada Point en la matriz al valor producido estableciendo los campos x y y en cero.

ejemplo final

El valor predeterminado de una estructura corresponde al valor devuelto por el constructor predeterminado de la estructura (§8.3.3). A diferencia de una clase, no se permite que un struct declare un constructor de instancia sin parámetros. En su lugar, cada estructura tiene implícitamente un constructor de instancia sin parámetros, que siempre devuelve el valor resultante de establecer todos los campos en sus valores predeterminados.

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 null cuando se llama explícitamente. En los casos en los que una KeyValuePair variable está sujeta a la inicialización del valor predeterminado, los key campos y value serán nully la estructura debe estar preparada para controlar este estado.

nota final

16.4.6 Conversión a y desde primitivas

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. struct no se reflejan en el recuadro struct. nota final

Para obtener más información sobre boxing y unboxing, consulte §10.2.9 y §10.3.7.

16.4.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
3

Aunque no es recomendable que ToString tenga efectos secundarios, el ejemplo demuestra que no se produjo ninguna conversión a objeto para las tres invocaciones de x.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 Increment modifica el valor de la variable x. Esto no equivale a la segunda llamada a Increment, que modifica el valor en una copia encapsulada de x. Por lo tanto, la salida del programa es:

0
1
1

ejemplo final

Inicializadores de campo 16.4.8

Como se describe en §16.4.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. Por este motivo, una estructura no permite que las declaraciones de campo de instancia incluyan inicializadores de variables. Esta restricción solo se aplica a los campos de instancia. Los campos estáticos de una estructura pueden incluir inicializadores de variables.

Ejemplo: lo siguiente

struct Point
{
    public int x = 1; // Error, initializer not permitted
    public int y = 1; // Error, initializer not permitted
}

se produce un error porque las declaraciones de campo de instancia incluyen inicializadores de variables.

ejemplo final

Una field_declaration declarada directamente dentro de un struct_declaration que tenga el struct_modifierreadonly tendrá el field_modifierreadonly.

16.4.9 Constructores

A diferencia de una clase, no se permite que un struct declare un constructor de instancia sin parámetros. En su lugar, cada estructura tiene implícitamente un constructor de instancia sin parámetros, 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). Un struct puede declarar constructores de instancia que tienen parámetros.

Ejemplo: dado lo siguiente

struct Point
{
    int x, y;

    public Point(int x, int y) 
    {
        this.x = x;
        this.y = y;
    }
}

class A
{
    static void Main()
    {
        Point p1 = new Point();
        Point p2 = new Point(0, 0);
    }
}

las instrucciones crean un Point con x y y inicializados en cero.

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.

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.

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 X y Y) hasta que se hayan asignado definitivamente todos los campos de la estructura que se construye. Sin embargo, tenga en cuenta que, si Point fuera 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.21.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.4.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.4.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.4.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

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.4.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.4.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.4.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.4.15 Restricción de contexto seguro

16.4.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 de e1 deberá ser caller-context.
  • Para una asignación e1 = e2 , el contexto seguro de e2 debe ser al menos tan amplio como contexto seguro de e1.

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.4.15.2 Contexto seguro de parámetros

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.4.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 foreach bucle, el contexto seguro de la variable es el mismo que el contexto seguro de la foreach expresió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.4.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.4.15.5 Operadores

La aplicación de un operador definido por el usuario se trata como una invocación de método (§16.4.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.4.15.6 Invocación de los métodos y las propiedades

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.4.15.7 stackalloc

El resultado de una expresión stackalloc tiene un contexto seguro de función miembro.

16.4.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 un ref campo. Las reglas de seguridad descritas en este documento dependen de ref campos que no sean una construcción válida en C# o .NET. nota final