Tipos de estrutura (referência de C#)
Um tipo de estrutura (ou tipo de struct) é um tipo de valor que pode encapsular dados e funcionalidades relacionadas. Você usa a palavra-chave struct
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 os tipos ref struct
e readonly ref struct
, confira 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 de método. Para variáveis de tipo de estrutura, uma instância do tipo é copiada. Para obter mais informações, confira Tipos de valor.
Normalmente, você usa tipos de estrutura para criar 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 booliano, 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.
Struct readonly
Use o modificador readonly
para declarar que um tipo de estrutura é imutável. Todos os membros de dados de um struct readonly
devem ser somente leitura da seguinte maneira:
- Qualquer declaração de campo deve ter o modificador
readonly
- Qualquer propriedade, incluindo as implementadas automaticamente, deve ser somente leitura ou
init
apenas. Observe que os setters somente init estão disponíveis somente em C# versão 9 em diante.
Isso garante que nenhum membro de um struct readonly
modifique o estado do struct. Isso significa que outros membros de instância, exceto constructos, são implicitamente readonly
.
Observação
Em um struct readonly
, um membro de dados de um tipo de referência mutável ainda pode alterar seu próprio estado. Por exemplo, você não pode substituir uma instância List<T>, mas pode adicionar novos elementos a ela.
O seguinte código define um struct readonly
com setters de propriedade somente inicialização:
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})";
}
Membros da instância readonly
Você também pode usar o modificador readonly
para declarar que um membro de instância não modifica o estado de um struct. Se você não puder declarar todo o tipo de estrutura como readonly
, use o modificador readonly
para marcar os membros da instância que não modificam o estado do struct.
Em um membro de instância readonly
, você não pode atribuir aos campos de instância da estrutura. No entanto, um membro readonly
pode chamar um não membro readonly
. Nesse caso, o compilador cria uma cópia da instância da estrutura e chama o membro não readonly
nessa cópia. Como resultado, a instância de estrutura original não é modificada.
Normalmente, você aplica o modificador readonly
aos seguintes tipos de membros de instância:
métodos:
public readonly double Sum() { return X + Y; }
Você também pode aplicar o modificador
readonly
aos métodos que substituem os 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 modificador
readonly
a ambos os acessadores de uma propriedade ou indexador, aplique-o na declaração da propriedade ou do indexador.Observação
O compilador declara um
get
acessador de uma propriedade implementada automaticamente comoreadonly
, independentemente da presença do modificador em uma declaração dereadonly
propriedade.Você pode aplicar o modificador
readonly
a uma propriedade ou a um indexador com um acessadorinit
:public readonly double X { get; init; }
Você pode aplicar o modificador readonly
a campos estáticos de um tipo de estrutura, mas não a outros membros estáticos, como propriedades ou métodos.
O compilador pode usar o modificador readonly
para otimizações de desempenho. Para obter mais informações, confira Como evitar alocações.
Mutação não destrutiva
A partir do C# 10, você pode usar a expressãowith
para produzir uma cópia de uma instância do tipo estrutura com as propriedades e campos especificados modificados. Você usa a sintaxe do inicializador de objeto para especificar quais membros modificar e seus novos valores.
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)
}
Struct record
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 os tipos record struct
e readonly record struct
. Um struct de registro não pode ser um ref struct
. Saiba mais e obtenha exemplos em Registros.
Matrizes embutidas
Começando com C# 12, você pode declarar as matrizes embutidas como um tipo struct
:
[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 equivalente de código seguro da declaração buffer fixo disponível apenas em código inseguro. Uma matriz embutida é um struct
com as seguintes características:
- Ele contém um único campo.
- O struct não especifica um layout explícito.
Além disso, o compilador valida o atributo System.Runtime.CompilerServices.InlineArrayAttribute:
- O comprimento deve ser maior que zero (
> 0
). - O tipo de destino deve ser um struct.
Na maioria dos casos, uma matriz embutida pode ser acessada como uma matriz, tanto para leitura quanto para gravação de valores. Além disso, você pode usar os operadores range e index.
Há restrições mínimas no tipo de campo único de uma matriz embutida. Não pode ser um tipo de ponteiro:
[System.Runtime.CompilerServices.InlineArray(10)]
public struct CharBufferWithPointer
{
private unsafe char* _pointerElement; // CS9184
}
mas pode ser qualquer tipo de referência ou qualquer tipo de valor:
[System.Runtime.CompilerServices.InlineArray(10)]
public struct CharBufferWithReferenceType
{
private string _referenceElement;
}
Você pode usar matrizes embutidas com quase qualquer estrutura de dados C#.
As matrizes embutida são um recurso avançado da linguagem. Destinam-se a cenários de alto desempenho em que um bloco de elementos embutidos e contíguo é mais rápido do que outras estruturas de dados alternativas. Você pode aprender mais sobre matrizes embutidas a partir da funcionalidade speclet
Inicialização de struct e valores padrão
Uma variável de um tipo struct
contém diretamente os dados para struct
. Isso cria uma distinção entre um valor não inicializado struct
, que tem seu valor padrão e um inicializado struct
, que armazena valores definidos pela construção dele. Por exemplo, considere o código a seguir:
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ê verá valores padrão está em matrizes ou em outras coleções em que o armazenamento interno inclui blocos de variáveis. O exemplo a seguir cria uma matriz de 30 estruturas TemperatureRange
, cada uma com o valor padrão:
// All elements have default values of 0:
TemperatureRange[] lastMonth = new TemperatureRange[30];
Todos os campos membros de um struct precisam ser atribuídos definitivamente quando ele é criado porque os tipos struct
armazenam dados diretamente. O valor default
de um struct definitivamente atribuiu todos os campos a 0. Todos os campos devem ser atribuídos definitivamente 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 do C# 11, se você não inicializar todos os campos em um struct, o compilador adicionará código ao construtor que inicializa esses campos ao valor padrão. O compilador executa sua análise de atribuição definida habitual. Todos os campos acessados antes de serem atribuídos ou não atribuídos definitivamente quando o construtor terminar de executar receberão seus valores padrão antes da execução do corpo do construtor. Se this
for acessado antes de todos os campos serem atribuídos, o struct será inicializado para 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
tem um construtor sem parâmetro public
. Se você escrever um construtor sem parâmetros, ele deverá ser público. Se um struct declarar qualquer inicializador de campo, ele deverá declarar explicitamente um constructo. Esse constructo não precisa ser sem parâmetros. Se um struct declarar um inicializador de campo, mas nenhum constructo, o compilador relatará um erro. Qualquer constructo declarado explicitamente (com parâmetros ou sem parâmetros) executa todos os inicializadores de campo para esse struct. Todos os campos sem um inicializador de campo ou uma atribuição em um constructo são definidos como valor padrão. Para obter mais informações, consulte nota de proposta de recurso de construtores de struct sem parâmetros.
Começando no C# 12, tipos struct
podem definir um construtor primário como parte de sua declaração. Os construtores primários fornecem uma sintaxe concisa para os parâmetros do construtor que podem ser usados em todo o corpo struct
, em qualquer declaração de membro para esse struct.
Se todos os campos de instância de um tipo de estrutura estiverem acessíveis, você também poderá instanciá-lo sem o operador new
. Nesse caso, você deve inicializar todos os campos de instância antes do primeiro uso da instância. O seguinte exemplo 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 design de um tipo de estrutura
Os structs têm a maioria dos recursos de um tipo de classe. Há algumas exceções e algumas exceções que foram removidas em versões mais recentes:
- Um tipo de estrutura não pode herdar de outro tipo de classe ou estrutura e não pode ser a base de uma classe. No entanto, um tipo de estrutura pode implementar interfaces.
- Você não pode 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.
Passando 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 por valor pode afetar o desempenho do código em cenários de alto desempenho que envolvem tipos de estrutura grandes. Você pode evitar a cópia de valor passando uma variável de tipo de estrutura por referência. Use os modificadores de parâmetro ref
, out
, in
ou ref readonly
método para indicar que um argumento deve ser passado por referência . Use ref returns para retornar um resultado de método por referência. Para obter mais informações, confira Evitar alocações.
restrição de struct
Você também usa a palavra-chave struct
na restrição struct
para especificar que um parâmetro de tipo é um tipo de valor não anulável. Os tipos de estrutura e enumeração atendem à restrição struct
.
Conversões
Para qualquer tipo de estrutura (exceto tipos ref struct
), existem conversões de boxing e unboxing de e para os tipos System.ValueType e System.Object. Também existem conversões de boxing e unboxing entre um tipo de estrutura e qualquer interface que ele implemente.
Especificação da linguagem C#
Para saber mais, confira a seção Structs da Especificação da linguagem C#.
Para mais informações sobre struct
recursos, consulte as seguintes notas sobre a proposta de recurso:
- Structs somente leitura
- Membros da instância readonly
- C# 10 - Constructos de struct sem parâmetros
- C# 10 - Permitir
with
expressão em structs - C# 10 - Registrar structs
- C# 11 - Structs de padrão automático