Conteúdo

Performance em .NET: Como utilizar structs corretamente

Structs são um tipo de dado muito poderoso introduzida no C# desde sua primeira versão. Embora muito poderosa, muitos desenvolvedores não sabem como usá-las corretamente, ou nem sabem que elas existem. Isso pode levar a problemas de performance, segurança e qualidade do código, pois structs tem características e comportamentos diferentes de outros tipos de dados, como classes e records.

O que são Structs

Structs são tipos de valor em C#, ou seja, eles armazenam diretamente o valor na memória, na stack. Isso significa que, quando você cria uma variável de um tipo struct, ela contém o valor do objeto, e não apenas um ponteiro para ele. Por exemplo, se você tem uma struct que representa saldo em estoque, ela terá três propriedades do tipo int, uma para o código do produto, uma para a quantidade e outra para o armazém. Quando você cria uma variável do tipo estoque, ela terá o valor do estoque, e não um endereço de memória.

Structs devem ser imutáveis, o que significa que eles não podem ser modificados depois de criados. Isso pode evitar bugs e tornar o código mais seguro e fácil de entender. Por exemplo, se você tentar alterar o valor de uma propriedade de uma struct, você terá um erro de compilação.

Structs podem representar conceitos simples e coesos. Isso pode aumentar a legibilidade e expressividade do código, pois você pode criar tipos de valor que se adequam ao seu domínio e às suas necessidades.

O que são tipos primitivos e por que struct trabalha melhor com tipos primitivos?

Tipos primitivos são os tipos de dados mais básicos e fundamentais em C#, que representam valores simples, como números, caracteres, booleanos, etc. Por exemplo, int, char, bool, double, etc. são tipos primitivos em C#. Tipos primitivos são tipos de valor, ou seja, eles armazenam diretamente o valor na memória, na stack.

Struct trabalha melhor com tipos primitivos porque eles também são tipos de valor, e têm características e comportamentos semelhantes. Por exemplo, structs e tipos primitivos são imutáveis, ocupam pouco espaço na memória, são copiados com frequência, e têm um comportamento personalizado e consistente. Isso significa que structs e tipos primitivos podem ser combinados de forma eficiente e eficaz, sem causar problemas de performance, igualdade e mutabilidade.

Por exemplo, se você criar uma struct que contém apenas tipos primitivos, como int, char, bool, etc., você terá uma struct simples e coesa, que representa um conceito imutável e indivisível, que ocupa pouco espaço na memória, que é copiada com frequência, e que tem um comportamento personalizado e consistente. Você poderá comparar, ordenar, manipular e armazenar essa struct sem problemas.

Quais são os principais erros ao usar Struct?

Um dos principais erros ao usar struct é não entender o que é uma struct e como ela funciona. Muitos desenvolvedores usam structs sem saber o que elas são, como elas se comportam na memória, quais são as suas vantagens e desvantagens, e quais são as boas práticas e os erros comuns. Isso pode levar a problemas de performance, segurança e qualidade do código, pois structs têm características e comportamentos diferentes de outros tipos de dados, como classes e records.

Outro erro comum ao usar struct é usar structs híbridas, ou seja, structs que contenham tipos de referência, como strings, dentro de si. Isso pode causar problemas de performance, igualdade e mutabilidade, pois você perde as vantagens de usar um tipo de valor.

O que é uma Structs híbridas?

Structs híbridas são structs que contêm tipos de referência, como strings, dentro de si. Isso pode causar problemas de performance, mutabilidade – desde que você não o trate – e igualdade, pois você perde as vantagens de usar um tipo de valor. Por exemplo, se você criar uma struct que representa estoque, ela terá uma propriedade do tipo string, que é um tipo de referência, para o código do produto. Quando você cria uma variável do estoque, ela não armazena diretamente o valor da string, mas sim um ponteiro para ela. Isso significa que, quando você compara ou copia essa struct, você não está comparando ou copiando o valor da string, mas sim o ponteiro para ela.

Um dos problemas de usar structs híbridas é que elas podem gerar resultados falsos ao comparar duas structs usando o operador ==, pois ele vai comparar apenas os ponteiros, e não os valores das strings. Por exemplo, se você criar outra variável do tipo produto com os mesmos valores, e compará-la com a primeira usando o operador ==, você vai obter um resultado falso, pois os ponteiros são diferentes.

Outro problema de usar structs híbridas é que elas podem gerar inconsistências ao copiar uma struct para outra, pois elas vão copiar apenas o ponteiro, e não o valor da string. Por exemplo, se você alterar o valor da string em uma das structs, você vai alterar o valor na outra também, pois elas apontam para o mesmo endereço de memória.

Classes e Records são uma alternativa para struct híbrida

Uma alternativa melhor para usar structs híbridas é usar classes ou records, que são tipos de referência, e que podem conter outros tipos de referência, como strings, sem problemas. Por exemplo, você poderia usar uma class ou um record para representar um estoque, e compará-los e copiá-los sem problemas.

Struct, por padrão, tem limitações de tamanho de memória, o uso de struct híbrida pode causar problemas de segurança

Uma das vantagens de usar structs é que elas ocupam pouco espaço na memória, pois elas são armazenadas na stack, que é uma região de memória rápida e limitada. Isso significa que structs podem ser criadas e destruídas rapidamente, sem causar muita sobrecarga de memória.

No entanto, essa vantagem também pode se tornar uma desvantagem, se você usar structs muito grandes, ou seja, structs que contenham muitos campos ou propriedades, ou que contenham tipos de referência, como strings, dentro de si. Isso pode causar problemas de consumo de memória e tempo de execução, pois você pode exceder o limite da pilha ou copiar dados desnecessariamente.

