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. Observe que os setters somente de inicialização só estão disponíveis a partir da versão 9 do C#.
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 comoreadonly
, independentemente da presença doreadonly
modificador em uma declaração de propriedade.Você pode aplicar o
readonly
modificador a uma propriedade ou indexador com uminit
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 struct
readonly record struct
tipos. Uma estrutura de registro não pode ser um ref struct
arquivo . 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 .
Há restrições mínimas sobre o 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 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 ref
parâ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:
- Estruturas somente leitura
- Membros de instância somente leitura
- C# 10 - Construtores struct sem parâmetros
- C# 10 - Permitir
with
expressão em structs - C# 10 - Estruturas de gravação
- C# 11 - Estruturas padrão automática