Estruturas com campos ou propriedades públicas mutáveis não são más.
Os métodos Struct (que diferem dos configuradores de propriedades) que modificam "isso" são um tanto ruins, apenas porque o .net não fornece um meio de distingui-los dos métodos que não o fazem. Os métodos Struct que não modificam "this" devem ser invocáveis, mesmo em estruturas somente leitura, sem a necessidade de cópia defensiva. Os métodos que modificam "this" não devem ser invocáveis em estruturas somente leitura. Como o .net não deseja proibir que métodos struct que não modificam "this" sejam invocados em estruturas somente leitura, mas não deseja permitir que estruturas somente leitura sejam alteradas, copia defensivamente as estruturas em read- apenas contextos, sem dúvida o pior dos dois mundos.
Apesar dos problemas com o manuseio de métodos auto-mutantes em contextos somente leitura, as estruturas mutáveis geralmente oferecem semântica muito superior aos tipos de classe mutáveis. Considere as três assinaturas de método a seguir:
struct PointyStruct {public int x, y, z;};
classe PointyClass {public int x, y, z;};
Método void1 (PointyStruct foo);
void Method2 (ref PointyStruct foo);
Método void3 (PointyClass foo);
Para cada método, responda às seguintes perguntas:
- Supondo que o método não use nenhum código "inseguro", ele pode ser modificado?
- Se nenhuma referência externa a 'foo' existir antes do método ser chamado, uma referência externa poderá existir depois?
Respostas:
Pergunta 1::
Method1()
não (intenção clara)
Method2()
: sim (intenção clara)
Method3()
: sim (intenção incerta)
Pergunta 2
Method1()
:: não
Method2()
: não (a menos que seja inseguro)
Method3()
: sim
O Method1 não pode modificar foo e nunca obtém uma referência. O Method2 obtém uma referência de curta duração a foo, que pode ser usada para modificar os campos de foo inúmeras vezes, em qualquer ordem, até que ele retorne, mas não pode persistir nessa referência. Antes que o Method2 retorne, a menos que use código não seguro, toda e qualquer cópia que possa ter sido feita com sua referência 'foo' desaparecerá. O Method3, ao contrário do Method2, obtém uma referência promiscuamente compartilhável ao foo, e não há como dizer o que ele pode fazer com ele. Pode não mudar nada, pode mudar novamente e, em seguida, retornar ou pode dar uma referência a outro tópico que pode ser modificado de alguma maneira arbitrária em algum momento futuro arbitrário.
Matrizes de estruturas oferecem semântica maravilhosa. Dado RectArray [500] do tipo Rectangle, é claro e óbvio como, por exemplo, copiar o elemento 123 para o elemento 456 e, algum tempo depois, definir a largura do elemento 123 para 555, sem perturbar o elemento 456. "RectArray [432] = RectArray [321 ]; ...; RectArray [123] .Width = 555; ". Saber que Rectangle é uma estrutura com um campo inteiro chamado Width informará tudo o que você precisa saber sobre as instruções acima.
Agora, suponha que RectClass fosse uma classe com os mesmos campos que Rectangle e que se desejasse realizar as mesmas operações em um RectClassArray [500] do tipo RectClass. Talvez o array deva conter 500 referências imutáveis pré-inicializadas para objetos RectClass mutáveis. nesse caso, o código apropriado seria algo como "RectClassArray [321]. SetBounds (RectClassArray [456]); ...; RectClassArray [321] .X = 555;". Talvez se presuma que o array contém instâncias que não serão alteradas, portanto o código apropriado seria mais parecido com "RectClassArray [321] = RectClassArray [456]; ...; RectClassArray [321] = Novo RectClass (RectClassArray [321 ]); RectClassArray [321] .X = 555; " Para saber o que se deve fazer, é preciso saber muito mais sobre o RectClass (por exemplo, ele suporta um construtor de cópias, um método de copiar de etc.) ) e o uso pretendido da matriz. Nem de longe tão limpo quanto usar uma estrutura.
Certamente, infelizmente, não há uma maneira agradável de qualquer classe de contêiner além de uma matriz oferecer a semântica limpa de uma matriz struct. O melhor que se poderia fazer, se alguém quisesse que uma coleção fosse indexada com, por exemplo, uma string, provavelmente seria oferecer um método "ActOnItem" genérico que aceitaria uma string para o índice, um parâmetro genérico e um delegado que seria passado por referência ao parâmetro genérico e ao item de coleção. Isso permitiria quase a mesma semântica que as matrizes struct, mas, a menos que as pessoas vb.net e C # possam ser perseguidas para oferecer uma boa sintaxe, o código terá uma aparência desajeitada, mesmo que seja razoavelmente desempenho (passar um parâmetro genérico permitir o uso de um representante estático e evitaria a necessidade de criar instâncias de classe temporárias).
Pessoalmente, estou irritado com o ódio de Eric Lippert et al. vomitar sobre tipos de valores mutáveis. Eles oferecem semântica muito mais limpa do que os tipos de referência promíscuos que são usados em todo o lugar. Apesar de algumas das limitações do suporte do .net para tipos de valor, há muitos casos em que os tipos de valor mutável são mais adequados do que qualquer outro tipo de entidade.
int
s,bool
s, e todos os outros tipos de valor são maus. Existem casos de mutabilidade e imutabilidade. Esses casos dependem do papel que os dados desempenham, não do tipo de alocação / compartilhamento de memória.