Compartilhar via


Structs de C#

Dica

Novo no desenvolvimento de software? Comece primeiro com os tutoriais de Introdução . Você encontrará structs quando precisar de tipos de valor leves em seu código.

Experimentou em outro idioma? Os structs de C# são tipos de valor semelhantes aos structs em C++ ou Swift, mas eles existem no heap gerenciado quando são encaixados e dão suporte a interfaces, construtores e métodos. Passe os olhos pela seção readonly structs em busca de padrões específicos de C#. Para structs de registro, consulte Registros.

Um struct é um tipo de valor que mantém seus dados diretamente na instância, em vez de por meio de uma referência a um objeto no heap. Quando você atribui um struct a uma nova variável, o runtime copia toda a instância. As alterações em uma variável não afetam a outra porque cada variável representa uma instância diferente. Use structs para tipos pequenos e leves cuja função primária é armazenar dados em vez de modelar o comportamento. Os exemplos incluem coordenadas, cores, medidas ou configurações.

Quando usar structs

Use uma struct para o tipo:

  • Representa um único valor ou um pequeno grupo de valores relacionados (aproximadamente 16 bytes ou menos).
  • Tem semântica de valor— duas instâncias com os mesmos dados devem ser iguais.
  • É principalmente um contêiner de dados em vez de um modelo de comportamento.
  • Não necessita de herança de um tipo base (structs não podem herdar de outros structs ou classes, mas podem implementar interfaces).

Para obter uma comparação mais ampla que inclua classes, registros, tuplas e interfaces, consulte Escolha qual tipo de dado.

Declarar uma estrutura

Defina um struct com a struct palavra-chave. Um struct pode conter campos, propriedades, métodos e construtores, assim como uma classe:

struct Point
{
    public double X { get; set; }
    public double Y { get; set; }

    public readonly double DistanceTo(Point other)
    {
        var dx = X - other.X;
        var dy = Y - other.Y;
        return Math.Sqrt(dx * dx + dy * dy);
    }

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

O Point struct armazena dois double valores e fornece um método para calcular a distância entre dois pontos. O DistanceTo método é marcado readonly porque não modifica o estado do struct. Esse padrão é abordado em membros somente leitura.

Semântica de valor

Structs são tipos de valor. A atribuição copia os dados, de modo que cada variável contém sua própria cópia independente:

var p1 = new Point { X = 3, Y = 4 };
var p2 = p1; // copies the data
p2.X = 10;

Console.WriteLine(p1); // (3, 4)  — p1 is unchanged
Console.WriteLine(p2); // (10, 4) — only p2 was modified

Como os structs são contêineres de dados, a atribuição copia cada membro de dados em uma nova instância independente. Cada cópia é distinta. Modificar um não afeta o outro. Esse comportamento difere das classes, em que a atribuição copia apenas a referência e ambas as variáveis compartilham o mesmo objeto. Para obter mais informações sobre a distinção, consulte tipos de valor e tipos de referência.

Construtores de struct

Você pode definir construtores em structs da mesma maneira que faz nas classes. Os structs podem ter construtores sem parâmetros que definem valores padrão personalizados. O termo "construtor sem parâmetros" distingue uma instância criada com new (que executa a lógica do seu construtor) de uma instância padrão criada com a expressão default (que inicializa todos os campos com zero):

struct ConnectionSettings
{
    public string Host { get; set; }
    public int Port { get; set; }
    public int MaxRetries { get; set; }

    public ConnectionSettings()
    {
        Host = "localhost";
        Port = 8080;
        MaxRetries = 3;
    }
}

Um construtor sem parâmetros é executado quando você usa new sem argumentos. A default expressão ignora o construtor e define todos os campos para seus valores padrão (0, , null). false Lembre-se da diferença:

var custom = new ConnectionSettings();
Console.WriteLine($"{custom.Host}:{custom.Port} (retries: {custom.MaxRetries})");
// localhost:8080 (retries: 3)

var defaults = default(ConnectionSettings);
Console.WriteLine($"{defaults.Host ?? "(null)"}:{defaults.Port} (retries: {defaults.MaxRetries})");
// (null):0 (retries: 0)

O compilador inicializa automaticamente todos os campos que você não define explicitamente em um construtor. Você pode inicializar apenas os campos que precisam de valores não padrão:

struct GameTile
{
    public int Row { get; set; }
    public int Column { get; set; }
    public bool IsBlocked { get; set; }

    public GameTile(int row, int column)
    {
        Row = row;
        Column = column;
        // IsBlocked is automatically initialized to false
    }
}

O exemplo a seguir exibe o valor padrão para IsBlocked:

var tile = new GameTile(2, 5);
Console.WriteLine($"Tile ({tile.Row}, {tile.Column}), blocked: {tile.IsBlocked}");
// Tile (2, 5), blocked: False

A IsBlocked propriedade não é atribuída no construtor, portanto, o compilador a define false como (o padrão para bool). Esse recurso reduz a clichê em construtores que só precisam definir alguns campos.

Estruturas readonly e membros readonly

É garantido por readonly struct que nenhum membro de instância modifica o estado do struct. O compilador impõe essa garantia exigindo que todos os campos e propriedades implementadas automaticamente sejam somente leitura:

readonly struct Temperature
{
    public double Celsius { get; }

    public Temperature(double celsius) => Celsius = celsius;

    public double Fahrenheit => Celsius * 9.0 / 5.0 + 32.0;

    public override string ToString() => $"{Celsius:F1}°C ({Fahrenheit:F1}°F)";
}

O exemplo a seguir cria uma Temperature instância e lê suas propriedades:

var temp = new Temperature(100);
Console.WriteLine(temp); // 100.0°C (212.0°F)
// temp.Celsius = 50; // Error: property is read-only

Quando você não precisar que todo o struct seja imutável, marque membros individuais como readonly em vez disso. Um readonly membro não pode modificar o estado do struct e o compilador verifica essa garantia:

struct Velocity
{
    public double X
    {
        readonly get;
        set;
    }

    public double Y
    {
        readonly get;
        set;
    }

    public readonly double Speed => Math.Sqrt(X * X + Y * Y);

    public readonly override string ToString() => $"({X}, {Y}) speed={Speed:F2}";
}

O exemplo a seguir mostra que readonly os membros retornam valores atualizados quando as propriedades mutáveis mudam:

var v = new Velocity { X = 3, Y = 4 };
Console.WriteLine(v.Speed); // 5
Console.WriteLine(v);       // (3, 4) speed=5.00
v.X = 6;
Console.WriteLine(v.Speed); // 7.211...

Marcar os membros readonly ajuda o compilador a otimizar as cópias defensivas. Quando você passa um readonly struct para um método que aceita um in parâmetro, o compilador sabe que nenhuma cópia é necessária.

Consulte também