Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
16.1 Generalidades
Structs são semelhantes a classes na medida em que representam estruturas de dados que podem conter membros de dados e membros de funções. No entanto, ao contrário das classes, as structs são tipos de valor e não requerem alocação na heap. Uma variável de um struct tipo contém diretamente os dados do struct, enquanto uma variável de um tipo de classe contém uma referência aos dados, este último conhecido como um objeto.
Nota: As estruturas são particularmente úteis para pequenas estruturas de dados que têm semântica de valor. Números complexos, pontos em um sistema de coordenadas ou pares chave-valor em um dicionário são bons exemplos de estruturas. A chave para essas estruturas de dados é que elas têm poucos membros de dados, que não exigem o uso de herança ou semântica de referência, em vez disso, podem ser convenientemente implementadas usando semântica de valor onde a atribuição copia o valor em vez da referência. Nota final
Conforme descrito no §8.3.5, os tipos simples fornecidos pelo C#, como int, doublee bool, são, na verdade, todos os tipos struct.
16.2 Declarações de estrutura
16.2.1 Generalidades
Uma struct_declaration é uma type_declaration (§14.8) que declara uma nova struct:
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 ';'?
| ';'
;
Uma struct_declaration é para uma estrutura não-registo ou para uma estrutura de registo.
Um non_record_struct_declaration consiste num conjunto opcional de atributos (§23), seguido por um conjunto opcional de struct_modifiers (§16.2.2), seguido por um modificador opcional ref (§16.2.3), seguido por um modificador parcial opcional (§15.2.7), seguido pela palavra-chave struct e um identificador que nomeia a estrutura, seguido por uma especificação type_parameter_list opcional (§15.2.3), seguida de uma especificação opcional de struct_interfaces (§16.2.5), seguida de uma especificação opcional de cláusulas de type_parameter_constraints (§15.2.5), seguida de uma struct_body (§16.2.6), opcionalmente seguida de um ponto e vírgula.
Um record_struct_declaration consiste num conjunto opcional de atributos (§23), seguido por um conjunto opcional de struct_modifiers (§16.2.2), seguido por um modificador parcial opcional (§15.2.7), seguido pela palavra-chave record, seguido pela palavra-chave struct e um identificador que nomeia a estrutura, seguido por uma especificação opcional de type_parameter_list (§15.2.3), seguido por uma delimited_parameter_list opcionalespecificação (§15.2.1), seguida de uma especificação opcional de struct_interfaces (§16.2.5), seguida de uma especificação opcional de cláusulas de type_parameter_constraints (§15.2.5), seguida de uma record_struct_body.
Um struct_declaration não deve fornecer type_parameter_constraints_clausea menos que também forneça um type_parameter_list.
Uma struct_declaration que fornece um type_parameter_list é uma declaração de estrutura genérica. Além disso, qualquer struct aninhado dentro de uma declaração de classe genérica ou uma declaração de struct genérica ela própria constitui uma declaração de struct genérica, uma vez que os argumentos de tipo para o tipo que contém devem ser fornecidos para criar um tipo construído (§8.4).
Um non_record_struct_declaration que inclua um ref modificador não terá uma struct_interfaces parte.
Um record_struct_declaration ter um delimited_parameter_list declara um registo posicional como constitucto.
No máximo, apenas um record_struct_declaration contendo partial pode fornecer um delimited_parameter_list.
Os parâmetros em delimited_parameter_list não devem ter ref, out nem this modificadores; no entanto, in e params modificadores são permitidos.
Para um record_struct_declaration, os record_struct_bodys {}, {};, e ; são equivalentes. Todos indicam que os únicos membros são aqueles sintetizados pelo compilador (§16.4).
16.2.2 Modificadores de estrutura
Um struct_declaration pode, opcionalmente, incluir uma sequência de struct_modifiers:
struct_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| 'readonly'
| unsafe_modifier // unsafe code support
;
unsafe_modifier (§24.2) só está disponível em código não seguro (§24).
É um erro em tempo de compilação para o mesmo modificador aparecer várias vezes em uma declaração struct.
Com exceção de readonly, os modificadores de uma declaração de struct têm o mesmo significado que os de uma declaração de classe (§15.2.2).
O readonly modificador indica que o struct_declaration declara um tipo cujas instâncias são imutáveis.
Uma struct somente leitura tem as seguintes restrições:
- Cada um dos seus campos de instância também deve ser declarado
readonly. - Não deverá declarar quaisquer eventos do tipo campo (§15.8.2).
**
Quando uma instância de uma struct de readonly é passada para um método, essa instância this é tratada como um argumento/parâmetro de entrada, que não permite acesso de gravação a quaisquer campos de instância, a menos que o acesso seja feito por construtores.
16.2.3 Modificador de ref
O ref modificador indica que o non_record_struct_declaration declara um tipo cujas instâncias são alocadas na pilha de execução. Esses tipos são chamados tipo ref struct. O ref modificador declara que as instâncias podem conter campos do tipo ref, e não devem ser copiadas do seu contexto seguro (§16.5.15). As regras para determinar o contexto seguro de uma ref struct estão descritas no §16.5.15.
É um erro em tempo de compilação se um tipo ref struct for usado em qualquer um dos seguintes contextos:
- Como o tipo de elemento de uma matriz.
- Como o tipo declarado de um campo de uma classe ou uma estrutura que não tem o
refmodificador. - Como um argumento de tipo.
- Como o tipo de um elemento de tupla.
- Num método assíncrono.
- Num iterador.
- Como tipo de recetor para uma conversão de grupo de métodos de um método de instância para um tipo de delegado.
- Como variável capturada numa expressão lambda ou numa função local.
Além disso, aplicam-se as seguintes restrições a um ref struct tipo:
- Um
ref structtipo não deve ser encaixado emSystem.ValueTypeouSystem.Object. - Um
ref structtipo não deve ser declarado para implementar qualquer interface. - Um método de instância declarado em
objectou emSystem.ValueType, mas não substituído num tiporef struct, não deve ser chamado com um recetor desse tiporef struct.
Nota: Um
ref structnão deve declarar métodos de instânciaasyncnem utilizar uma instruçãoyield returnouyield breakdentro de um método de instância, porque o parâmetro implícitothisnão pode ser usado nesses contextos. Nota final
Essas restrições garantem que uma variável do tipo ref struct não se refira à memória da pilha que não é mais válida ou a variáveis que não estão mais válidas.
16.2.4 Modificador parcial
O partial modificador indica que esse struct_declaration é uma declaração de tipo parcial. Várias declarações struct parciais com o mesmo nome dentro de um namespace ou declaração de tipo anexa combinam-se para formar uma declaração struct, seguindo as regras especificadas no §15.2.7.
16.2.5 Interfaces Struct
Uma declaração struct pode incluir uma especificação struct_interfaces , caso em que se diz que a struct implementa diretamente os tipos de interface fornecidos. Para um tipo struct construído, incluindo um tipo aninhado declarado dentro de uma declaração de tipo genérico (§15.3.9.7), obtém-se cada tipo de interface implementado substituindo, em cada type_parameter na interface dada, o type_argument correspondente do tipo construído.
struct_interfaces
: ':' interface_type_list
;
O tratamento de interfaces em várias partes de uma declaração struct parcial (§15.2.7) é discutido mais pormenorizadamente no §15.2.4.3.
As implementações de interface são discutidas mais detalhadamente no §19.6.
16.2.6 Corpo estrutural
O struct_body de um struct define os membros do struct.
struct_body
: '{' struct_member_declaration* '}'
;
16.3 Membros da estrutura
16.3.1 Geral
Os membros de uma estrutura são compostos pelos elementos introduzidos pelas suas struct_member_declarations e pelos elementos herdados do tipo System.ValueType. Para um registo de registo, o conjunto de membros inclui também os membros sintetizados gerados pelo 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) só está disponível em código não seguro (§24).
Nota: Todos os tipos de class_member_declarations, exceto finalizer_declaration, são também struct_member_declarations. Nota final
Exceto pelas diferenças referidas no §16.5, as descrições dos membros da classe fornecidas nos §15.3 até ao §15.12 aplicam-se também aos membros do struct.
É um erro que um campo de instância de uma estrutura de registo tenha um tipo inseguro.
16.3.2 Membros apenas de leitura
Uma definição de membro de instância ou acessório de uma propriedade de instância, indexador ou evento que inclua o readonly modificador tem as seguintes restrições:
- O
thisparâmetro é umaref readonlyreferência. - O membro não deve reatribuir o valor ou
thisum campo de instância do recetor. - O membro não deve reatribuir o valor de um evento semelhante a um campo de instância (§15.8.2) do recetor.
- Se um membro apenas de leitura invocar um membro não apenas de leitura, a estrutura referida por
thisdeve ser copiada para usar uma referência gravável para othisargumento.
Nota: Os campos de instância incluem o campo de apoio oculto usado para propriedades implementadas automaticamente (§15.7.4). Nota final
Exemplo: Um membro só de leitura pode modificar o estado de um objeto referido por um campo de instância, mesmo que o membro apenas de leitura não possa reatribuir esse membro da instância. O código seguinte demonstra a reatribuição e modificação de um campo de instância:
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); } }O
readonlymétodoAddMessagepode alterar o estado de uma lista de mensagens. OInitializeMessagesmembro pode limpar e reinicializar a lista de mensagens. No caso deAddMessage, oreadonlymodificador é válido. No caso deInitializeMessages, adicionar oreadonlymodificador é inválido. Exemplo final
16.4 Membros sintetizados da estrutura de discos
16.4.1 Generalidades
No caso de um registo de registo, os membros são sintetizados a menos que um membro com uma assinatura "correspondente" seja declarado no record_struct_body ou que seja herdado um membro concreto não virtual acessível com uma assinatura "correspondente". Dois membros são considerados compatíveis se tiverem a mesma assinatura ou seriam considerados "escondidos" num cenário de herança. (Ver Assinaturas e sobrecarga §7.6.)
Os membros sintetizados são descritos nas suborações seguintes.
16.4.2 Membros da Igualdade
Os membros de igualdade sintetizada são semelhantes aos de uma classe de registo (§15.16.2), exceto pela ausência de EqualityContractverificações nulas ou herança.
Uma estrutura R de registos System.IEquatable<R> implementa e inclui uma sobrecarga sintetizada fortemente tipada de Equals(R other), que é pública, da seguinte forma:
public readonly bool Equals(R other);
Este método pode ser declarado explicitamente. No entanto, é um erro se a declaração explícita não corresponder à assinatura ou acessibilidade esperada.
Se Equals(R other) for definido pelo utilizador (isto é, não sintetizado) mas GetHashCode não for, será produzido um aviso.
A sintetizada Equals(R) deverá devolver true se e somente se, para cada campo fieldN de instância na estrutura de registo, o valor de System.Collections.Generic.EqualityComparer<TN>.Default.Equals(fieldN, other.fieldN), onde TN é o tipo de campo, é true.
A estrutura de registos inclui operadores sintetizados == e != equivalentes aos operadores declarados da seguinte forma:
public static bool operator==(R r1, R r2) => r1.Equals(r2);
public static bool operator!=(R r1, R r2) => !(r1 == r2);
O Equals método chamado pelo == operador é o Equals(R other) método especificado acima. O != operador delega ao == operador. É um erro se os operadores forem declarados explicitamente.
A estrutura de registos inclui um override sintetizado equivalente a um método declarado da seguinte forma:
public override readonly bool Equals(object? obj);
É um erro se a sobreposição for declarada explicitamente. A sobreposição sintetizada deverá retornar other is R temp && Equals(temp) onde R é o registo de registo.
A estrutura de registos inclui um override sintetizado equivalente a um método declarado da seguinte forma:
public override readonly int GetHashCode();
Este método pode ser declarado explicitamente.
Um aviso deve ser reportado se um dos Equals(R) e GetHashCode() for explicitamente declarado, mas o outro método não.
A sobreposição sintetizada de GetHashCode() deverá devolver um int resultado da combinação dos valores de System.Collections.Generic.EqualityComparer<TN>.Default.GetHashCode(fieldN) para cada campo fieldN de instância com TN sendo o tipo de fieldN.
Exemplo: Considere a seguinte estrutura de registo:
record struct R1(T1 P1, T2 P2);Para isto, os membros da igualdade sintetizada seriam algo como:
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));Exemplo final
16.4.3 Membros da impressão
Uma estrutura de registos inclui um método sintetizado equivalente ao seguinte:
private bool PrintMembers(System.Text.StringBuilder builder);
Este método executa as seguintes tarefas:
- Para cada um dos membros imprimíveis do registo (campo público não estático e membros de propriedades legíveis), acrescenta o nome desse membro seguido de "
=" seguido do valor do membro separado por ", “, - Retorna verdadeiro se a estrutura de registos tiver membros imprimíveis.
Para um membro que tenha um tipo de valor, o seu valor deve ser convertido numa representação de cadeia.
Se os membros imprimíveis do registo não incluírem uma propriedade legível com um não-acessorreadonlyget , então o sintetizado PrintMembers é readonly. Não é necessário que os campos do registo sejam readonly para que o PrintMembers método seja readonly.
O PrintMembers método pode ser declarado explicitamente. No entanto, é um erro se a declaração explícita não corresponder à assinatura ou acessibilidade esperada.
A estrutura de discos inclui um método sintetizado equivalente ao seguinte:
public override string ToString();
Se o método do PrintMembers registo de struct for readonly, então o método sintetizado ToString() será readonly.
Este método pode ser declarado explicitamente. É um erro se a declaração explícita não corresponder à assinatura ou acessibilidade esperada.
Este método executa as seguintes tarefas:
- Cria uma
StringBuilderinstância, - Acrescenta o nome da estrutura de registo ao construtor, seguido de "
{", - Invoca o método do
PrintMembersregisto struct, dando-lhe o construtor, seguido de "" se devolver verdadeiro, - Acrescenta "
}", - Devolve o conteúdo do construtor com
builder.ToString().
Exemplo: Considere a seguinte estrutura de registo:
record struct R1(T1 P1, T2 P2);Para esta estrutura de discos, os membros sintetizados da impressão seriam algo como:
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(); } }Exemplo final
16.4.4 Membros da estrutura de registo posicional
16.4.4.1 Geral
Para além de fornecer os membros descritos nas subcláusulas anteriores, as estruturas de registo posicional (§16.2.1) sintetizam membros adicionais com as mesmas condições que os outros membros, conforme descrito nas subcláusulas seguintes.
16.4.4.2 Construtor principal
Uma estrutura de registo tem um construtor público cuja assinatura corresponde aos parâmetros de valor da declaração do tipo. Isto é chamado de construtor primário para o tipo. É um erro ter um construtor primário e um construtor com a mesma assinatura já presentes no struct. Se a declaração de tipo não incluir um delimited_parameter_list, não é gerado nenhum construtor primário.
record struct R1 { public R1() { } // OK } record struct R2() { public R2() { } // error: 'R2' already defines // a constructor with the same parameter types }
Declarações de campos de instância para uma estrutura de registo podem incluir inicializadores de variáveis. Se não houver construtor primário, os inicializadores de instância executam-se como parte do construtor sem parâmetros. Caso contrário, em tempo de execução o construtor primário executa os inicializadores de instância que aparecem no registo-struct-corpo.
Se uma estrutura de registos tiver um construtor primário, qualquer construtor definido pelo utilizador terá um inicializador explícito this que chama o construtor primário ou um construtor explicitamente declarado.
Os parâmetros do construtor primário, bem como os membros da estrutura de registo, estão dentro do âmbito dos inicializadores dos campos ou propriedades da instância. Os membros da instância seriam um erro nessas localizações, mas os parâmetros do construtor primário estariam dentro do âmbito e utilizáveis, e fariam sombra de membros. Elementos estáticos também seriam utilizáveis.
Um aviso deve ser produzido se um parâmetro do construtor primário não for lido.
As regras de atribuição definitiva para construtores de instâncias de struct aplicam-se ao construtor primário de structs de registo. Por exemplo, o seguinte é um erro:
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 Propriedades
Para cada parâmetro de um delimited_parameter_list que tenha o mesmo nome e tipo que um campo de instância explicitamente declarado, o restante desta subcláusula não se aplica.
Para cada parâmetro de struct de registo de um delimited_parameter_list existe um membro correspondente de propriedade pública cujo nome e tipo são retirados da declaração do parâmetro de valor.
Para um registo:
Uma propriedade pública
geteinitauto-propriedade é criada se a estrutura de registo tiver umreadonlymodificador,getesetcaso contrário. Ambos os tipos de acessórios de conjunto (seteinit) são considerados "emparelhados". Assim, o utilizador pode declarar uma propriedade apenas init em vez de uma propriedade mutável sintetizada.Uma propriedade herdada
abstractcom tipo correspondente é sobreposta.Não é criada nenhuma auto-propriedade se a estrutura de registo tiver um campo de instância com nome e tipo esperados.
É um erro se a propriedade herdada não tiver
publicacessóriosset/init.getÉ um erro se a propriedade ou campo herdado estiver oculto.
A auto-propriedade é inicializada ao valor do correspondente parâmetro construtor primário.
Os atributos podem ser aplicados à auto-propriedade sintetizada e ao seu campo de apoio usando
property:oufield:targets para atributos aplicados sintaticamente ao correspondente parâmetro struct de registo.
16.4.4.4 Desconstrução
Uma estrutura de registo posicional com pelo menos um parâmetro sintetiza um método público voidde instância -return, chamado Deconstruct com uma declaração do parâmetro out para cada parâmetro da declaração do construtor primário. Cada parâmetro de Deconstruct tem o mesmo tipo que o parâmetro correspondente da declaração do construtor primário. O corpo do método atribui cada parâmetro do método Deconstruct ao valor de acesso de um membro da instância a um membro com o mesmo nome.
Se os membros da instância acedidos no corpo não incluírem uma propriedade com um não-acessorreadonlyget , então o método sintetizado Deconstruct é readonly.
O método pode ser declarado explicitamente. É um erro se a declaração explícita não corresponder à assinatura ou acessibilidade esperada, ou se for estática.
16.5 Diferenças entre classes e estruturas
16.5.1 Geral
As estruturas diferem das classes de várias maneiras importantes:
- As estruturas são tipos de valor (§16.5.2).
- Todos os tipos de struct herdam implicitamente da classe
System.ValueType(§16.5.3). - A atribuição a uma variável de um tipo struct cria uma cópia do valor atribuído (§16.5.4).
- O valor padrão de uma estrutura é o valor produzido ao definir todos os campos para o seu valor padrão (§16.5.5).
- As operações de boxing e unboxing são usadas para converter entre um tipo de struct e certos tipos de referência (§16.5.6).
- O significado de
thisé diferente dentro dos membros da struct (§16.5.7). - Um struct não tem permissão para declarar um finalizador.
- Declarações de eventos, declarações de propriedade, acessores de propriedade, declarações de indexador e declarações de método têm permissão para ter o modificador
readonly, embora isso geralmente não seja permitido para esses mesmos tipos de membros em classes.
16.5.2 Semântica de valores
Structs são tipos de valor (§8.3) e são consideradas como tendo semântica de valor. As classes, por outro lado, são tipos de referência (§8.2) e dizem ter semântica de referência.
Uma variável de um tipo struct contém diretamente os dados do struct, enquanto uma variável de um tipo de classe contém uma referência a um objeto que contém os dados. Quando um struct B contém um campo de instância do tipo A e A é um tipo struct, é um erro de compilação se A depender de B ou de um tipo construído a partir de B. Um struct Xdepende diretamente de um struct Y se X contém um campo de instância do tipo Y. Dada esta definição, o conjunto completo de structs do qual uma struct depende é o fecho transitivo da relação depende diretamente de.
Exemplo:
struct Node { int data; Node next; // error, Node directly depends on itself }é um erro porque
Nodecontém um campo de instância de seu próprio tipo. Outro exemplostruct A { B b; } struct B { C c; } struct C { A a; }é um erro porque cada um dos tipos
A,BeCdependem uns dos outros.Exemplo final
Com classes, é possível que duas variáveis façam referência ao mesmo objeto e, portanto, é possível que operações em uma variável afetem o objeto referenciado pela outra variável. Com structs, cada uma das variáveis tem sua própria cópia dos dados (exceto no caso de parâmetros por referência), e não é possível que as operações em uma afetem a outra. Além disso, exceto quando explicitamente anuláveis (§8.3.12), não é possível que valores de um tipo struct sejam null.
Nota: Se um struct contém um campo de tipo de referência, então o conteúdo do objeto referenciado pode ser alterado por outras operações. No entanto, o valor do campo em si, ou seja, a que objeto ele se refere, não pode ser alterado através de uma mutação de um valor struct diferente. Nota final
Exemplo: Dado o seguinte
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); } }A saída é
10. A atribuição deaparabcria uma cópia do valor eb, portanto, não é afetada pela atribuição aa.x. Em vez disso, tivessePointsido declarada como uma classe, a saída seria100porqueaebfaria referência ao mesmo objeto.Exemplo final
16.5.3 Herança
Todos os tipos de estruturas herdam implicitamente da classe System.ValueType, que, por sua vez, herda da classe object. Uma declaração struct pode especificar uma lista de interfaces implementadas, mas não é possível para uma declaração struct especificar uma classe base.
Os tipos de estrutura nunca são abstratos e são sempre implicitamente selados. Os modificadores abstract e sealed não são permitidos, portanto, numa declaração de estrutura.
Como a herança não é suportada para structs, a acessibilidade declarada de um membro de struct não pode ser protected, private protected, ou protected internal.
Os membros da função em uma struct não podem ser abstratos ou virtuais, e o override modificador só pode substituir métodos herdados de System.ValueType.
16.5.4 Atribuição
A atribuição a uma variável de um tipo struct cria uma cópia do valor que está sendo atribuído. Isso difere da atribuição a uma variável de um tipo de classe, que copia a referência, mas não o objeto identificado pela referência.
Semelhante a uma atribuição, quando uma struct é passada como um parâmetro value ou retornada como resultado de um membro da função, uma cópia da struct é criada. Um struct pode ser passado por referência a um membro da função usando um parâmetro by-reference.
Quando uma propriedade ou indexador de uma struct é o destino de uma atribuição, a expressão de instância associada à propriedade ou ao acesso do indexador deve ser classificada como uma variável. Se a expressão de instância for classificada como um valor, ocorrerá um erro em tempo de compilação. Isto é descrito com mais detalhe no §12.24.2.
16.5.5 Valores padrão
Conforme descrito no §9.3, vários tipos de variáveis são automaticamente inicializados para o seu valor padrão quando são criadas. Para variáveis de tipos de classe e outros tipos de referência, esse valor padrão é null. No entanto, como structs são tipos de valor que não podem ser null, o valor padrão de um struct é o valor produzido definindo todos os campos de tipo de valor como seu valor padrão e todos os campos de tipo de referência como null.
Exemplo: Referindo-se à estrutura declarada
Pointacima.Point[] a = new Point[100];Inicializa cada
Pointna matriz para o valor obtido ao definir os camposxeya zero.Exemplo final
O valor padrão de um struct corresponde ao valor retornado pelo construtor padrão do struct (§8.3.3). Quando uma struct não declara um construtor explícito de instância sem parâmetros, o construtor padrão é sintetizado e devolve sempre o valor que resulta de definir todos os campos para os seus valores predefinidos. A default expressão produz sempre o valor padrão inicializado a zero, mesmo quando uma estrutura declara um construtor explícito de instância sem parâmetros (§16.4.9).
Nota: Structs deve ser projetado para considerar o estado de inicialização padrão um estado válido. No exemplo
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; } }O construtor de instância definido pelo usuário protege contra
nullvalores somente quando é explicitamente chamado. Nos casos em que umaKeyValuePairvariável está sujeita à inicialização do valor padrão, oskeycampos evalueserãonull, e o struct deve estar preparado para lidar com esse estado.Nota final
16.5.6 Boxe e unboxing
Um valor de um tipo de classe pode ser convertido em tipo object ou em um tipo de interface que é implementado pela classe simplesmente tratando a referência como outro tipo em tempo de compilação. Da mesma forma, um valor de tipo object ou um valor de um tipo de interface pode ser convertido de volta para um tipo de classe sem alterar a referência (mas, é claro, uma verificação de tipo em tempo de execução é necessária neste caso).
Como structs não são tipos de referência, essas operações são implementadas de forma diferente para tipos struct. Quando um valor de um tipo struct é convertido em determinados tipos de referência (conforme definido no §10.2.9), ocorre uma operação de empacotamento. Da mesma forma, quando um valor de certos tipos de referência (conforme definido no §10.3.7) é convertido de volta para um tipo struct, ocorre uma operação de unboxing. Uma diferença importante em relação às mesmas operações em tipos de classe é que o boxing e o unboxing copiam o valor struct para dentro ou para fora da instância "encaixotada".
Nota: Assim, após uma operação de encapsulamento ou desembrulhar, as alterações feitas no objeto desembrulhado
structnão são refletidas no objeto encapsuladostruct. Nota final
Para mais detalhes sobre boxe e unboxing, ver §10.2.9 e §10.3.7.
16.5.7 Significado disto
O significado de this em uma estrutura difere do significado de this em uma classe, conforme descrito no §12.8.14. Quando um tipo struct substitui um método virtual herdado de System.ValueType (como Equals, GetHashCode, ou ToString), a invocação do método virtual por meio de uma instância do tipo struct não faz com que ocorra boxe. Isso é verdadeiro mesmo quando o struct é usado como um parâmetro type e a invocação ocorre por meio de uma instância do type parameter type.
Exemplo:
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>(); }A saída do programa é:
1 2 3Embora seja mau estilo para
ToStringter efeitos colaterais, o exemplo demonstra que nenhum boxe ocorreu para as três invocações dex.ToString().Exemplo final
Da mesma forma, o boxe nunca ocorre implicitamente ao acessar um membro em um parâmetro de tipo restrito quando o membro é implementado dentro do tipo de valor. Por exemplo, suponha que uma interface ICounter contenha um método Increment, que pode ser usado para modificar um valor. Se ICounter for usado como uma restrição, a implementação do método Increment é invocada com uma referência à variável em que Increment foi chamado, nunca uma cópia encaixotada.
Exemplo:
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>(); }A primeira chamada para
Incrementmodifica o valor na variávelx. Isso não é equivalente à segunda chamada paraIncrement, que modifica o valor em uma cópia encapsulada dox. Assim, a saída do programa é:0 1 1Exemplo final
16.5.8 Inicializadores de campo
Como descrito no §16.5.5, o valor padrão de uma estrutura consiste no valor que resulta de definir todos os campos de tipo de valor para o seu valor padrão e todos os campos de tipo de referência para null. Campos estáticos e de instância de uma struct podem incluir inicializadores de variáveis; no entanto, no caso de um inicializador de campo de instância, pelo menos um construtor de instância também deve ser declarado, ou, para um registo de registo, deve estar presente um delimited_parameter_list .
Exemplo:
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)Exemplo final
Quando um construtor de instância struct não tem inicializador de construtor, esse construtor executa implicitamente as inicializações especificadas pelos variable_initializers dos campos de instância declarados na sua struct. Isto corresponde a uma sequência de atribuições que são executadas imediatamente após a entrada no construtor.
Quando um construtor de instância de struct tem um this() inicializador de construtor que representa o construtor sem parâmetros padrão, o construtor declarado limpa implicitamente todos os campos de instância e executa as inicializações especificadas pelos variable_initializers dos campos de instância declarados no seu struct. Imediatamente após a introdução do construtor, todos os campos de tipo de valor são definidos para o seu valor padrão e todos os campos de tipo de referência para null. Imediatamente a seguir, é executada uma sequência de atribuições correspondentes aos variable_initializers.
Um field_declaration declarado diretamente dentro de um struct_declaration que possua o struct_modifierreadonly terá o field_modifierreadonly.
16.5.9 Construtores
Uma struct pode declarar construtores de instância, com parâmetros zero ou mais. Se uma struct não tiver um construtor de instância sem parâmetros explicitamente declarado, é sintetizado, com acessibilidade pública, que devolve sempre o valor resultante de definir todos os campos de tipo de valor para o seu valor padrão e todos os campos de tipo de referência para null (§8.3.3). Nesse caso, quaisquer inicializadores de campo de instância são ignorados quando esse construtor é executado.
Um construtor de instância sem parâmetros declarado explicitamente deve ter acessibilidade pública.
Exemplo: Dado o seguinte
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)As instruções criam uma
Pointcomxeyinicializam a zero, o que, no caso da chamada ao construtor de instância sem parâmetros, pode ser surpreendente, pois ambos os campos de instância têm inicializadores, mas não são executados.Exemplo final
Um construtor de instância struct não tem permissão para incluir um inicializador de construtor do formulário base(argument_list), onde argument_list é opcional. A execução de um construtor de instância não resultará na execução de um construtor no tipo System.ValueTypebase da estrutura .
O this parâmetro de um construtor de instância struct corresponde a um parâmetro de saída do tipo struct. Como tal, this deve ser definitivamente atribuído (§9.4) em cada local onde o construtor retorna. Da mesma forma, não pode ser lido (mesmo implicitamente) no corpo do construtor antes de ser definitivamente atribuído.
Se o construtor struct instance especificar um inicializador do construtor, esse inicializador será considerado uma atribuição definida para isso que ocorre antes do corpo do construtor. Portanto, o corpo em si não tem requisitos de inicialização.
Campos de instância (exceto fixed campos) devem ser definitivamente atribuídos em construtores de instância struct que não tenham inicializador this() .
Exemplo: Considere a implementação do construtor de instância abaixo:
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 } }Nenhum membro da função de instância (incluindo os acessadores definidos para as propriedades
XeY) pode ser chamado até que todos os campos da estrutura que está sendo construída tenham sido definitivamente atribuídos. Observe, no entanto, que sePointfosse uma classe em vez de uma struct, a implementação do construtor de instância seria permitida. Há uma exceção a isso, e que envolve propriedades implementadas automaticamente (§15.7.4). As regras de atribuição definitiva (§12.24.2) isentam especificamente a atribuição a uma auto-propriedade de um tipo de struct dentro de um construtor de instância desse tipo de struct: tal atribuição é considerada uma atribuição definida do campo oculto de suporte da auto-propriedade. Assim, é permitido: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 } }exemplo final]
16.5.10 Construtores estáticos
Os construtores estáticos para structs seguem a maioria das mesmas regras que para classes. A execução de um construtor estático para um tipo struct é acionada pelo primeiro dos seguintes eventos a ocorrer dentro de um domínio de aplicativo:
- Um membro estático do tipo struct é referenciado.
- Um construtor explicitamente declarado do tipo struct é chamado.
Nota: A criação de valores padrão (§16.5.5) dos tipos de struct não ativa o construtor estático. (Um exemplo disso é o valor inicial dos elementos em uma matriz.) Nota final
16.5.11 Propriedades
Um property_declaration (§15.7.1) para uma propriedade de instância em um struct_declaration pode conter o property_modifierreadonly. No entanto, uma propriedade estática não deve conter esse modificador.
É um erro em tempo de compilação tentar modificar o estado de uma variável de instância do tipo struct através de uma propriedade readonly declarada nesse struct.
É um erro em tempo de compilação uma propriedade implementada automaticamente possuir um modificador readonly, ao mesmo tempo que possuir um acessador set.
É um erro em tempo de compilação para uma propriedade implementada automaticamente em um readonly struct ter um set acessador.
Uma propriedade implementada automaticamente declarada dentro de uma readonly struct não precisa ter um readonly modificador, pois seu get método de acesso é implicitamente considerado como de leitura apenas.
É um erro em tempo de compilação ter um readonly modificador em uma propriedade em si, bem como em qualquer um dos seus get e set acessadores.
É um erro em tempo de compilação para uma propriedade ter um modificador somente de leitura em qualquer dos seus acessadores.
Nota: Para corrigir o erro, mova o modificador dos acessadores para a própria propriedade. Nota final
Para uma expressão de acessador de propriedade, s.P:
- É um erro em tempo de compilação se
s.Pinvocar o acessadorMdefinido do tipoTquando o processo no §12.6.6.1 criaria uma cópia temporária dos. - Se
s.Pinvocar o tipoTget accessor , o processo no §12.6.6.1 é seguido, incluindo a criação de uma cópia temporária dosse necessário.
As propriedades implementadas automaticamente (§15.7.4) usam campos de suporte ocultos, que só são acessíveis aos acessadores de propriedade.
Nota: Essa restrição de acesso significa que os construtores em structs contendo propriedades implementadas automaticamente geralmente precisam de um inicializador de construtor explícito onde de outra forma não precisariam de um, para satisfazer o requisito de todos os campos serem definitivamente atribuídos antes que qualquer membro da função seja invocado ou o construtor retorne. Nota final
16.5.12 Métodos
Um method_declaration (§15.6.1) para um método de instância em um struct_declaration pode conter o method_modifierreadonly. No entanto, um método estático não deve conter esse modificador.
É um erro em tempo de compilação tentar modificar o estado de uma variável de estrutura de instância por meio de um método de leitura apenas declarado nessa estrutura.
Embora um método somente leitura possa chamar um irmão, método não somente leitura, ou propriedade ou indexador obter acessador, isso resulta na criação de uma cópia implícita de this como uma medida defensiva.
Um método somente leitura pode chamar uma propriedade irmã ou um acessador de conjunto de indexadores que é somente leitura. Se o acessador de um membro irmão não for explícita ou implicitamente somente leitura, ocorrerá um erro de compilação.
Todos os method_declarations de um método parcial devem ter um readonly modificador, ou nenhum deles deverá ter um.
16.5.13 Indexadores
Um indexer_declaration (§15.9) para um indexador de instância num struct_declaration pode conter o indexer_modifierreadonly.
É um erro em tempo de compilação tentar modificar o estado de uma variável struct de instância através de um indexador de leitura apenas declarado nessa struct.
É um erro em tempo de compilação ter um readonly modificador em um indexador em si, bem como em qualquer um dos seus get ou set acessadores.
É um erro em tempo de compilação que um indexador tenha um modificador só de leitura em todos os seus acessores.
Nota: Para corrigir o erro, mova o modificador dos acessadores para o próprio indexador. Nota final
16.5.14 Eventos
Um event_declaration (§15.8.1) por exemplo, um evento não semelhante a um campo em um struct_declaration pode conter o event_modifierreadonly. No entanto, um evento estático não deve conter esse modificador.
16.5.15 Restrição de contexto seguro
16.5.15.1 Geral
Em tempo de compilação, cada expressão é associada a um contexto onde essa instância e todos os seus campos podem ser acessados com segurança, seu contexto seguro. O contexto seguro é um contexto que encerra uma expressão, para o qual é seguro que o valor escape.
Qualquer expressão cujo tipo de tempo de compilação não seja uma estrutura de referência tem um contexto seguro no contexto do chamador.
Uma expressão default, de qualquer tipo, tem um contexto seguro de chamada.
Para qualquer expressão não padrão cujo tipo em tempo de compilação seja um 'ref struct', existe um contexto seguro definido pelas seções seguintes.
O contexto seguro registra para qual contexto um valor pode ser copiado. Dada uma atribuição de uma expressão E1 com um contexto seguro S1, para uma expressão E2 com um contexto seguro S2, ocorre um erro se S2 for um contexto mais amplo que S1.
Existem três valores diferentes de contexto seguro, os mesmos que os valores ref-safe-context definidos para variáveis de referência (§9.7.2): declaration-block, function-member e caller-context. O contexto seguro de uma expressão restringe o seu uso da seguinte forma:
- Para uma declaração de retorno
return e1, o contexto seguro dee1deve ser o contexto do chamador. - Para uma atribuição
e1 = e2, o contexto seguro dee2deve ser, pelo menos, um contexto tão amplo como o contexto seguro dee1.
Para uma invocação de método, se houver um ref ou out argumento de um ref struct tipo (incluindo o recetor, a menos que o tipo seja readonly), com contexto S1seguro, então nenhum argumento (incluindo o recetor) pode ter um contexto seguro mais restrito do que S1.
16.5.15.2 Contexto seguro para parâmetros
Um parâmetro de um tipo ref struct, incluindo o parâmetro this de um método de instância, tem um contexto seguro do contexto do chamador.
16.5.15.3 Contexto seguro de variáveis locais
Uma variável local de um tipo ref struct tem um contexto seguro da seguinte forma:
- Se a variável é uma variável de iteração de um
foreachloop, então o contexto seguro da variável é o mesmo que o contexto seguro da expressão doforeachloop. - Caso contrário, se a declaração da variável tiver um inicializador, o contexto seguro da variável será o mesmo que o contexto seguro desse inicializador.
- Caso contrário, a variável não será inicializada no momento da declaração e terá um contexto seguro do contexto do chamador.
16.5.15.4 Contexto do seguro de campo
Uma referência a um campo e.F, onde o tipo de F é um tipo struct ref, tem um contexto seguro que é o mesmo que o contexto seguro de e.
16.5.15.5 Operadores
A aplicação de um operador definido pelo utilizador é tratada como uma invocação de método (§16.5.15.6).
Para um operador que produz um valor, como e1 + e2 ou c ? e1 : e2, o contexto seguro do resultado é o contexto mais estreito entre os contextos seguros dos operandos do operador. Como consequência, para um operador unário que produz um valor, como +e, o contexto seguro do resultado é o contexto seguro do operando.
Nota: O primeiro operando de um operador condicional é um
bool, portanto, seu contexto seguro é o contexto do chamador. Segue-se que o contexto seguro resultante é o contexto seguro mais estreito do segundo e terceiro operando. Nota final
16.5.15.6 Invocação de métodos e propriedades
Um valor resultante de uma invocação e1.M(e2, ...) de método ou invocação e.P de propriedade tem contexto seguro no menor dos seguintes contextos:
- contexto do chamador.
- O contexto seguro de todas as expressões de argumento (incluindo o recetor).
Uma invocação de propriedade (seja get ou set) é tratada como uma invocação de método do método subjacente pelas regras acima.
16.5.15.7 stackalloc
O resultado de uma expressão stackalloc tem contexto seguro de function-member.
16.5.15.8 Invocações de construtores
Uma new expressão que invoca um construtor obedece às mesmas regras que uma invocação de método que é considerada para retornar o tipo que está sendo construído.
Além disso, o contexto seguro é o menor dos contextos seguros de todos os argumentos e operandos de todas as expressões do inicializador de objeto, recursivamente, se algum inicializador estiver presente.
Nota: Estas regras baseiam-se em
Span<T>não ter um construtor da seguinte forma:public Span<T>(ref T p)Tal construtor torna instâncias de
Span<T>usadas como campos indistinguíveis de um camporef. As regras de segurança descritas neste documento dependem derefcampos não serem uma construção válida em C# ou .NET. Nota final
ECMA C# draft specification