Compartilhar via


16 Estruturas

16.1 Geral

As estruturas são semelhantes às classes, pois representam estruturas de dados que podem conter membros de dados e membros de função. No entanto, ao contrário das classes, os structs são tipos de valor e não exigem alocação de 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 objeto.

Observação: 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 exemplos de structs. 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 em que a atribuição copia o valor em vez da referência. nota final

Conforme descrito em §8.3.5, os tipos simples fornecidos pelo C#, como int, doublee bool, são, na verdade, todos os tipos de struct.

16.2 Declarações de struct

16.2.1 Geral

Um struct_declaration é um type_declaration (§14.7) que declara um novo struct:

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

Um struct_declaration consiste em um conjunto opcional de atributos (§22), 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 o struct, seguido por uma especificação type_parameter_list opcional (§15.2.3), seguida por uma especificação de struct_interfaces opcional (§16.2.5), seguida por uma especificação opcional de type_parameter_constraints cláusulas (§15.2.5), seguida por uma struct_body (§16.2.6), opcionalmente seguida por um ponto-e-vírgula.

Uma declaração de estrutura não deve fornecer um type_parameter_constraints_clauses a menos que também forneça uma type_parameter_list.

Uma declaração struct que fornece um type_parameter_list é uma declaração struct 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 é em si uma declaração de struct genérica, uma vez que os argumentos de tipo para o tipo que o contém devem ser fornecidos para criar um tipo construído (§8.4).

Uma declaração struct que inclui uma ref palavra-chave não deve ter uma parte struct_interfaces .

16.2.2 Modificadores de struct

Um struct_declaration pode incluir opcionalmente uma sequência de struct_modifiers:

struct_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | 'readonly'
    | unsafe_modifier   // unsafe code support
    ;

unsafe_modifier (§23.2) está disponível apenas em código não seguro (§23).

É um erro de tempo de compilação para o mesmo modificador aparecer várias vezes em uma declaração de struct.

Com exceção de readonly, os modificadores de uma declaração 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.

Um struct somente leitura tem as seguintes restrições:

  • Cada um de seus campos de instância também deve ser declarado readonly.
  • Nenhuma de suas propriedades de instância deve ter um set_accessor_declaration (§15.7.3).
  • Ele não deve declarar nenhum evento semelhante a um campo (§15.8.2).

Quando uma instância de um struct readonly é passada para um método, ela this é tratada como um argumento/parâmetro de entrada, o que não permite o acesso de gravação a qualquer campo de instância (exceto por construtores).

16.2.3 Modificador de referência

O ref modificador indica que o struct_declaration declara um tipo cujas instâncias são alocadas na pilha de execução. Esses tipos são chamados de tipos ref struct . O ref modificador declara que as instâncias podem conter campos semelhantes a ref e não devem ser copiadas de seu contexto seguro (§16.4.12). As regras para determinar o contexto seguro de uma estrutura ref são descritas em §16.4.12.

É um erro em tempo de compilação se um tipo de struct ref 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 struct que não tem o ref modificador.
  • Ser encaixotado em System.ValueType ou System.Object.
  • Como um argumento de tipo.
  • Como o tipo de um elemento de tupla.
  • Um método assíncrono.
  • Um iterador.
  • Não há conversão de um ref struct tipo para o tipo object ou o tipo System.ValueType.
  • Um ref struct tipo não deve ser declarado para implementar nenhuma interface.
  • Um método de instância declarado em object ou em System.ValueType , mas não substituído em um ref struct tipo, não deve ser chamado com um receptor desse ref struct tipo.
  • Um método de instância de um ref struct tipo não deve ser capturado pela conversão do grupo de métodos em um tipo delegado.
  • Uma estrutura ref não deve ser capturada por uma expressão lambda ou uma função local.

Nota: A ref struct não deve declarar async métodos de instância nem usar uma instrução or yield return yield break dentro de um método de instância, porque o parâmetro implícito this não pode ser usado nesses contextos. nota final

Essas restrições garantem que uma variável do tipo não se refira à memória de ref struct pilha que não é mais válida ou a variáveis que não são mais válidas.

16.2.4 Modificador parcial

O partial modificador indica que essa struct_declaration é uma declaração de tipo parcial. Várias declarações de struct parciais com o mesmo nome dentro de um namespace delimitador ou declaração de tipo se combinam para formar uma declaração de struct, seguindo as regras especificadas em §15.2.7.

16.2.5 Interfaces de struct

Uma declaração de struct pode incluir uma especificação struct_interfaces , caso em que se diz que o struct implementa diretamente os tipos de interface fornecidos. Para um tipo de struct construído, incluindo um tipo aninhado declarado em uma declaração de tipo genérico (§15.3.9.7), cada tipo de interface implementado é obtido substituindo, para cada type_parameter na interface fornecida, 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 de struct parcial (§15.2.7) é discutido mais adiante em §15.2.4.3.

