Tipos de estrutura (referência C#)

Um tipo de estrutura (ou tipo struct) é um tipo de valor que pode encapsular dados e funcionalidades relacionadas. Use a struct palavra-chave para definir um tipo de estrutura:

public struct Coords
{
    public Coords(double x, double y)
    {
        X = x;
        Y = y;
    }

    public double X { get; }
    public double Y { get; }

    public override string ToString() => $"({X}, {Y})";
}

Para obter informações sobre ref struct e readonly ref struct tipos, consulte o artigo Tipos de estrutura ref.

Os tipos de estrutura têm semântica de valor. Ou seja, uma variável de um tipo de estrutura contém uma instância do tipo. Por padrão, os valores das variáveis são copiados na atribuição, passando um argumento para um método e retornando um resultado do método. Para variáveis de tipo de estrutura, uma instância do tipo é copiada. Para obter mais informações, consulte Tipos de valor.

Normalmente, você usa tipos de estrutura para projetar pequenos tipos centrados em dados que fornecem pouco ou nenhum comportamento. Por exemplo, o .NET usa tipos de estrutura para representar um número (inteiro e real), um valor booleano, um caractere Unicode, uma instância de tempo. Se você estiver focado no comportamento de um tipo, considere definir uma classe. Os tipos de classe têm semântica de referência. Ou seja, uma variável de um tipo de classe contém uma referência a uma instância do tipo, não à instância em si.

Como os tipos de estrutura têm semântica de valor, recomendamos que você defina tipos de estrutura imutáveis .

readonly estruturar

Use o readonly modificador para declarar que um tipo de estrutura é imutável. Todos os membros de dados de uma readonly struct devem ser somente leitura da seguinte maneira:

  • Qualquer declaração de campo deve ter o readonly modificador
  • Qualquer propriedade, incluindo as implementadas automaticamente, deve ser somente leitura ou init somente leitura.

Isso garante que nenhum membro de uma readonly struct modifique o estado da struct. Isso significa que outros membros da instância, exceto construtores, estão implicitamente readonly.

Nota

Em uma readonly estrutura, um membro de dados de um tipo de referência mutável ainda pode mutar seu próprio estado. Por exemplo, você não pode substituir uma List<T> instância, mas pode adicionar novos elementos a ela.

O código a seguir define uma readonly struct com setters de propriedade somente init:

public readonly struct Coords
{
    public Coords(double x, double y)
    {
        X = x;
        Y = y;
    }

    public double X { get; init; }
    public double Y { get; init; }

    public override string ToString() => $"({X}, {Y})";
}

readonly Membros da instância

Você também pode usar o readonly modificador para declarar que um membro da instância não modifica o estado de uma struct. Se você não puder declarar todo o tipo de estrutura como readonly, use o readonly modificador para marcar os membros da instância que não modificam o estado da estrutura.

Dentro de um readonly membro da instância, você não pode atribuir aos campos de instância da estrutura. No entanto, um readonly membro pode chamar um não-membroreadonly . Nesse caso, o compilador cria uma cópia da instância de estrutura e chama o não-membroreadonly nessa cópia. Como resultado, a instância da estrutura original não é modificada.

Normalmente, você aplica o readonly modificador aos seguintes tipos de membros da instância:

  • Metodologia:

    public readonly double Sum()
    {
        return X + Y;
    }
    

    Você também pode aplicar o readonly modificador a métodos que substituem métodos declarados em System.Object:

    public readonly override string ToString() => $"({X}, {Y})";
    
  • Propriedades e indexadores:

    private int counter;
    public int Counter
    {
        readonly get => counter;
        set => counter = value;
    }
    

    Se você precisar aplicar o readonly modificador a ambos os acessadores de uma propriedade ou indexador, aplique-o na declaração da propriedade ou indexador.

    Nota

    O compilador declara um get acessador de uma propriedade implementada automaticamente como readonly, independentemente da presença do modificador em uma declaração de readonly propriedade.

    Você pode aplicar o readonly modificador a uma propriedade ou indexador com um init acessador:

    public readonly double X { get; init; }
    

Você pode aplicar o readonly modificador a campos estáticos de um tipo de estrutura, mas não a quaisquer outros membros estáticos, como propriedades ou métodos.

O compilador pode fazer uso do readonly modificador para otimizações de desempenho. Para obter mais informações, consulte Evitando alocações.

Mutação não destrutiva

A partir do C# 10, você pode usar a with expressão para produzir uma cópia de uma instância do tipo estrutura com as propriedades especificadas e os campos modificados. Você usa a sintaxe do inicializador de objeto para especificar quais membros modificar e seus novos valores, como mostra o exemplo a seguir:

public readonly struct Coords
{
    public Coords(double x, double y)
    {
        X = x;
        Y = y;
    }

    public double X { get; init; }
    public double Y { get; init; }

    public override string ToString() => $"({X}, {Y})";
}

public static void Main()
{
    var p1 = new Coords(0, 0);
    Console.WriteLine(p1);  // output: (0, 0)

    var p2 = p1 with { X = 3 };
    Console.WriteLine(p2);  // output: (3, 0)

    var p3 = p1 with { X = 1, Y = 4 };
    Console.WriteLine(p3);  // output: (1, 4)
}

record estruturar

A partir do C# 10, você pode definir tipos de estrutura de registro. Os tipos de registro fornecem funcionalidade interna para encapsular dados. Você pode definir ambos e record structreadonly record struct tipos. Uma estrutura de registro não pode ser um ref structarquivo . Para obter mais informações e exemplos, consulte Registros.

Matrizes em linha

A partir do C# 12, você pode declarar matrizes embutidas como um struct tipo:

[System.Runtime.CompilerServices.InlineArray(10)]
public struct CharBuffer
{
    private char _firstElement;
}

Uma matriz embutida é uma estrutura que contém um bloco contíguo de N elementos do mesmo tipo. É um código seguro equivalente à declaração de buffer fixo disponível apenas em código não seguro. Uma matriz embutida é uma struct matriz com as seguintes características:

  • Contém um único campo.
  • O struct não especifica um layout explícito.

Além disso, o compilador valida o System.Runtime.CompilerServices.InlineArrayAttribute atributo:

  • O comprimento deve ser maior que zero (> 0).
  • O tipo de destino deve ser uma estrutura.

Na maioria dos casos, uma matriz embutida pode ser acessada como uma matriz, tanto para ler quanto para gravar valores. Além disso, você pode usar os operadores de intervalo e índice .

Existem restrições mínimas quanto ao tipo de campo único. Não pode ser um tipo de ponteiro, mas pode ser qualquer tipo de referência ou qualquer tipo de valor. Você pode usar matrizes embutidas com praticamente qualquer estrutura de dados C#.

As matrizes em linha são um recurso de linguagem avançada. Destinam-se a cenários de alto desempenho em que um bloco de elementos em linha e contíguo é mais rápido do que outras estruturas de dados alternativas. Você pode saber mais sobre matrizes embutidas a partir do speclet de recurso

Inicialização de estrutura e valores padrão

Uma variável de um struct tipo contém diretamente os dados para esse struct. Isso cria uma distinção entre um não inicializado struct, que tem seu valor padrão e um inicializado struct, que armazena valores definidos ao construí-lo. Por exemplo, considere o seguinte código:

public readonly struct Measurement
{
    public Measurement()
    {
        Value = double.NaN;
        Description = "Undefined";
    }

    public Measurement(double value, string description)
    {
        Value = value;
        Description = description;
    }

    public double Value { get; init; }
    public string Description { get; init; }

    public override string ToString() => $"{Value} ({Description})";
}

public static void Main()
{
    var m1 = new Measurement();
    Console.WriteLine(m1);  // output: NaN (Undefined)

    var m2 = default(Measurement);
    Console.WriteLine(m2);  // output: 0 ()

    var ms = new Measurement[2];
    Console.WriteLine(string.Join(", ", ms));  // output: 0 (), 0 ()
}

Como mostra o exemplo anterior, a expressão de valor padrão ignora um construtor sem parâmetros e produz o valor padrão do tipo de estrutura. A instanciação de matriz do tipo estrutura também ignora um construtor sem parâmetros e produz uma matriz preenchida com os valores padrão de um tipo de estrutura.

A situação mais comum em que você vê valores padrão é em matrizes ou em outras coleções onde o armazenamento interno inclui blocos de variáveis. O exemplo a seguir cria uma matriz de 30 TemperatureRange estruturas, cada uma das quais tem o valor padrão:

// All elements have default values of 0:
TemperatureRange[] lastMonth = new TemperatureRange[30];

Todos os campos de membro de um struct devem ser definitivamente atribuídos quando ele é criado, porque struct os tipos armazenam diretamente seus dados. O default valor de uma struct definitivamente atribuiu todos os campos a 0. Todos os campos devem ser definitivamente atribuídos quando um construtor é invocado. Você inicializa campos usando os seguintes mecanismos:

  • Você pode adicionar inicializadores de campo a qualquer campo ou propriedade implementada automaticamente.
  • Você pode inicializar quaisquer campos, ou propriedades automáticas, no corpo do construtor.

A partir de C# 11, se você não inicializar todos os campos em uma struct, o compilador adicionará código ao construtor que inicializa esses campos para o valor padrão. O compilador executa sua análise de atribuição definitiva usual. Todos os campos que são acessados antes de serem atribuídos, ou não definitivamente atribuídos quando o construtor termina de executar, recebem seus valores padrão antes que o corpo do construtor seja executado. Se this for acessado antes de todos os campos serem atribuídos, o struct será inicializado com o valor padrão antes que o corpo do construtor seja executado.

public readonly struct Measurement
{
    public Measurement(double value)
    {
        Value = value;
    }

    public Measurement(double value, string description)
    {
        Value = value;
        Description = description;
    }

    public Measurement(string description)
    {
        Description = description;
    }

    public double Value { get; init; }
    public string Description { get; init; } = "Ordinary measurement";

    public override string ToString() => $"{Value} ({Description})";
}

public static void Main()
{
    var m1 = new Measurement(5);
    Console.WriteLine(m1);  // output: 5 (Ordinary measurement)

    var m2 = new Measurement();
    Console.WriteLine(m2);  // output: 0 ()

    var m3 = default(Measurement);
    Console.WriteLine(m3);  // output: 0 ()
}

Cada struct um tem um public construtor sem parâmetros. Se você escrever um construtor sem parâmetros, ele deve ser público. Se um struct declara qualquer inicializador de campo, ele deve declarar explicitamente um construtor. Esse construtor não precisa ser sem parâmetros. Se um struct declara um inicializador de campo, mas nenhum construtor, o compilador relata um erro. Qualquer construtor explicitamente declarado (com parâmetros ou sem parâmetros) executa todos os inicializadores de campo para essa estrutura. Todos os campos sem um inicializador de campo ou uma atribuição em um construtor são definidos como o valor padrão. Para obter mais informações, consulte a nota de proposta de recurso Construtores struct sem parâmetros.

A partir do C# 12, struct os tipos podem definir um construtor primário como parte de sua declaração. Construtores primários fornece uma sintaxe concisa para parâmetros do construtor que podem ser usados em todo o struct corpo, em qualquer declaração de membro para essa estrutura.

Se todos os campos de instância de um tipo de estrutura estiverem acessíveis, você também poderá instanciá-lo sem o new operador. Nesse caso, você deve inicializar todos os campos da instância antes do primeiro uso da instância. O exemplo a seguir mostra como fazer isso:

public static class StructWithoutNew
{
    public struct Coords
    {
        public double x;
        public double y;
    }

    public static void Main()
    {
        Coords p;
        p.x = 3;
        p.y = 4;
        Console.WriteLine($"({p.x}, {p.y})");  // output: (3, 4)
    }
}

No caso dos tipos de valor internos, use os literais correspondentes para especificar um valor do tipo.

Limitações com o projeto de um tipo de estrutura

As estruturas têm a maioria dos recursos de um tipo de classe . Existem algumas exceções e algumas exceções que foram removidas em versões mais recentes:

  • Um tipo de estrutura não pode herdar de outra classe ou tipo de estrutura e não pode ser a base de uma classe. No entanto, um tipo de estrutura pode implementar interfaces.
  • Não é possível declarar um finalizador dentro de um tipo de estrutura.
  • Antes do C# 11, um construtor de um tipo de estrutura deve inicializar todos os campos de instância do tipo.

Passagem de variáveis de tipo de estrutura por referência

Quando você passa uma variável de tipo de estrutura para um método como um argumento ou retorna um valor de tipo de estrutura de um método, toda a instância de um tipo de estrutura é copiada. Passar pelo valor pode afetar o desempenho do seu código em cenários de alto desempenho que envolvem grandes tipos de estrutura. Você pode evitar a cópia de valor passando uma variável de tipo de estrutura por referência. Use os modificadores de refparâmetro , out, in, ou ref readonly método para indicar que um argumento deve ser passado por referência. Use ref retorna para retornar um resultado de método por referência. Para obter mais informações, consulte Evitar alocações.

restrição struct

Você também usa a struct palavra-chave na struct restrição para especificar que um parâmetro type é um tipo de valor não anulável. Ambos os tipos de estrutura e enumeração satisfazem a struct restrição.

Conversões

Para qualquer tipo de estrutura (exceto ref struct tipos), existem conversões de boxe e unboxing de e para os System.ValueType tipos e System.Object . Existem também conversões de boxe e unboxing entre um tipo de estrutura e qualquer interface que ele implemente.

Especificação da linguagem C#

Para obter mais informações, consulte a seção Structs da especificação da linguagem C#.

Para obter mais informações sobre struct recursos, consulte as seguintes notas de proposta de recurso:

Consulte também