Garantir a imutabilidade é uma justificativa para a exposição de um campo em vez de uma propriedade?

10

A orientação geral para C # é sempre usar uma propriedade em um campo público. Isso faz sentido - ao expor um campo, você expõe muitos detalhes da implementação. Com uma propriedade, você encapsula esses detalhes para que não ocorram o consumo de código, e as alterações na implementação são dissociadas das alterações na interface.

No entanto, estou me perguntando se às vezes há uma exceção válida a essa regra ao lidar com a readonlypalavra - chave. Ao aplicar essa palavra-chave a um campo público, você oferece uma garantia extra: imutabilidade. Este não é apenas um detalhe de implementação; a imutabilidade é algo em que um consumidor pode se interessar. O uso de um readonlycampo faz parte do contrato público e algo que não pode ser quebrado por alterações ou herança futuras sem a necessidade de modificar a interface pública. Isso é algo que uma propriedade não pode oferecer.

Portanto, garantir a imutabilidade é uma razão legítima para escolher um readonlycampo em vez de uma propriedade em alguns casos?

(Para esclarecimento, certamente não estou dizendo que você deve sempre fazer essa escolha apenas porque o campo é imutável no momento, apenas quando faz sentido como parte do design da classe e se seu uso pretendido incluir imutabilidade em seu contrato. Estou interessado principalmente em respostas focadas em saber se isso pode ser justificado, em vez de casos específicos em que não é, como quando você precisa que o membro esteja em um interfaceou deseja fazer um carregamento lento.)

Ben Aaronson
fonte
11
@gnat Apenas para esclarecer, por "Propriedade ReadOnly" eles estão usando a terminologia VB.NET, que significa uma propriedade sem setter, não um campo somente leitura. Para os fins da minha pergunta, as duas opções que essa pergunta compara são equivalentes - ambas estão usando propriedades.
Ben Aaronson

Respostas:

6

Campos públicos estáticos somente leitura certamente estão bem. Eles são recomendados para determinadas situações, como quando você deseja um objeto constante e nomeado, mas não pode usar a constpalavra - chave.

Os campos de membros somente leitura são um pouco mais complicados. Não há muita vantagem sobre uma propriedade sem um setter público e há uma grande desvantagem: os campos não podem ser membros de interfaces. Portanto, se você decidir refatorar seu código para operar contra uma interface em vez de um tipo concreto, precisará alterar seus campos somente leitura para propriedades com getters públicos.

Uma propriedade pública não virtual sem um setter protegido fornece proteção contra herança, a menos que as classes herdadas tenham acesso ao campo de suporte. Você pode impor completamente esse contrato na classe base.

Não poder alterar a "imutabilidade" do membro sem alterar a interface pública da classe não é uma grande barreira nesse caso. Geralmente, se você deseja transformar um campo público em uma propriedade, tudo corre bem, exceto que há dois casos com os quais você precisa lidar:

  1. Qualquer coisa que escreva para um membro desse objeto, como: object.Location.X = 4só é possível se Locationfor um campo. Isso não é relevante para um campo somente leitura, porque você não pode modificar uma estrutura somente leitura. (Isso pressupõe que Locationé um tipo de valor - caso contrário, esse problema não se aplicaria de qualquer maneira, porque readonlynão protege contra esse tipo de coisa.)
  2. Qualquer chamada para um método que transmita o valor como outou ref. Novamente, isso não é realmente relevante para um campo somente leitura, porque oute os refparâmetros tendem a ser modificados, e é um erro do compilador passar uma valor somente leitura como out ou ref.

Em outras palavras, embora a conversão de um campo somente leitura em uma propriedade somente leitura quebre a compatibilidade binária, outro código-fonte precisaria ser recompilado apenas sem nenhuma alteração para lidar com essa alteração. Isso torna a garantia realmente fácil de quebrar.

Não vejo nenhuma vantagem no readonlycampo de membro, e a incapacidade de ter esse tipo de coisa em uma interface é uma desvantagem suficiente para mim que eu não a usaria.

Erik
fonte
Fora de interesse, porque são public static readonly campos ok? É porque excluir os métodos de instância remove a maioria dos casos de uso de propriedades em campos imutáveis ​​(como contagem de hits, carregamento lento etc.)?
Ben Aaronson
A principal desvantagem de um campo público somente leitura versus propriedade somente leitura se foi - os membros estáticos não podem fazer parte das interfaces, portanto, estão em pé de igualdade. Uma propriedade estática somente leitura precisa de armazenamento que seja inicializado ou precisa reconstruir seu valor de retorno toda vez que for chamada, enquanto um campo somente leitura terá sintaxe mais simples e será inicializado pelo construtor estático. Portanto, acho que um campo estático público somente para leitura tem o potencial de maior otimização pelo JIT, sem nenhuma das desvantagens que você normalmente teria ao expor um campo.
Erik
2