As implementações de interface são discutidas mais adiante em §18.6.

16.2.6 Corpo da estrutura

A struct_body de um struct define os membros do struct.

struct_body
    : '{' struct_member_declaration* '}'
    ;

16.3 Membros da estrutura

Os membros de um struct consistem nos membros introduzidos por seus struct_member_declarations e nos membros herdados do 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) só está disponível em código não seguro (§23).

Nota: Todos os tipos de class_member_declarations, exceto finalizer_declaration também são struct_member_declarations. nota final

Exceto pelas diferenças observadas em §16.4, as descrições de membros de classe fornecidas em §15.3 a §15.12 também se aplicam a membros de struct.

16.4 Diferenças de classe e estrutura

16.4.1 Geral

Os structs diferem das classes de várias maneiras importantes:

  • Structs são tipos de valor (§16.4.2).
  • Todos os tipos de struct herdam implicitamente da classe System.ValueType (§16.4.3).
  • A atribuição a uma variável de um tipo struct cria uma cópia do valor que está sendo atribuído (§16.4.4).
  • O valor padrão de um struct é o valor produzido pela definição de todos os campos como seu valor padrão (§16.4.5).
  • As operações de conversão boxing e unboxing são usadas para converter entre um tipo de struct e determinados tipos de referência (§16.4.6).
  • O significado de é diferente dentro dos this membros struct (§16.4.7).
  • As declarações de campo de instância para um struct não têm permissão para incluir inicializadores de variável (§16.4.8).
  • Um struct não tem permissão para declarar um construtor de instância sem parâmetros (§16.4.9).
  • Um struct não tem permissão para declarar um finalizador.

16.4.2 Semântica de valor

Structs são tipos de valor (§8.3) e dizem ter 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 tempo de compilação para A depender de ou um tipo construído a B partir de B. A struct Xdepende diretamente de um struct Y se X contiver um campo de instância do tipo Y. Dada essa definição, o conjunto completo de structs do qual um struct depende é o fechamento transitivo do relacionamento diretamente depende .

Exemplo:

struct Node
{
    int data;
    Node next; // error, Node directly depends on itself
}

é um erro porque Node contém um campo de instância de seu próprio tipo. Outro exemplo

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

é um erro porque cada um dos tipos A, B, e C dependem um do outro.

exemplo de fim

Com classes, é possível que duas variáveis façam referência ao mesmo objeto e, portanto, é possível que as 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ável (§8.3.12), não é possível que os valores de um tipo struct sejam null.

Observação: se um struct contiver um campo do tipo de referência, o conteúdo do objeto referenciado poderá ser alterado por outras operações. No entanto, o valor do campo em si, ou seja, qual objeto ele referencia, não pode ser alterado por meio de uma mutação de um valor de 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 de a para b cria uma cópia do valor e b , portanto, não é afetada pela atribuição a a.x. Em vez disso, tivesse Point sido declarado como uma classe, a saída seria 100 because a e b faria referência ao mesmo objeto.

exemplo de fim

16.4.3 Herança

Todos os tipos de struct 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 que uma declaração struct especifique uma classe base.

Os tipos de struct nunca são abstratos e são sempre selados implicitamente. Os abstract modificadores and sealed não são, portanto, permitidos em uma struct declaração.

Como não há suporte para herança para structs, a acessibilidade declarada de um membro struct não pode ser protected, private protected, ou protected internal.

Os membros da função em um struct não podem ser abstratos ou virtuais, e o override modificador só tem permissão para substituir métodos herdados de System.ValueType.

16.4.4 Cessão

A atribuição a uma variável de um tipo de 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 um struct é passado como um parâmetro de valor ou retornado como resultado de um membro da função, uma cópia do struct é criada. Um struct pode ser passado por referência a um membro de função usando um parâmetro por referência.

Quando uma propriedade ou indexador de um 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. Isso é descrito em mais detalhes em §12.21.2.

16.4.5 Valores padrão

Conforme descrito na seção 9.3, vários tipos de variáveis são inicializados automaticamente para seu valor padrão quando são criados. Para variáveis de tipos de classe e outros tipos de referência, esse valor padrão é null. No entanto, como os structs são tipos de valor que não podem ser null, o valor padrão de um struct é o valor produzido pela definição de 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 ao struct declarado Point acima, o exemplo

Point[] a = new Point[100];

inicializa cada um Point na matriz para o valor produzido definindo os x campos e y como zero.

exemplo de fim

