Por que devo evitar o uso de propriedades em c #?

Em seu excelente livro, CLR Via C #, Jeffrey Richter disse que não gosta de propriedades e recomenda não usá-las. Ele deu alguma razão, mas eu realmente não entendo. Alguém pode me explicar por que eu deveria ou não usar propriedades? No C # 3.0, com propriedades automáticas, isso muda?

Como referência, acrescentei as opiniões de Jeffrey Richter:

• Uma propriedade pode ser somente leitura ou somente gravação; o access de campo é sempre legível e gravável. Se você definir uma propriedade, é melhor oferecer os methods de access get e set.

• Um método de propriedade pode lançar uma exceção; o access de campo nunca lança uma exceção.

• Uma propriedade não pode ser passada como um parâmetro out ou ref para um método; um campo pode. Por exemplo, o código a seguir não compilará:

using System; public sealed class SomeType { private static String Name { get { return null; } set {} } static void MethodWithOutParam(out String n) { n = null; } public static void Main() { // For the line of code below, the C# compiler emits the following: // error CS0206: A property or indexer may not // be passed as an out or ref parameter MethodWithOutParam(out Name); } } 

• Um método de propriedade pode levar muito tempo para ser executado; o access ao campo sempre é concluído imediatamente. Um motivo comum para usar propriedades é executar a synchronization de encadeamentos, que pode parar o encadeamento para sempre e, portanto, uma propriedade não deve ser usada se a synchronization de encadeamentos for necessária. Nessa situação, um método é preferido. Além disso, se sua class pode ser acessada remotamente (por exemplo, sua class é derivada de System.MashalByRefObject), chamar o método de propriedade será muito lento e, portanto, um método é preferencial a uma propriedade. Na minha opinião, as classs derivadas de MarshalByRefObject nunca devem usar propriedades.

• Se chamado várias vezes em uma linha, um método de propriedade pode retornar um valor diferente a cada vez; um campo retorna o mesmo valor a cada vez. A class System.DateTime tem uma propriedade Readonly Now que retorna a data e hora atuais. Cada vez que você consulta essa propriedade, ela retornará um valor diferente. Isso é um erro, e a Microsoft deseja que eles possam consertar a class fazendo agora um método em vez de uma propriedade.

• Um método de propriedade pode causar efeitos colaterais observáveis; access de campo nunca faz. Em outras palavras, um usuário de um tipo deve ser capaz de definir várias propriedades definidas por um tipo em qualquer ordem que ele escolha sem perceber qualquer comportamento diferente no tipo.

• Um método de propriedade pode exigir memory adicional ou retornar uma referência a algo que não é realmente parte do estado do object, portanto, modificar o object retornado não tem efeito sobre o object original; Consultar um campo sempre retorna uma referência a um object com garantia de fazer parte do estado do object original. Trabalhar com uma propriedade que retorna uma cópia pode ser muito confuso para os desenvolvedores, e essa característica geralmente não é documentada.

   

A razão de Jeff não gostar das propriedades é porque elas se parecem com campos – então os desenvolvedores que não entenderem a diferença irão tratá-los como se fossem campos, assumindo que eles serão baratos para executar, etc.

Pessoalmente eu discordo dele neste ponto em particular – eu acho que as propriedades tornam o código do cliente muito mais simples de ler do que as chamadas de método equivalentes. Eu concordo que os desenvolvedores precisam saber que as propriedades são basicamente methods disfarçados – mas eu acho que educar os desenvolvedores sobre isso é melhor do que tornar o código mais difícil de ler usando methods. (Em particular, tendo visto código Java com vários getters e setters sendo chamados na mesma instrução, eu sei que o código equivalente em C # seria muito mais simples de ler. A Lei de Deméter está muito bem na teoria, mas às vezes foo.Name.Length é realmente a coisa certa para usar …)

(E não, propriedades implementadas automaticamente não mudam nada disso.)

Isto é um pouco como os argumentos contra o uso de methods de extensão – eu posso entender o raciocínio, mas o benefício prático (quando usado com moderação) supera o lado negativo na minha opinião.

Bem, vamos pegar seus argumentos um por um:

Uma propriedade pode ser somente leitura ou somente gravação; o access de campo é sempre legível e gravável.

Esta é uma vitória para as propriedades, pois você tem um controle de access mais refinado.

Um método de propriedade pode lançar uma exceção; o access de campo nunca lança uma exceção.

Embora isso seja verdade, você pode muito bem chamar um método em um campo de object não inicializado e ter uma exceção lançada.

• Uma propriedade não pode ser passada como um parâmetro out ou ref para um método; um campo pode.

Justo.

• Um método de propriedade pode levar muito tempo para ser executado; o access ao campo sempre é concluído imediatamente.

Também pode levar muito pouco tempo.

• Se chamado várias vezes em uma linha, um método de propriedade pode retornar um valor diferente a cada vez; um campo retorna o mesmo valor a cada vez.

Não é verdade. Como você sabe que o valor do campo não foi alterado (possivelmente por outro thread)?

A class System.DateTime tem uma propriedade Readonly Now que retorna a data e hora atuais. Cada vez que você consulta essa propriedade, ela retornará um valor diferente. Isso é um erro, e a Microsoft deseja que eles possam consertar a class fazendo agora um método em vez de uma propriedade.

Se é um erro, é um erro menor.

• Um método de propriedade pode causar efeitos colaterais observáveis; access de campo nunca faz. Em outras palavras, um usuário de um tipo deve ser capaz de definir várias propriedades definidas por um tipo em qualquer ordem que ele escolha sem perceber qualquer comportamento diferente no tipo.

Justo.

• Um método de propriedade pode exigir memory adicional ou retornar uma referência a algo que não é realmente parte do estado do object, portanto, modificar o object retornado não tem efeito sobre o object original; Consultar um campo sempre retorna uma referência a um object com garantia de fazer parte do estado do object original. Trabalhar com uma propriedade que retorna uma cópia pode ser muito confuso para os desenvolvedores, e essa característica geralmente não é documentada.

A maioria dos protestos pode ser dita para os getters e setters de Java também – e nós os tivemos por um bom tempo sem tais problemas na prática.

Eu acho que a maioria dos problemas poderia ser resolvida com melhor realce de syntax (ou seja, diferenciando propriedades de campos) para que o programador saiba o que esperar.

Eu não li o livro, e você não citou a parte que você não entende, então vou ter que adivinhar.

Algumas pessoas não gostam de propriedades porque podem fazer seu código fazer coisas surpreendentes.

Se eu digitar Foo.Bar , as pessoas que Foo.Bar lendo normalmente esperarão que isso esteja simplesmente acessando um campo de membro da class Foo. É uma operação barata, quase gratuita, e é determinista. Eu posso ligar várias vezes e obter o mesmo resultado todas as vezes.

Em vez disso, com propriedades, pode realmente ser uma chamada de function. Pode ser um loop infinito. Pode abrir uma conexão de database. Pode retornar valores diferentes toda vez que eu acessá-lo.

É um argumento semelhante ao motivo pelo qual Linus odeia o C ++. Seu código pode agir de maneira surpreendente para o leitor. Ele odeia sobrecarga de operadores: a + b não significa necessariamente adição simples. Isso pode significar uma operação extremamente complicada, assim como as propriedades do C #. Pode ter efeitos colaterais. Pode fazer qualquer coisa.

Honestamente, acho que este é um argumento fraco. Ambas as linguagens estão cheias de coisas assim. (Devemos evitar a sobrecarga do operador em C # também? Afinal, o mesmo argumento pode ser usado lá)

Propriedades permitem abstração. Podemos fingir que algo é um campo regular, e usá-lo como se fosse um, e não ter que se preocupar com o que acontece nos bastidores.

Isso geralmente é considerado uma coisa boa, mas obviamente depende do programador escrever abstrações significativas. Suas propriedades devem se comportar como campos. Eles não devem ter efeitos colaterais, eles não devem realizar operações caras ou inseguras. Queremos poder pensar neles como campos.

No entanto, tenho outra razão para achá-los menos que perfeitos. Eles não podem ser passados ​​por referência a outras funções.

Os campos podem ser passados ​​como ref , permitindo que uma function chamada acesse diretamente. Funções podem ser passadas como delegadas, permitindo que uma function chamada acesse diretamente.

Propriedades … não podem.

Isso é uma merda.

Mas isso não significa que as propriedades sejam más ou não devam ser usadas. Para muitos propósitos, eles são ótimos.

Em 2009, esse conselho parecia apenas uma dor de cabeça da variedade Who Moved My Cheese . Hoje, é quase ridiculamente obsoleto.

Um ponto muito importante para o qual muitas respostas parecem andar na ponta dos pés, mas não se debruça sobre o assunto, é que esses supostos “perigos” das propriedades são uma parte intencional do design do framework!

Sim, as propriedades podem:

  • Especifique modificadores de access diferentes para o getter e setter. Esta é uma vantagem sobre os campos. Um padrão comum é ter um getter público e um setter protegido ou interno , uma técnica de inheritance muito útil que não é alcançável apenas por campos.

  • Lance uma exceção. Até hoje, esse continua sendo um dos methods mais eficazes de validação, especialmente ao trabalhar com estruturas de interface do usuário que envolvem conceitos de vinculação de dados. É muito mais difícil garantir que um object permaneça em um estado válido ao trabalhar com campos.

  • Demora muito tempo para executar. A comparação válida aqui é com methods , que levam igualmente longos – não campos . Nenhuma base é dada para a declaração “um método é preferido” diferente de preferência pessoal de um autor.

  • Retorna valores diferentes de seu getter em execuções subseqüentes. Isso quase parece uma piada tão próxima do ponto que exalta as virtudes dos parâmetros ref / out com os campos, cujo valor de um campo após uma chamada ref / out é praticamente garantido para ser diferente do seu valor anterior, e imprevisivelmente .

    Se estamos falando sobre o caso específico (e praticamente acadêmico) de access single-threaded sem acoplamentos aferentes, é bem compreendido que é apenas um projeto de propriedade ruim ter efeitos colaterais de mudança de estado visível, e talvez minha memory seja desvanecimento, mas eu não consigo lembrar de alguns exemplos de pessoas usando DateTime.Now e esperando o mesmo valor para sair sempre. Pelo menos não há casos em que eles não teriam estragado tudo tão mal com um hipotético DateTime.Now() .

  • Causam efeitos colaterais observáveis ​​- o que, é claro, é justamente a razão pela qual as propriedades foram inventadas como um recurso de linguagem em primeiro lugar. As diretrizes da Property Design da Microsoft indicam que a ordem de setter não deve importar, pois de outra forma implicaria acoplamento temporal . Certamente, você não pode obter o acoplamento temporal apenas com campos, mas isso é apenas porque você não pode fazer com que nenhum comportamento significativo aconteça apenas com os campos, até que algum método seja executado.

    Os acessadores de propriedade podem realmente ajudar a impedir certos tipos de acoplamento temporal forçando o object em um estado válido antes que qualquer ação seja executada – por exemplo, se uma class tiver um StartDate e um EndDate , então definir o EndDate antes do StartDate poderia forçar o StartDate também. Isso é verdade mesmo em ambientes multiencadeados ou asynchronouss, incluindo o exemplo óbvio de uma interface de usuário orientada a events.

Outras coisas que as propriedades podem fazer que campos não podem include:

  • Carregamento preguiçoso , uma das formas mais eficazes de evitar erros de ordem de boot.
  • Alterar Notificações , que são praticamente a base inteira da arquitetura do MVVM .
  • Herança , por exemplo, definindo um Type ou Name abstrato para que classs derivadas possam fornecer metadados interessantes, porém constantes, sobre si mesmos.
  • Intercepção , graças ao acima.
  • Indexadores , que todos que já tiveram que trabalhar com interoperabilidade COM e o inevitável spew de chamadas de Item(i) reconhecerão como uma coisa maravilhosa.
  • Trabalhe com PropertyDescriptor, que é essencial para criar designers e para estruturas XAML em geral.

Richter é claramente um autor prolífico e sabe muito sobre o CLR e C #, mas eu tenho que dizer, parece que quando ele escreveu este conselho originalmente (eu não tenho certeza se está em suas revisões mais recentes – eu sinceramente espero que não) que ele simplesmente não queria abandonar velhos hábitos e estava tendo problemas para aceitar as convenções de C # (vs. C ++, por exemplo).

O que quero dizer com isso é que o argumento “propriedades consideradas prejudiciais” essencialmente se resume a uma única afirmação: as propriedades parecem campos, mas podem não funcionar como campos. E o problema com a afirmação é que não é verdade ou, na melhor das hipóteses, é altamente enganador. As propriedades não se parecem com campos – pelo menos, elas não devem parecer com campos.

Existem duas convenções de codificação muito fortes em C # com convenções similares compartilhadas por outras linguagens CLR, e o FXCop gritará com você se você não segui-las:

  1. Os campos devem ser sempre privados, nunca públicos.
  2. Os campos devem ser declarados no camelCase. Propriedades são PascalCase.

Portanto, não há ambiguidade sobre se Foo.Bar = 42 é um acessador de propriedade ou um acessador de campo. É um acessador de propriedades e deve ser tratado como qualquer outro método – pode ser lento, pode lançar uma exceção, etc. Essa é a natureza do Abstraction – é inteiramente a critério da class declarante como reagir. Os projetistas de classs devem aplicar o princípio da menor surpresa, mas os chamadores não devem assumir nada sobre uma propriedade, exceto que ela faz o que diz na lata. Isso é de propósito.

A alternativa para propriedades é methods getter / setter em todos os lugares. Essa é a abordagem Java, e tem sido controversa desde o começo . Tudo bem se é essa a sua bolsa, mas não é como rolamos no campo do .NET. Nós tentamos, pelo menos dentro dos limites de um sistema estaticamente tipado, evitar o que Fowler chama de Ruído Sintático . Não queremos parênteses extras, verrugas extras de get / set ou assinaturas extras de methods – não se pudermos evitá-las sem perda de clareza.

Diga o que quiser, mas foo.Bar.Baz = quux.Answers[42] sempre será muito mais fácil de ler que foo.getBar().setBaz(quux.getAnswers().getItem(42)) . E quando você está lendo milhares de linhas por dia, isso faz diferença.

(E se a sua resposta natural ao parágrafo acima é para dizer, “com certeza é difícil de ler, mas seria mais fácil se você dividir em várias linhas”, então eu sinto muito em dizer que você perdeu completamente o ponto .)

Não vejo motivos para você não usar as Propriedades em geral.

Propriedades automáticas em C # 3+ apenas simplificam um pouco a syntax (a la syntatic sugar).

É apenas a opinião de uma pessoa. Eu li alguns c # livros e eu ainda não vi ninguém dizendo “não use propriedades”.

Eu pessoalmente acho que as propriedades são uma das melhores coisas sobre c #. Eles permitem que você exponha o estado através de qualquer mecanismo que você gosta. Você pode instanciar preguiçosamente a primeira vez que algo é usado e você pode fazer a validação na configuração de um valor, etc. Ao usá-los e escrevê-los, penso apenas em propriedades como setters e getters, que são uma syntax muito mais agradável.

Quanto às ressalvas com propriedades, há um casal. Um é provavelmente um mau uso de propriedades, o outro pode ser sutil.

Em primeiro lugar, propriedades são tipos de methods. Pode ser surpreendente se você colocar lógica complicada em uma propriedade, porque a maioria dos usuários de uma class espera que a propriedade seja bastante leve.

Por exemplo

 public class WorkerUsingMethod { // Explicitly obvious that calculation is being done here public int CalculateResult() { return ExpensiveLongRunningCalculation(); } } public class WorkerUsingProperty { // Not at all obvious. Looks like it may just be returning a cached result. public int Result { get { return ExpensiveLongRunningCalculation(); } } } 

Acho que usar methods para esses casos ajuda a fazer uma distinção.

Em segundo lugar, e mais importante, as propriedades podem ter efeitos colaterais se você as avaliar durante a debugging.

Digamos que você tenha alguma propriedade assim:

 public int Result { get { m_numberQueries++; return m_result; } } 

Agora, suponha que você tenha uma exceção que ocorre quando muitas consultas são feitas. Adivinha o que acontece quando você inicia a debugging e rollover a propriedade no depurador? Coisas ruins. Evite fazer isso! Olhando para a propriedade muda o estado do programa.

Estas são as únicas ressalvas que tenho. Eu acho que os benefícios das propriedades superam os problemas.

Essa razão deve ter sido dada dentro de um contexto muito específico. Geralmente é o contrário – é recomendável usar propriedades, pois elas fornecem um nível de abstração que permite alterar o comportamento de uma class sem afetar seus clientes …

Eu não posso deixar de escolher os detalhes das opiniões de Jeffrey Richter:

Uma propriedade pode ser somente leitura ou somente gravação; o access de campo é sempre legível e gravável.

Errado: Os campos podem ser marcados como somente leitura, portanto, somente o construtor do object pode gravar neles.

Um método de propriedade pode lançar uma exceção; o access de campo nunca lança uma exceção.

Errado: A implementação de uma class pode alterar o modificador de access de um campo de público para privado. Tenta ler campos privados em tempo de execução sempre resultará em uma exceção.

Eu não concordo com Jeffrey Richter, mas posso imaginar por que ele não gosta de propriedades (eu não li o livro dele).

Mesmo assim, as propriedades são exatamente como methods (implementação-wise), como um usuário de uma class, eu espero que suas propriedades se comportem “mais ou menos” como um campo público, por exemplo:

  • não há nenhuma operação demorada acontecendo dentro do getter / setter da propriedade
  • o getter da propriedade não tem efeitos colaterais (chamá-lo várias vezes, não altera o resultado)

Infelizmente, tenho visto propriedades que não se comportam dessa maneira. Mas o problema não são as propriedades em si, mas as pessoas que as implementaram. Então, isso requer apenas alguma educação.

Há um tempo em que considero não usar propriedades, e isso está escrito. Código do Net Compact Framework. O compilador CF JIT não executa a mesma otimização que o compilador JIT da área de trabalho e não otimiza os acessadores de propriedades simples, portanto, neste caso, adicionar uma propriedade simples faz com que uma pequena quantidade de código fique inchada usando um campo público. Normalmente, isso não seria um problema, mas quase sempre no mundo do Compact Framework, você enfrenta restrições de memory rígidas, de modo que até pequenas economias como essa contam.

Você não deve evitar usá-los, mas deve usá-los com qualificação e cuidado, pelas razões dadas por outros colaboradores.

Certa vez, vi uma propriedade chamada algo como Clientes que, internamente, abria uma chamada fora do processo para um database e lia a lista de clientes. O código do cliente tinha um ‘for (int i para Customers.Count)’ que estava causando uma chamada separada para o database em cada iteração e para o access do cliente selecionado. Esse é um exemplo notório que demonstra o princípio de manter a propriedade muito leve – raramente mais do que um access de campo interno.

Um argumento para usar propriedades é que elas permitem validar o valor que está sendo definido. Outra é que o valor da propriedade pode ser um valor derivado, não um único campo, como TotalValue = quantidade * quantidade.

Pessoalmente, só uso propriedades ao criar methods get / set simples. Eu me afasto disso quando chego a complicadas estruturas de dados.

O argumento assume que as propriedades são ruins porque se parecem com campos, mas podem fazer ações surpreendentes. Essa suposição é invalidada pelas expectativas dos programadores .NET:

Propriedades não se parecem com campos. Campos parecem propriedades.

• Um método de propriedade pode lançar uma exceção; o access de campo nunca lança uma exceção.

Assim, um campo é como uma propriedade que é garantida para nunca lançar uma exceção.

• Uma propriedade não pode ser passada como um parâmetro out ou ref para um método; um campo pode.

Portanto, um campo é como uma propriedade, mas possui resources adicionais: passar para methods de aceitação de ref / out .

• Um método de propriedade pode levar muito tempo para ser executado; o access ao campo sempre é concluído imediatamente. […]

Então, um campo é como uma propriedade rápida.

• Se chamado várias vezes em uma linha, um método de propriedade pode retornar um valor diferente a cada vez; um campo retorna o mesmo valor a cada vez. A class System.DateTime tem uma propriedade Readonly Now que retorna a data e hora atuais.

Portanto, um campo é como uma propriedade com garantia de retornar o mesmo valor, a menos que o campo tenha sido definido com um valor diferente.

• Um método de propriedade pode causar efeitos colaterais observáveis; access de campo nunca faz.

Novamente, um campo é uma propriedade garantida para não fazer isso.

• Um método de propriedade pode exigir memory adicional ou retornar uma referência a algo que não é realmente parte do estado do object, portanto, modificar o object retornado não tem efeito sobre o object original; Consultar um campo sempre retorna uma referência a um object com garantia de fazer parte do estado do object original. Trabalhar com uma propriedade que retorna uma cópia pode ser muito confuso para os desenvolvedores, e essa característica geralmente não é documentada.

Este pode ser surpreendente, mas não porque é uma propriedade que faz isso. Pelo contrário, é que quase ninguém retorna cópias mutáveis ​​em propriedades, de modo que o caso de 0,1% é surpreendente.

Invocar methods em vez de propriedades reduz muito a legibilidade do código de chamada. Em J #, por exemplo, usar o ADO.NET foi um pesadelo porque o Java não suporta propriedades e indexadores (que são essencialmente propriedades com argumentos). O código resultante era extremamente feio, com chamadas vazias de parênteses por todo o lugar.

O suporte para propriedades e indexadores é uma das vantagens básicas do C # em relação ao Java.