Converter cegamente estruturas em classes para ocultar o construtor padrão?
-
07-07-2019 - |
Pergunta
Eu li todas as perguntas relacionadas a este tópico, e todas dão razões pelas quais um construtor padrão em um struct
Não está disponível em C#, mas ainda não encontrei ninguém que sugere um curso geral de ação quando confrontado com essa situação.
A solução óbvia é simplesmente converter o struct
para um class
e lidar com as consequências.
Existem outras opções para mantê -lo como um struct
?
Eu encontrei essa situação com um de nossos objetos internos de API de comércio. O designer o converteu de um class
para um struct
, e agora o construtor padrão (que era privado antes) deixa o objeto em um estado inválido.
Eu pensei que se vamos manter o objeto como um struct
, um mecanismo para verificar a validade do estado deve ser introduzido (algo como um IsValid
propriedade). Recebi muita resistência e uma explicação de "quem usa a API não deve usar o construtor padrão", um comentário que certamente levantou minhas sobrancelhas. (Nota: o objeto em questão é construído "adequadamente" através de métodos estáticos de fábrica, e todos os outros construtores são internal
.)
Todo mundo está simplesmente convertendo seu struct
s para class
ES nesta situação sem pensar segundo?
Editar: eu gostaria de ver algumas sugestões sobre como manter esse tipo de objeto como um struct
- O objeto em questão acima é muito mais adequado como um struct
do que como um class
.
Solução
Para struct
, você projeta o tipo para que a instância construída padrão (campos todos zero) seja um estado válido. Você não [não deve] use arbitrariamente struct
ao invés de class
Sem um bom motivo - não há nada de errado em usar um tipo de referência imutável.
Minhas sugestões:
- Verifique se o motivo do uso de um
struct
é válido (um perfilador [real] revelou problemas de desempenho significativos resultantes da alocação pesada de um objeto muito leve). - Projete o tipo para que a instância construída padrão seja válida.
- Se o design do tipo for ditado por restrições de interoperas nativas/com, embrulhe a funcionalidade e não exponha o
struct
Fora do invólucro (tipo aninhado privado). Dessa forma, você pode documentar e verificar facilmente o uso adequado dos requisitos de tipo restrito.
Outras dicas
A razão para isso é que uma estrutura (uma instância do sistema.valuetype) é tratada especialmente pelo CLR: é inicializada com todos os campos sendo 0 (ou padrão). Você nem precisa criar um - basta declará -lo. É por isso que os construtores padrão são necessários.
Você pode contornar isso de duas maneiras:
- Crie uma propriedade como o ISValid para indicar se é uma estrutura válida, como você indica e
- no .NET 2.0 Considere usar o Nullableu003CT> Para permitir uma estrutura não inicializada (nula).
Alterar a estrutura para uma classe pode ter algumas consequências muito sutis (em termos de uso de memória e identidade de objetos que surgem mais em um ambiente multithread) e não-sutil, mas difícil de depurar o NullReferenceExceptions para objetos não inicializados.
A razão pela qual não há possibilidade de definir um construtor padrão é ilustrado pela seguinte expressão:
new MyStruct[1000];
Você tem 3 opções aqui
- chamando o construtor padrão 1000 vezes, ou
- Criando dados corrompidos (observe que uma estrutura pode conter referências; se você não inicializar ou emanhar a referência, poderá acessar a memória arbitrária) ou
- em branco a memória alocada com zeros (no nível do byte).
.NET faz o mesmo para estruturas e classes: campos e elementos da matriz são apagados com zeros. Isso também recebe um comportamento mais consistente entre estruturas e classes e nenhum código inseguro. Ele também permite que a estrutura .NET não seja especializada em algo como new byte[1000]
.
E esse é o construtor padrão para estruturas .NET exige e cuida de si mesma: zero todos os bytes.
Agora, para lidar com isso, você tem algumas opções:
- Adicione uma propriedade AM-i-inicializada à estrutura (como
HasValue
sobreNullable
). - Permita que a estrutura zero seja um valor válido (como 0 é um valor válido para um decimal).