O valor padrão de um struct corresponde ao valor retornado pelo construtor padrão do struct (§8.3.3). Ao contrário de uma classe, um struct não tem permissão para declarar um construtor de instância sem parâmetros. Em vez disso, cada struct tem implicitamente um construtor de instância sem parâmetros, que sempre retorna o valor resultante da configuração de todos os campos para seus valores padrão.

Observação: os structs devem ser projetados 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 null valores somente onde ele é chamado explicitamente. Nos casos em que uma KeyValuePair variável está sujeita à inicialização do valor padrão, os key campos and value serão null, e o struct deve estar preparado para lidar com esse estado.

nota final

16.4.6 Boxe e unboxing

Um valor de um tipo de classe pode ser convertido em tipo object ou em um tipo de interface implementado pela classe simplesmente tratando a referência como outro tipo em tempo de compilação. Da mesma forma, um valor de type object ou um valor de um tipo de interface pode ser convertido de volta em um tipo de classe sem alterar a referência (mas, é claro, uma verificação de tipo de tempo de execução é necessária nesse caso).

Como os structs não são tipos de referência, essas operações são implementadas de forma diferente para os tipos de struct. Quando um valor de um tipo struct é convertido em determinados tipos de referência (conforme definido em §10.2.9), ocorre uma operação de boxing. Da mesma forma, quando um valor de determinados tipos de referência (conforme definido em §10.3.7) é convertido de volta em um tipo de struct, ocorre uma operação de unboxing. Uma diferença importante das mesmas operações em tipos de classe é que a conversão boxing e a unboxing copiam o valor struct para dentro ou para fora da instância em caixa.

Observação: Assim, após uma operação de encaixotamento ou unboxing, as alterações feitas no unboxed struct não são refletidas structno arquivo . nota final

Para obter mais detalhes sobre boxing e unboxing, consulte §10.2.9 e §10.3.7.

16.4.7 Significado deste

O significado de this em uma estrutura difere do significado de this em uma classe, conforme descrito em §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 a conversão boxing ocorra. Isso é verdadeiro mesmo quando o struct é usado como um parâmetro de tipo e a invocação ocorre por meio de uma instância do tipo de parâmetro 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
3

Embora seja um estilo ruim para ToString ter efeitos colaterais, o exemplo demonstra que nenhuma conversão boxing ocorreu para as três invocações de x.ToString().

exemplo de fim

Da mesma forma, a conversão boxing 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 Increment método é chamada com uma referência à variável que Increment foi chamada, nunca uma cópia em caixa.

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 Increment modifica o valor na variável x. Isso não é equivalente à segunda chamada para Increment, que modifica o valor em uma cópia em caixa de x. Assim, a saída do programa é:

0
1
1

exemplo de fim

16.4.8 Inicializadores de campo

Conforme descrito em §16.4.5, o valor padrão de um struct consiste no valor que resulta da configuração de todos os campos de tipo de valor como seu valor padrão e todos os campos de tipo de referência como null. Por esse motivo, um struct não permite que declarações de campo de instância incluam inicializadores de variável. Essa restrição se aplica somente aos campos de instância. Os campos estáticos de um struct têm permissão para incluir inicializadores de variáveis.

Exemplo: o seguinte

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

está com erro porque as declarações de campo de instância incluem inicializadores de variáveis.

exemplo de fim

16.4.9 Construtores

Ao contrário de uma classe, um struct não tem permissão para declarar um construtor de instância sem parâmetros. Em vez disso, cada struct tem implicitamente um construtor de instância sem parâmetros, que sempre retorna o valor resultante da definição de todos os campos de tipo de valor como seu valor padrão e todos os campos de tipo de referência como null (§8.3.3). Um struct pode declarar construtores de instância com parâmetros.

Exemplo: Dado o seguinte

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);
    }
}

As instruções criam um Point with x e y inicializaram como zero.

exemplo de fim

Um construtor de instância struct não tem permissão para incluir um inicializador de construtor do formulário base(argument_list), em que argument_list é opcional.

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 todos os locais onde o construtor retorna. Da mesma forma, ele não pode ser lido (mesmo implicitamente) no corpo do construtor antes de ser definitivamente atribuído.

Se o construtor de instância struct especificar um inicializador de construtor, esse inicializador será considerado uma atribuição definida a isso que ocorre antes do corpo do construtor. Portanto, o corpo em si não tem requisitos de inicialização.

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 de função de instância (incluindo os acessadores set para as propriedades X e Y) pode ser chamado até que todos os campos do struct que está sendo construído tenham sido definitivamente atribuídos. Observe, no entanto, que se Point fosse uma classe em vez de um struct, a implementação do construtor de instância seria permitida. Há uma exceção a isso, e isso envolve propriedades implementadas automaticamente (§15.7.4). As regras de atribuição definida (§12.21.2) isentam especificamente a atribuição a uma propriedade automática de um tipo de struct dentro de um construtor de instância desse tipo de struct: essa atribuição é considerada uma atribuição definida do campo de suporte oculto da propriedade automática. 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 de end]

