Uma estrutura é, em seu cerne, nada mais nada menos do que uma agregação de campos. No .NET é possível que uma estrutura "finja" ser um objeto, e para cada tipo de estrutura o .NET define implicitamente um tipo de objeto heap com os mesmos campos e métodos que - sendo um objeto heap - se comportarão como um objeto . Uma variável que contém uma referência a tal objeto heap (estrutura "em caixa") exibirá semântica de referência, mas aquela que contém uma estrutura diretamente é simplesmente uma agregação de variáveis.
Acho que grande parte da confusão estrutura versus classe vem do fato de que as estruturas têm dois casos de uso muito diferentes, que devem ter diretrizes de design muito diferentes, mas as diretrizes da MS não fazem distinção entre eles. Às vezes, há necessidade de algo que se comporte como um objeto; nesse caso, as diretrizes da MS são bastante razoáveis, embora o "limite de 16 bytes" provavelmente deva ser mais parecido com 24-32. Às vezes, no entanto, o que é necessário é uma agregação de variáveis. Uma estrutura usada para essa finalidade deve consistir simplesmente em um monte de campos públicos e, possivelmente, uma Equals
substituição, uma ToString
substituição eIEquatable(itsType).Equals
implementação. Estruturas que são usadas como agregações de campos não são objetos e não deveriam fingir ser. Do ponto de vista da estrutura, o significado de campo deve ser nada mais nada menos que "a última coisa escrita neste campo". Qualquer significado adicional deve ser determinado pelo código do cliente.
Por exemplo, se uma estrutura de agregação de variável tem membros Minimum
e Maximum
, a própria estrutura não deve prometer isso Minimum <= Maximum
. Código que recebe a estrutura tal como um parâmetro deve se comportar como se fosse aprovada separada Minimum
e Maximum
valores. Um requisito que Minimum
não seja maior do que Maximum
deve ser considerado como um requisito de que um Minimum
parâmetro não seja maior do que um aprovado separadamente Maximum
.
Um padrão útil a ser considerado às vezes é ter uma ExposedHolder<T>
classe definida como:
class ExposedHolder<T>
{
public T Value;
ExposedHolder() { }
ExposedHolder(T val) { Value = T; }
}
Se alguém tem um List<ExposedHolder<someStruct>>
, onde someStruct
é uma estrutura de agregação de variável, pode-se fazer coisas como myList[3].Value.someField += 7;
, mas fornecer myList[3].Value
a outro código fornecerá o conteúdo de, em Value
vez de fornecer um meio de alterá-lo. Por outro lado, se um usasse um List<someStruct>
, seria necessário usar var temp=myList[3]; temp.someField += 7; myList[3] = temp;
. Se alguém usasse um tipo de classe mutável, expor o conteúdo do myList[3]
código externo exigiria a cópia de todos os campos para algum outro objeto. Se alguém usasse um tipo de classe imutável ou uma estrutura de "estilo de objeto", seria necessário construir uma nova instância semelhante, myList[3]
exceto pela someField
qual fosse diferente, e então armazenar essa nova instância na lista.
Uma observação adicional: se você estiver armazenando um grande número de coisas semelhantes, pode ser bom armazená-las em matrizes de estruturas possivelmente aninhadas, de preferência tentando manter o tamanho de cada matriz entre 1K e 64K ou algo assim. Os arranjos de estruturas são especiais, na medida em que uma indexação produzirá uma referência direta a uma estrutura interna, então pode-se dizer "a [12] .x = 5;". Embora seja possível definir objetos semelhantes a matrizes, C # não permite que eles compartilhem essa sintaxe com matrizes.