Partilhar via


Estruturas em C#

Sugestão

Novo no desenvolvimento de software? Começa primeiro pelos tutoriais para começar . Vais encontrar structs assim que precisares de tipos de valores leves no teu código.

Experiente noutra língua? Os structs em C# são tipos de valores semelhantes aos structs em C++ ou Swift, mas permanecem no heap gerido quando estão em box e suportam interfaces, construtores e métodos. Dá uma vista de olhos à secção de estruturas só de leitura para padrões específicos de C#. Para estruturas de registos, veja Registos.

Uma struct é um tipo de valor que guarda os seus dados diretamente na instância, em vez de através de uma referência a um objeto no heap. Quando atribuis uma struct a uma nova variável, o runtime copia toda a instância. As alterações a uma variável não afetam a outra porque cada variável representa uma instância diferente. Use structs para tipos pequenos e leves cujo papel principal é armazenar dados em vez de modelar o comportamento. Exemplos incluem coordenadas, cores, medições ou definições de configuração.

Quando usar structs

Usa um struct quando estiveres a escrever:

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

Para uma comparação mais ampla que inclua classes, registos, tuplas e interfaces, veja Escolher que tipo de tipo.

Declarar uma estrutura

Defina uma estrutura com a struct palavra-chave. Uma struct pode conter campos, propriedades, métodos e construtores, tal 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})";
}

A Point estrutura 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 da estrutura. Esse padrão está coberto por membros apenas relegados.

Semântica de valores

Estruturas são tipos de valor. Assignment copia os dados, de modo que cada variável contém a 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 as structs são contentores de dados, a atribuição copia cada membro de dados para uma nova instância independente. Cada cópia é distinta. Modificar um não afeta o outro. Este comportamento difere das classes, onde a atribuição copia apenas a referência e ambas as variáveis partilham o mesmo objeto. Para mais informações sobre a distinção, veja Tipos de valor e tipos de referência.

Construtores de estruturas

Podes definir construtores em structs da mesma forma que fazes nas classes. As structs podem ter construtores sem parâmetros que definem valores predefinidos personalizados. O termo "construtor sem parâmetros" distingue uma instância criada com new (que executa a lógica do construtor) da 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 corre quando se usa new sem argumentos. A default expressão ignora o construtor e define todos os campos para os seus valores padrão (0, null, false). Esteja atento à 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 quaisquer campos que não definas explicitamente num construtor. Podes inicializar apenas os campos que necessitam 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 seguinte apresenta 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, por isso o compilador define-a como false (o padrão para bool). Esta funcionalidade reduz o boilerplate em construtores que só precisam de definir alguns campos.

Estruturas readonly e membros readonly

A readonly struct garante que nenhum membro da instância modifica o estado da estrutura. O compilador faz cumprir esta garantia exigindo que todos os campos e propriedades auto-implementadas sejam apenas de 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 seguinte cria uma Temperature instância e lê as 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 não precisares que toda a estrutura seja imutável, marca os membros individuais como readonly. Um readonly membro não pode modificar o estado da estrutura, 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 seguinte 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 cópias defensivas. Quando passa uma readonly struct para um método que aceita um in parâmetro, o compilador sabe que não é necessária nenhuma cópia.

Consulte também