Pela minha experiência, a readonlypalavra-chave não é a mágica que promete.

Por exemplo, você tem uma classe em que o construtor costumava ser simples. Dado o uso (e o fato de que algumas propriedades / campos da classe são imutáveis ​​após a construção), você pode pensar que isso não importa, por isso usou a readonlypalavra - chave.

Mais tarde, a classe se torna mais complexa, assim como seu construtor. (Digamos que isso aconteça em um projeto experimental ou que tenha uma velocidade alta o suficiente fora do escopo / controle, mas você precisa fazê-lo funcionar de alguma forma.) Você descobrirá que os campos que readonlypodem ser modificados somente dentro do construtor - você não pode modificá-lo nos métodos, mesmo que esse método seja chamado apenas de um construtor.

Essa limitação técnica foi reconhecida pelos designers do C # e pode ser corrigida em uma versão futura. Mas até que seja corrigido, o uso de readonlyé mais restritivo e pode incentivar algum estilo de codificação ruim (colocando tudo no método construtor).

Como um lembrete, readonlynem a propriedade não configuradora pública fornece qualquer garantia de um "objeto totalmente inicializado". Em outras palavras, é o projetista de uma classe que decide o que significa "totalmente inicializado" e como protegê-lo contra o uso inadvertido, e essa proteção geralmente não é infalível, o que significa que alguém pode encontrar uma maneira de contornar isso.

rwong
fonte
11
Eu prefiro manter construtores simples - veja brendan.enrick.com/post/... , blog.ploeh.dk/2011/03/03/InjectionConstructorsshouldbesimple Então, na verdade, somente leitura incentiva bom estilo de codificação
AlexFoxGill
11
Um construtor pode passar um readonlycampo como parâmetro outou refpara outros métodos, que estarão livres para modificá-lo como entenderem.
Supercat 21/07
1

Os campos são SEMPRE detalhes de implementação. Você não deseja expor seus detalhes de implementação.

Um princípio fundamental da engenharia de software é que não sabemos quais serão os requisitos no futuro. Embora seu readonlycampo possa ser bom hoje, não há nada que sugira que ele fará parte dos requisitos de amanhã. E se amanhã for necessário implementar um contador de visitas para determinar quantas vezes esse campo é acessado? E se você precisar permitir que as subclasses substituam o campo?

As propriedades são tão fáceis de usar e são muito mais flexíveis que os campos públicos que não consigo pensar em um único caso de uso em que eu escolheria c # como meu idioma e usaria campos públicos somente de leitura em uma classe. Se o desempenho fosse tão baixo que eu não aguentasse a chamada de método extra, provavelmente usaria um idioma diferente.

Só porque nós pode fazer alguma coisa com a língua não significa que deve fazer algo com o idioma.

Eu realmente me pergunto se os designers de idiomas se arrependem de permitir que os campos sejam divulgados. Não me lembro da última vez que considerei usar campos públicos não const no meu código.

Stephen
fonte
11
Entendo a importância de criar flexibilidade futura, mas certamente, se eu escrever uma classe como, digamos, Rationalcom público, imutável Numeratore Denominatormembros, posso estar extremamente confiante de que esses membros não exigirão carregamento lento ou um contador de visitas ou semelhante?
precisa
Mas você pode ter certeza de que a implementação usando floattipos para Numeratore Denominatornão precisará ser alterada doubles?
Stephen
11
Bem, er, não, eles definitivamente não deveriam ser nem floatnem double. Eles deveriam ser int, longou BigInteger. Talvez eles precisem mudar ... mas, se assim for, também precisariam mudar na interface pública, então realmente usar propriedades não adiciona encapsulamento em torno desses detalhes.
Ben Aaronson
-3

Não. Não é uma justificativa.

Usar o readonly como garantia de imutabilidade em não uma justificativa é mais como um hack.

Somente leitura significa que o valor é atribuído por um construtor o quando a variável é definida.

Eu acho que vai ser uma má prática

Se você realmente deseja garantir a imutabilidade, use uma interface com mais prós do que contras.

Exemplo de interface simples: insira a descrição da imagem aqui

Pablo Granados
fonte
11
"use uma interface" Como?
Ben Aaronson