Por exemplo, se você usar uma struct híbrida, que contém um tipo de referência, como uma string, dentro de si, você está misturando tipos de valor e tipos de referência, e isso pode gerar uma ineficiência de memória. Isso porque, quando você cria uma variável de um tipo struct híbrida, ela não armazena diretamente o valor da string, mas sim um ponteiro para ela, que está armazenado no monte (heap), que é uma região de memória lenta e ilimitada. Isso significa que, quando você copia ou compara essa struct, você não está copiando ou comparando o valor da string, mas sim o ponteiro para ela, que pode estar em um local diferente na memória.

Isso pode causar um desperdício de memória, pois você está usando mais espaço do que o necessário para armazenar a struct, e um desperdício de tempo, pois você está acessando mais de uma região de memória para manipular a struct. Além disso, isso pode gerar resultados falsos ou inconsistentes, pois você pode estar comparando ou alterando ponteiros, e não valores.

Por isso, é recomendado que você use structs simples, que contenham apenas tipos de valor, como int, char, bool, etc., dentro de si, e que ocupem pouco espaço na memória. Isso pode evitar problemas de performance, igualdade e mutabilidade, e aproveitar as vantagens de usar um tipo de valor.

Comparando struct e struct híbrida

O benchmark compara o tempo de execução e o consumo de memória de dois métodos: UsingStruct e UsingHybridStruct. Esses métodos criam e retornam uma lista de 1000 objetos de um tipo struct, sendo que o primeiro usa uma struct simples, que contém apenas tipos de valor, e o segundo usa uma struct híbrida, que contém um tipo de referência, como uma string, dentro de si.

Os resultados do benchmark mostram que o método UsingStruct é mais rápido e mais eficiente do que o método UsingHybridStruct, pois ele tem um tempo médio de execução menor (28.97 ns contra 34.21 ns), um erro padrão menor (0.212 ns contra 0.413 ns), e um consumo de memória menor (0 bytes contra 29 bytes).

MétodoTempo médioErro padrãoDesvio padrãoRazãoGen0Memória alocadaRazão de alocação
UsingStruct28.97 ns0.239 ns0.212 ns1.00NA
UsingHybridStruct34.21 ns0.442 ns0.413 ns1.180.006929 BNA

Isso confirma o que escrevemos anteriormente, sobre as vantagens e desvantagens de usar structs e structs híbridas. Nós dissemos que structs são tipos de valor que podem melhorar a performance e a qualidade do código em C#, se usadas corretamente, pois elas ocupam pouco espaço na memória, são imutáveis por padrão, e têm um comportamento personalizado e consistente. Nós também dissemos que structs híbridas são structs que contêm tipos de referência, como strings, dentro de si, e que podem causar problemas de performance, igualdade e mutabilidade, pois elas perdem as vantagens de usar um tipo de valor.

Conclusão

Ao utilizar structs, que são tipos de valor armazenados na pilha (stack), teremos o foco em performance, pois elas ocupam pouco espaço na memória e são criadas e destruídas rapidamente. Entretanto, não entender como elas funcionam pode causar problemas de consumo de memória, tempo de execução, igualdade e mutabilidade. Evitar usar structs híbridas, que contêm tipos de referência, como strings, dentro de si, tornará o seu código mais seguro e consistente, pois elas perdem as vantagens de usar um tipo de valor. Troque structs híbridas por classes ou records, que são tipos de referência, e que podem conter outros tipos de referência, sem problemas. Assim, você poderá usar structs com critério e conhecimento, e melhorar a qualidade do seu código em C#.

FAQ: Perguntas Frequentes

1. O que são structs em C#?

Structs são tipos de valor em C#, que armazenam diretamente o valor na memória, na pilha (stack). Structs são imutáveis por padrão, e podem implementar interfaces e sobrecarregar operadores. Structs podem representar conceitos simples e coesos, como coordenadas, pontos, cores, números complexos, etc.

2. O que são structs híbridas em C#?

Structs híbridas são structs que contêm tipos de referência, como strings, dentro de si. Isso pode causar problemas de performance, igualdade e mutabilidade, pois elas perdem as vantagens de usar um tipo de valor. Structs híbridas podem gerar resultados falsos ao comparar ou copiar structs, pois elas comparam ou copiam apenas os ponteiros, e não os valores das strings.

3. Quais são as vantagens de usar structs em C#?

As vantagens de usar structs em C# são: elas ocupam pouco espaço na memória, elas são imutáveis por padrão, elas têm um comportamento personalizado e consistente, elas podem melhorar a legibilidade e expressividade do código, elas podem aumentar a segurança e evitar bugs.

4. Quais são as desvantagens de usar structs em C#?

As desvantagens de usar structs em C# são: elas podem exceder o limite da pilha, elas podem copiar dados desnecessariamente, elas podem gerar inconsistências ao implementar interfaces e sobrecarregar operadores, elas podem ser confundidas com classes ou records, elas podem não ser adequadas para representar conceitos complexos e mutáveis.

5. Como evitar structs híbridas em C#?

Para evitar structs híbridas em C#, você pode usar classes ou records, que são tipos de referência, e que podem conter outros tipos de referência, como strings, sem problemas. Você também pode usar o modificador get; init;, que permite inicializar a propriedade apenas uma vez, e depois não permite que ela seja alterada. Você também pode usar o modificador readonly, que impede que a struct seja modificada depois de criada.

Compartilhe:

Tiago Tartari

Tiago Tartari

Eu ajudo e capacito pessoas e organizações a transformar problemas complexos em soluções práticas usando a tecnologia para atingir resultados extraordinários.

Qual é o desafio
que você tem hoje?