16.4.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 de struct é disparada pelo primeiro dos seguintes eventos a ocorrer em um domínio de aplicativo:

  • Um membro estático do tipo struct é referenciado.
  • Um construtor declarado explicitamente do tipo struct é chamado.

Observação: a criação de valores padrão (§16.4.5) de tipos de struct não dispara o construtor estático. (Um exemplo disso é o valor inicial dos elementos em uma matriz.) nota final

16.4.11 Propriedades implementadas automaticamente

As propriedades implementadas automaticamente (§15.7.4) usam campos de suporte ocultos, que só podem ser acessados pelos acessadores de propriedade.

Observação: essa restrição de acesso significa que os construtores em structs que contêm 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.4.12 Restrição de contexto seguro

16.4.12.1 Geral

Em tempo de compilação, cada expressão é associada a um contexto em que essa instância e todos os seus campos podem ser acessados com segurança, seu contexto seguro. O contexto seguro é um contexto, incluindo uma expressão, para a qual é seguro escapar o valor.

Qualquer expressão cujo tipo de tempo de compilação não seja um struct ref tem um contexto seguro de caller-context.

Uma default expressão, para qualquer tipo, tem safe-context de caller-context.

Para qualquer expressão não padrão cujo tipo de tempo de compilação é um ref, struct tem um contexto seguro definido pelas seções a seguir.

O contexto seguro registra em qual contexto um valor pode ser copiado. Dada uma atribuição de uma expressão E1 com um contexto S1seguro , para uma expressão E2 com contexto S2seguro , é um erro se S2 for um contexto mais amplo que S1.

Há 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 seu uso da seguinte maneira:

  • Para uma instrução return e1de retorno , o contexto seguro de e1 deve ser contexto de chamada.
  • Para uma atribuição e1 = e2 , o contexto seguro de e2 deve ser pelo menos tão amplo quanto o contexto seguro de e1.

Para uma invocação de método, se houver um ref argumento or out de um ref struct tipo (incluindo o receptor, a menos que o tipo seja readonly), com contexto S1seguro , nenhum argumento (incluindo o receptor) poderá ter um contexto seguro mais restrito do que S1.

16.4.12.2 Contexto seguro de parâmetros

Um parâmetro de um tipo ref struct, incluindo o this parâmetro de um método de instância, tem um contexto seguro de caller-context.

16.4.12.3 Contexto seguro de variável local

Uma variável local de um tipo ref struct tem um contexto seguro da seguinte maneira:

  • Se a variável for uma variável de iteração de um foreach loop, o contexto seguro da variável será o mesmo que o contexto seguro da expressão do foreach loop.
  • 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 ponto de declaração e terá um contexto seguro de contexto de chamada.

16.4.12.4 Contexto de segurança de campo

Uma referência a um campo e.F, em que o tipo de é um tipo de F struct ref, tem um contexto seguro que é o mesmo que o contexto seguro de e.

16.4.12.5 Operadores

A aplicação de um operador definido pelo usuário é tratada como uma invocação de método (§16.4.12.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.

Observação: o primeiro operando de um operador condicional é um bool, portanto, seu contexto seguro é caller-context. Segue-se que o contexto seguro resultante é o contexto seguro mais estreito do segundo e terceiro operando. nota final

16.4.12.6 Invocação de método e propriedade

Um valor resultante de uma invocação e1.M(e2, ...) de método ou invocação e.P de propriedade tem um contexto seguro do menor dos seguintes contextos:

  • contexto do chamador.
  • O contexto seguro de todas as expressões de argumento (incluindo o receptor).

Uma invocação de propriedade (ou get set) é tratada como uma invocação de método do método subjacente pelas regras acima.

16.4.12.7 StackAlloc

O resultado de uma expressão stackalloc tem contexto seguro de function-member.

16.4.12.8 Invocações do construtor

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 safe-context é o menor dos safe-contexts de todos os argumentos e operandos de todas as expressões do inicializador de objeto, recursivamente, se algum inicializador estiver presente.

Observação: essas regras dependem de Span<T> não ter um construtor da seguinte forma:

public Span<T>(ref T p)

Esse construtor torna as instâncias de Span<T> used as fields indistinguíveis de um ref campo. As regras de segurança descritas neste documento dependem de ref campos que não são uma construção válida em C# ou .NET. nota final