Em C#, tipos struct
ficam armazenados na Stack e não na Heap. Por isso, são extremamente importantes, principalmente em aplicações onde a performance é aspecto crítico.
Veja também
- Melhorando a performance de aplicações .NET com “Value Types” bem implementados
- Entendendo a Stack (e a StackOverflow Exception)
- Entendendo ponteiros e a heap (free store)
Por definição, tipos struct
deveriam ser sempre imutáveis. Entretanto, há cenários onde faz sentido projetá-los suportando mutabilidade. Eles são perfeitos para a implementação de value objects do DDD e, também, são ótima alternativa para superação da utilização excessiva de tipos tipos primitivos.
A partir do C# 7.2, é possível marcar uma struct
com o modificador readonly
.
public readonly struct Point3 { public Point3(float x, float y, float z) => (X, Y, Z) = (x, y, z); public float X { get; } public float Y { get; } public float Z { get; } public override string ToString() => $"{X}; {Y}; {Z}"; }
Esse recurso permite ao programador confirmar sua intenção de manter a struct
imutável e, também, ao compilador aplicar otimizações importantes.
Por padrão, o compilador decide por criar “cópias de segurança” de tipos struct
potencialmente mutáveis, defensivamente, em alguns cenários. Obviamente, esta “postura defensiva” gera overhead de processamento perceptível.
Marcar uma struct
com readonly
previne que o compilador adote essa “postura defensiva” e melhora a performance.
Eventualmente, entretanto, precisaremos criar tipos struct
que sejam mutáveis (?!).
Importante destacar que tipos struct devem ser imutáveis na grande maioria das situações. [tweet]A necessidade de tornar, por alguma razão, um tipo struct mutável é forte indício de falha de design.[/tweet]
Agora, a partir do C# 8, temos a possibilidade de marcar elementos e não, necessariamente, todo o tipo struct
como imutável.
public struct Point2 { public Point2(float x, float y) => (X, Y) = (x, y); public float X { get; set; } public float Y { get; set; } public readonly double DistanceTo(Point2 other) { var dx = other.X - X; var dy = other.Y - Y; return Math.Sqrt(dx * dx + dy * dy); } public override readonly string ToString() => $"{X}; {Y}"; }
Essa nova feature, além de permitir que escrevamos códigos mais expressivos, previne que alteremos o estado de forma acidental e indesejada. Mais importante ainda, permite ao compilador adotar postura “menos defensiva” durante a execução desses membros impedindo eventuais “cópias de segurança” desnecessárias, melhorando a performance.