Por que o seguinte código C # não é permitido:
public abstract class BaseClass
{
public abstract int Bar { get;}
}
public class ConcreteClass : BaseClass
{
public override int Bar
{
get { return 0; }
set {}
}
}
CS0546 'ConcreteClass.Bar.set': não é possível substituir porque 'BaseClass.Bar' não possui um acessador de conjunto substituível
c#
.net
properties
getter-setter
ripper234
fonte
fonte
Foo
que não faz nada além de encapsular umGetFoo
método abstrato protegido . Um tipo derivado abstrato pode sombrear a propriedade com uma propriedade de leitura / gravação não virtual que não faz nada além de agrupar oGetFoo
método mencionado acima , juntamente com umSetFoo
método abstrato ; um tipo derivado concreto poderia fazer o acima, mas fornecer definições paraGetFoo
eSetFoo
.Respostas:
Como o escritor da Baseclass declarou explicitamente que Bar deve ser uma propriedade somente leitura. Não faz sentido que derivações violem este contrato e o façam com leitura / gravação.
Eu estou com a Microsoft neste.
Digamos que eu sou um novo programador que foi instruído a codificar contra a derivação da classe base. Eu escrevo algo que pressupõe que Bar não pode ser gravado (uma vez que a Baseclass afirma explicitamente que é uma propriedade get only). Agora, com sua derivação, meu código pode quebrar. por exemplo
Como Bar não pode ser definido de acordo com a interface BaseClass, o BarProvider assume que o cache é uma coisa segura a se fazer - Como Bar não pode ser modificado. Mas se o conjunto fosse possível em uma derivação, essa classe poderia estar servindo valores antigos se alguém modificasse a propriedade Bar do objeto _source externamente. O ponto é ' Seja aberto, evite fazer coisas sorrateiras e surpreender as pessoas '
Atualização : Ilya Ryzhenkov pergunta: 'Por que as interfaces não seguem as mesmas regras?' Hmm .. isso fica mais confuso como eu penso sobre isso.
Uma interface é um contrato que diz 'espere que uma implementação tenha uma propriedade de leitura denominada Bar'. Pessoalmente , é muito menos provável que eu faça essa suposição de somente leitura se vi uma interface. Quando vejo uma propriedade get-only em uma interface, eu a leio como 'Qualquer implementação expõe esse atributo Bar' ... em uma classe base, clica em 'Bar é uma propriedade somente leitura'. Claro que tecnicamente você não está quebrando o contrato. Está fazendo mais. Então, você está certo em um sentido. Gostaria de dizer 'dificultar o máximo possível para que surjam mal-entendidos'.
fonte
InvalidOperationException("This instance is read-only")
.Eu acho que a principal razão é simplesmente que a sintaxe é muito explícita para que isso funcione de outra maneira. Este código:
é bastante explícito que tanto o
get
quanto oset
são substituições. Não existeset
na classe base, portanto o compilador reclama. Assim como você não pode substituir um método que não está definido na classe base, também não pode substituir um setter.Você pode dizer que o compilador deve adivinhar sua intenção e aplicar apenas a substituição ao método que pode ser substituído (por exemplo, o getter nesse caso), mas isso contraria um dos princípios de design do C # - que o compilador não deve adivinhar suas intenções , porque isso pode parecer errado sem você saber.
Acho que a sintaxe a seguir pode funcionar bem, mas como Eric Lippert continua dizendo, implementar até um recurso menor como esse ainda é uma grande quantidade de esforço ...
ou, para propriedades autoimplementadas,
fonte
Eu me deparei com o mesmo problema hoje e acho que tenho uma razão muito válida para querer isso.
Primeiro, eu gostaria de argumentar que ter uma propriedade get-only não se traduz necessariamente em somente leitura. Eu interpreto como "A partir desta interface / abtract você pode obter esse valor", isso não significa que alguma implementação dessa interface / classe abstrata não precise do usuário / programa para definir esse valor explicitamente. As classes abstratas servem ao propósito de implementar parte da funcionalidade necessária. Não vejo absolutamente nenhuma razão para que uma classe herdada não possa adicionar um setter sem violar nenhum contrato.
A seguir, é apresentado um exemplo simplificado do que eu precisava hoje. Acabei tendo que adicionar um setter na minha interface apenas para contornar isso. O motivo para adicionar o setter e não adicionar, digamos, um método SetProp é que uma implementação específica da interface usava o DataContract / DataMember para serialização do Prop, que seria desnecessariamente complicado se fosse necessário adicionar outra propriedade apenas para esse fim de serialização.
Sei que este é apenas um post "meus 2 centavos", mas sinto que com o pôster original e tentar racionalizar que isso é uma coisa boa me parece estranho, especialmente considerando que as mesmas limitações não se aplicam ao herdar diretamente de um interface.
Além disso, a menção sobre o uso de novo em vez de substituição não se aplica aqui, ele simplesmente não funciona e, mesmo que não, não daria o resultado desejado, ou seja, um getter virtual, conforme descrito pela interface.
fonte
É possível
tl; dr - Você pode substituir um método get-only por um setter, se desejar. É basicamente apenas:
Crie uma
new
propriedade que tenha aget
e aset
usando o mesmo nome.Se você não fizer mais nada, o
get
método antigo ainda será chamado quando a classe derivada for chamada por meio de seu tipo base. Para corrigir isso, adicione umaabstract
camada intermediária que useoverride
noget
método antigo para forçá-lo a retornar oget
resultado do novo método.Isso nos permite substituir propriedades por
get
/set
mesmo que não possuam uma em sua definição básica.Como bônus, você também pode alterar o tipo de retorno, se desejar.
Se a definição de base fosse
get
apenas, você poderá usar um tipo de retorno mais derivado.Se a definição base fosse
set
apenas, você pode usar um tipo de retorno menos derivado.Se a definição base já era
get
/set
, então:você pode usar um tipo de retorno mais derivado se o fizer
set
apenas;você pode usar um tipo de retorno menos derivado se o fizer
get
apenas.Em todos os casos, você pode manter o mesmo tipo de retorno, se desejar. Os exemplos abaixo usam o mesmo tipo de retorno para simplificar.
Situação:
get
propriedade unicamente pré-existenteVocê tem alguma estrutura de classe que não pode modificar. Talvez seja apenas uma classe ou uma árvore de herança pré-existente. Seja qual for o caso, você deseja adicionar um
set
método a uma propriedade, mas não pode.Problema: Não é possível
override
aget
-apenas comget
/set
Você deseja
override
com umget
/set
property, mas ele não será compilado.Solução: use uma
abstract
camada intermediáriaEmbora você não possa diretamente
override
com umget
/set
property, você pode :Crie uma propriedade
new
get
/set
com o mesmo nome.override
oget
método antigo com um acessador para o novoget
método para garantir consistência.Então, primeiro você escreve a
abstract
camada intermediária:Em seguida, você escreve a classe que não seria compilada anteriormente. Desta vez, ele será compilado porque você não está realmente
override
naget
propriedade -only; em vez disso, você está substituindo-o usando anew
palavra - chave.Resultado: Tudo funciona!
Obviamente, isso funciona como planejado
D
.Tudo ainda funciona como pretendido ao visualizar
D
como uma de suas classes base, por exemplo,A
ouB
. Mas, a razão pela qual funciona pode ser um pouco menos óbvia.No entanto, a definição de classe base
get
ainda é substituída pela definição de classe derivada deget
, portanto ainda é completamente consistente.Discussão
Este método permite adicionar
set
métodos àsget
propriedades -only. Você também pode usá-lo para fazer coisas como:Altere qualquer propriedade para uma propriedade
get
-only,set
-lyly ouget
-and-set
, independentemente do que estava em uma classe base.Altere o tipo de retorno de um método nas classes derivadas.
As principais desvantagens são que há mais codificação a ser feita e um extra
abstract class
na árvore de herança. Isso pode ser um pouco irritante para os construtores que aceitam parâmetros porque eles precisam ser copiados / colados na camada intermediária.fonte
Concordo que não ser capaz de substituir um getter em um tipo derivado é um anti-padrão. Somente leitura especifica falta de implementação, não um contrato de puro funcional (implícito na resposta do voto principal).
Suspeito que a Microsoft tenha essa limitação porque o mesmo equívoco foi promovido ou talvez devido à simplificação da gramática; No entanto, agora que o escopo pode ser aplicado para obter ou definir individualmente, talvez possamos esperar que a substituição também seja.
O equívoco indicado pelo voto superior responde que uma propriedade somente leitura deve ser de alguma forma mais "pura" do que uma propriedade de leitura / gravação é ridícula. Basta olhar para muitas propriedades comuns de somente leitura na estrutura; o valor não é constante / puramente funcional; por exemplo, DateTime.Now é somente leitura, mas qualquer coisa, exceto um valor funcional puro. Uma tentativa de 'cache' de um valor de uma propriedade somente leitura, assumindo que ele retornará o mesmo valor da próxima vez que for arriscado.
De qualquer forma, usei uma das seguintes estratégias para superar essa limitação; ambos são menos que perfeitos, mas permitem que você manque além dessa deficiência de idioma:
fonte
Talvez você possa solucionar o problema criando uma nova propriedade:
fonte
Eu posso entender todos os seus pontos de vista, mas efetivamente as propriedades automáticas do C # 3.0 ficam inúteis nesse caso.
Você não pode fazer nada assim:
IMO, C # não deve restringir esses cenários. É de responsabilidade do desenvolvedor usá-lo adequadamente.
fonte
O problema é que, por qualquer motivo, a Microsoft decidiu que deveria haver três tipos distintos de propriedades: somente leitura, somente gravação e leitura e gravação, apenas uma das quais pode existir com uma determinada assinatura em um determinado contexto; propriedades podem ser substituídas apenas por propriedades declaradas de forma idêntica. Para fazer o que você deseja, seria necessário criar duas propriedades com o mesmo nome e assinatura - uma das quais era somente leitura e uma das quais era leitura e gravação.
Pessoalmente, desejo que todo o conceito de "propriedades" possa ser abolido, exceto que a sintaxe property-ish pode ser usada como açúcar sintático para chamar os métodos "get" e "set". Isso não apenas facilitaria a opção 'adicionar conjunto', mas também permitiria que 'get' retornasse um tipo diferente de 'conjunto'. Embora essa capacidade não seja usada com muita frequência, às vezes pode ser útil fazer com que um método 'get' retorne um objeto de wrapper, enquanto o 'set' pode aceitar um wrapper ou dados reais.
fonte
Aqui está uma solução alternativa para conseguir isso usando o Reflection:
Dessa forma, podemos definir o valor do objeto em uma propriedade que é somente leitura. Pode não funcionar em todos os cenários!
fonte
Você deve alterar o título da sua pergunta para detalhar que sua pergunta é unicamente em relação à substituição de uma propriedade abstrata ou que sua pergunta é sobre a substituição geral da propriedade get-only de uma classe.
Se o primeiro (substituindo uma propriedade abstrata)
Esse código é inútil. Uma classe base sozinha não deve lhe dizer que você é forçado a substituir uma propriedade Get-Only (talvez uma interface). Uma classe base fornece funcionalidade comum que pode exigir entrada específica de uma classe de implementação. Portanto, a funcionalidade comum pode fazer chamadas para abstrair propriedades ou métodos. Nesse caso, os métodos de funcionalidade comuns devem solicitar que você substitua um método abstrato, como:
Mas se você não tem controle sobre isso, e a funcionalidade da classe base lê de sua própria propriedade pública (estranha), faça o seguinte:
Quero ressaltar o comentário (estranho): eu diria que uma prática recomendada é que uma classe não use suas próprias propriedades públicas, mas use seus campos privados / protegidos quando existirem. Portanto, este é um padrão melhor:
Se o último (substituindo a propriedade get-only de uma classe)
Toda propriedade não abstrata tem um setter. Caso contrário, é inútil e você não deve se importar em usá-lo. A Microsoft não precisa permitir que você faça o que deseja. Razão: o levantador existe de uma forma ou de outra e você pode realizar o que deseja Veerryy facilmente.
A classe base ou qualquer classe com a qual você possa ler uma propriedade
{get;}
possui ALGUM tipo de setter exposto para essa propriedade. Os metadados ficarão assim:Mas a implementação terá duas extremidades do espectro de complexidade:
Menos complexo:
Mais complexo:
Meu argumento é que, de qualquer maneira, você tem o levantador exposto. No seu caso, você pode substituir,
int Bar
porque não deseja que a classe base lide com ela, não tenha acesso para revisar como ela está lidando com ela ou foi encarregado de colocar algum código muito rapidamente contra a sua vontade .Nos Últimos e Antigos (Conclusão)
Longa história: Não é necessário que a Microsoft mude nada. Você pode escolher como sua classe de implementação é configurada e, sem o construtor, usar toda ou nenhuma da classe base.
fonte
Solução para apenas um pequeno subconjunto de casos de uso, mas, no entanto: no C # 6.0, o setter "somente leitura" é adicionado automaticamente para propriedades substituídas somente de getter.
fonte
Porque isso quebraria o conceito de encapsulamento e ocultação de implementação. Considere o caso ao criar uma classe, enviá-la e, em seguida, o consumidor da sua classe poderá definir uma propriedade para a qual você originalmente fornece apenas um getter. Isso afetaria efetivamente quaisquer invariantes da sua classe dos quais você pode confiar em sua implementação.
fonte
Isto não é impossível. Você simplesmente precisa usar a palavra-chave "nova" em sua propriedade. Por exemplo,
Parece que essa abordagem satisfaz os dois lados desta discussão. O uso de "novo" quebra o contrato entre a implementação da classe base e a implementação da subclasse. Isso é necessário quando uma classe pode ter vários contratos (via interface ou classe base).
Espero que isto ajude
fonte
new
não substitui mais a base virtual. Em particular, se a propriedade base for abstrata , como na postagem original, é impossível usar anew
palavra - chave em uma subclasse não abstrata, pois é necessário substituir todos os membros abstratos.Test t = new Test(); Base b = t; Console.WriteLine(t.BaseProperty)
dão 5,Console.WriteLine(b.BaseProperty)
dão 0 e, quando seu sucessor precisa depurar isso, é melhor você ter se mudado para muito longe.Uma propriedade somente leitura na classe base indica que essa propriedade representa um valor que sempre pode ser determinado de dentro da classe (por exemplo, um valor de enumeração que corresponde ao contexto (db-) de um objeto). Portanto, a responsabilidade de determinar o valor permanece dentro da classe.
Adicionar um setter causaria um problema estranho aqui: Um erro de validação deve ocorrer se você definir o valor para qualquer outra coisa que não seja o único valor possível que ele já possui.
As regras geralmente têm exceções, no entanto. É muito bem possível que, por exemplo, em uma classe derivada, o contexto reduza os possíveis valores de enum para 3 em 10, mas o usuário desse objeto ainda precisa decidir qual é o correto. A classe derivada precisa delegar a responsabilidade de determinar o valor para o usuário deste objeto. É importante perceber que o usuário desse objeto deve estar ciente dessa exceção e assumir a responsabilidade de definir o valor correto.
Minha solução nesse tipo de situação seria deixar a propriedade somente leitura e adicionar uma nova propriedade de leitura e gravação à classe derivada para suportar a exceção. A substituição da propriedade original simplesmente retornará o valor da nova propriedade. A nova propriedade pode ter um nome próprio, indicando o contexto dessa exceção corretamente.
Isso também apóia a observação válida: "torne o mais difícil possível que ocorram mal-entendidos" por Gishu.
fonte
ReadableMatrix
classe (com uma propriedade indexada somente leitura de dois argumentos) tendo subtiposImmutableMatrix
eMutableMatrix
. O código que precisa apenas ler uma matriz e não se importa se pode mudar algum tempo no futuro, pode aceitar um parâmetro do tipoReadableMatrix
e ficar perfeitamente satisfeito com os subtipos mutáveis ou imutáveis.List<T>.Count
faz: Ele não pode ser atribuído, mas isso não significa que seja "somente leitura", pois não pode ser modificado pelos usuários da classe. Pode muito bem ser modificado, embora indiretamente, adicionando e removendo itens da lista.Como no nível de IL, uma propriedade de leitura / gravação se traduz em dois métodos (getter e setter).
Ao substituir, você deve continuar suportando a interface subjacente. Se você pudesse adicionar um setter, estaria efetivamente adicionando um novo método, que permaneceria invisível para o mundo exterior, no que diz respeito à interface de suas classes.
É verdade que adicionar um novo método não quebraria a compatibilidade propriamente dita, mas, como permaneceria oculto, a decisão de proibir isso faz todo o sentido.
fonte
new
vez deoverride
.new
com suas adições.Porque uma classe que possui uma propriedade somente leitura (sem setter) provavelmente tem uma boa razão para isso. Pode não haver nenhum armazenamento de dados subjacente, por exemplo. Permitir que você crie um levantador quebra o contrato estabelecido pela classe. É apenas ruim OOP.
fonte