Por que é impossível substituir uma propriedade só de getter e adicionar um setter? [fechadas]

StackOverflow https://stackoverflow.com/questions/82437

Pergunta

Por que o seguinte código C # não permitidos:

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 tem um conjunto substituível acessor

Foi útil?

Solução

Como o escritor de baseclass tem declarado explicitamente que Bar tem que ser uma propriedade só de leitura. Não faz sentido para derivações para quebrar este contrato e torná-lo leitura e escrita.

Estou com a Microsoft sobre este.
Vamos dizer que eu sou um novo programador que tem sido dito para código contra a derivação baseclass. i escrever algo que assume que Bar não pode ser escrito (uma vez que o baseclass afirma explicitamente que é um get única propriedade). Agora, com a sua derivação, meu código pode quebrar. por exemplo.

public class BarProvider
{ BaseClass _source;
  Bar _currentBar;

  public void setSource(BaseClass b)
  {
    _source = b;
    _currentBar = b.Bar;
  }

  public Bar getBar()
  { return _currentBar;  }
}

Desde Bar não pode ser definido de acordo com a interface ClasseBase, BarProvider assume que o cache é uma coisa segura a fazer - Desde Bar não pode ser modificado. Mas se set era possível em uma derivação, essa classe poderia estar servindo valores obsoletos se alguém modificado propriedade Bar do objeto fonte_ externamente. O ponto é ' Esteja aberto, evitar fazer coisas sorrateiras e surpreender as pessoas '

Atualizar : Ilya Ryzhenkov pede '? Por que não interfaces de jogar pelas mesmas regras, em seguida,' Hmm .. isso fica muddier como eu penso sobre isso.
Uma interface é um contrato que diz 'esperar uma implementação ter uma propriedade de leitura chamado Bar.' Pessoalmente Eu sou muito menos propensos a fazer essa suposição de somente leitura, se eu vi uma interface. Quando vejo uma propriedade somente get em uma interface, eu lê-lo como 'Qualquer implementação iria expor este atributo Bar' ... em uma classe base de encaixá-lo como 'Bar é uma propriedade somente leitura'. É claro que tecnicamente você não está quebrando o contrato .. você está fazendo mais. Então você está certo em um sentido .. I perto dizendo iria 'torná-lo tão duro quanto possível para mal-entendidos a surgir'.

Outras dicas

Acho que a principal razão é simplesmente que a sintaxe é muito explícita para que isso funcione de outra maneira. Este código:

public override int MyProperty { get { ... } set { ... } }

é bastante explícito que tanto o get eo set são substituições. Não há set na classe base, de modo que o compilador reclama. Assim como você não pode substituir um método que não está definido na classe base, você não pode substituir um setter quer.

Pode-se dizer que o compilador deve adivinhar sua intenção e só aplicar a substituição para o método que pode ser substituído (ou seja, o getter, neste caso), mas isso vai contra um dos princípios de design C # - que o compilador não deve acho que suas intenções, porque pode errar sem você saber.

Eu acho que a seguinte sintaxe pode fazer muito bem, mas como Eric Lippert continua dizendo, implementando até mesmo uma característica menor como esta ainda é uma grande quantidade de esforço ...

public int MyProperty
{
    override get { ... }
    set { ... }
}

ou, para propriedades autoimplemented,

public int MyProperty { override get; set; }

me deparei com o mesmo problema hoje e acho que ter uma razão muito válida para querer isso.

Em primeiro lugar eu gostaria de argumentar que ter uma propriedade somente get não se traduz necessariamente em read-only. Eu interpreto-o como "A partir dessa interface / abtract você pode obter este valor", isso não significa que alguns implementação dessa interface de classe / abstract não vai precisar que o usuário / programa para definir este valor explicitamente. As classes abstratas servir o propósito de implementar parte da funcionalidade necessária. Não vejo absolutamente nenhuma razão para que uma classe herdada não conseguiu adicionar um setter sem violar quaisquer contratos.

A seguir é um exemplo simplificado do que eu precisava hoje. Acabei por ter de adicionar um setter em minha interface apenas para contornar esta situação. A razão para adicionar o setter e não adicionando, por exemplo, um método SetProp é que uma implementação específica da interface usada DataContract / DataMember para serialização de Prop, o que teria sido feita desnecessariamente complicado se eu tivesse que adicionar outra propriedade apenas para o propósito de serialização.

interface ITest
{
    // Other stuff
    string Prop { get; }
}

// Implements other stuff
abstract class ATest : ITest
{
    abstract public string Prop { get; }
}

// This implementation of ITest needs the user to set the value of Prop
class BTest : ATest
{
    string foo = "BTest";
    public override string Prop
    {
        get { return foo; }
        set { foo = value; } // Not allowed. 'BTest.Prop.set': cannot override because 'ATest.Prop' does not have an overridable set accessor
    }
}

// This implementation of ITest generates the value for Prop itself
class CTest : ATest
{
    string foo = "CTest";
    public override string Prop
    {
        get { return foo; }
        // set; // Not needed
    }
}

Eu sei que este é apenas um "meus 2 centavos" post, mas eu me sinto com o cartaz original e tentar racionalizar que esta é uma boa coisa parece estranho para mim, especialmente considerando que as mesmas limitações não se aplicam quando herdando directamente a partir de uma interface.

Além disso, a menção sobre o uso de novo em vez de substituição não se aplica aqui, ela simplesmente não funciona e mesmo que o fizesse, não iria dar-lhe o resultado desejado, ou seja, um getter virtual como descrito pela interface.

É possível

tl; dr - Você pode substituir um método somente get com um setter se quiser. É basicamente apenas:

  1. Crie uma propriedade new que tem tanto um get e uma set usando o mesmo nome.

  2. Se você não fazer qualquer outra coisa, então o método get antiga ainda será chamado quando a classe derivada é chamado por meio de seu tipo base. Para corrigir isso, adicione uma camada intermediária abstract que usos override no método get velho para forçá-lo a retornar resultado do novo método get.

Isso nos permite substituir as propriedades com get / set mesmo que eles não tinham um em sua definição de base.

Como um bônus, você também pode alterar o tipo de retorno, se quiser.

  • Se a definição base era get-only, então você pode usar um tipo de retorno mais derivado.

  • Se a definição base era set-only, então você pode nos um tipo de retorno menos derivada.

  • Se a definição de base já estava get / set, então:

    • Você pode usar um tipo mais derivado de retorno se você faz set-only;

    • Você pode usar um tipo de retorno menos derivada se você faz get-only.

Em todos os casos, você pode manter o mesmo tipo de retorno, se quiser. A seguir exemplos usam o mesmo tipo de retorno para a simplicidade.

Situação: Pré-existente propriedade somente get

Você tem alguma estrutura de classe que você 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 método set a uma propriedade, mas não pode.

public abstract class A                     // Pre-existing class; can't modify
{
    public abstract int X { get; }          // You want a setter, but can't add it.
}
public class B : A                          // Pre-existing class; can't modify
{
    public override int X { get { return 0; } }
}

Problema: Não é possível override o get somente com get / set

Você quer override com uma propriedade get / set, mas não irá compilar.

public class C : B
{
    private int _x;
    public override int X
    {
        get { return _x; }
        set { _x = value; }   //  Won't compile
    }
}

Solução: A utilização de uma camada intermédia abstract

Enquanto você não pode diretamente override com uma propriedade get / set, você pode :

  1. Crie uma propriedade new get / set com o mesmo nome.

  2. override o método get idade com um sistema de acesso ao novo método get para garantir a consistência.

Então, primeiro você escrever a camada intermediária abstract:

public abstract class C : B
{
    //  Seal off the old getter.  From now on, its only job
    //  is to alias the new getter in the base classes.
    public sealed override int X { get { return this.XGetter; }  }
    protected abstract int XGetter { get; }
}

Então, você escreve a classe que não compilar anteriormente. Ele vai compilar este tempo, porque você não está realmente override'ing a propriedade somente get; em vez disso, você está substituindo-lo usando a palavra-chave new.

public class D : C
{
    private int _x;
    public new virtual int X { get { return this._x; } set { this._x = value; } }

    //  Ensure base classes (A,B,C) use the new get method.
    protected sealed override int XGetter { get { return this.X; } }
}

Resultado: Tudo funciona

Obviamente, esta obra-as-destinado para D.

var test = new D();
Print(test.X);      // Prints "0", the default value of an int.

test.X = 7;
Print(test.X);      // Prints "7", as intended.

Tudo ainda funciona-as-destinados ao visualizar D como uma de suas classes base, por exemplo, A ou B. Mas, a razão pela qual ele funciona pode ser um pouco menos óbvio.

var test = new D() as B;
//test.X = 7;       // This won't compile, because test looks like a B,
                    // and B still doesn't provide a visible setter.

No entanto, a definição de classe base do get está ainda em última análise, substituído por definição da classe derivada de get, por isso ainda é completamente consistente.

var test = new D();
Print(test.X);      // Prints "0", the default value of an int.

var baseTest = test as A;
Print(test.X);      // Prints "7", as intended.

Discussão

Este método permite que você adicione métodos set para propriedades somente get. Você também pode usá-lo para fazer coisas como:

  1. Alterar qualquer propriedade em um get-only, set-somente, ou propriedade get-and-set, independentemente do que eleestava em uma classe base.

  2. Alterar o tipo de retorno de um método em classes derivadas.

As principais desvantagens são que há mais de codificação para fazer e um abstract class extra na árvore de herança. Isso pode ser um pouco chato com construtores que usam parâmetros porque aqueles têm de ser copiar / colar na camada intermediária.

Eu concordo que não ser capaz de substituir um getter em um tipo derivado é um anti-padrão. Read-only especifica falta de implementação, não um contrato de um puro funcional (implícito na resposta superior voto).

Eu suspeito Microsoft tinha essa limitação seja porque o mesmo equívoco foi promovido, ou talvez por causa de simplificar a gramática; porém, agora que escopo pode ser aplicado para obter ou definir individualmente, talvez podemos esperar override pode ser também.

O equívoco indicado pela resposta superior voto, que uma propriedade somente leitura deve de alguma forma ser mais "pura" do que uma propriedade de leitura / gravação é ridículo. Basta olhar para muitos comum ler apenas as propriedades do quadro; o valor não é uma constante / puramente funcional; por exemplo, DateTime.Now é somente leitura, mas nada além de um valor funcional pura. Uma tentativa de 'cache' um valor de uma leitura única propriedade supondo que ele irá retornar o mesmo valor na próxima vez é arriscado.

Em qualquer caso, eu usei uma das seguintes estratégias para superar esta limitação; ambos são menos do que perfeito, mas irá permitir que você a coxear além desta deficiência de idioma:

   class BaseType
   {
      public virtual T LastRequest { get {...} }
   }

   class DerivedTypeStrategy1
   {
      /// get or set the value returned by the LastRequest property.
      public bool T LastRequestValue { get; set; }

      public override T LastRequest { get { return LastRequestValue; } }
   }

   class DerivedTypeStrategy2
   {
      /// set the value returned by the LastRequest property.
      public bool SetLastRequest( T value ) { this._x = value; }

      public override T LastRequest { get { return _x; } }

      private bool _x;
   }

Você poderia talvez ir ao redor do problema, criando uma nova propriedade:

public new int Bar 
{            
    get { return 0; }
    set {}        
}

int IBase.Bar { 
  get { return Bar; }
}

Eu posso entender todos os seus pontos, mas efetivamente, C # 3,0 de propriedades automáticas obter inútil nesse caso.

Você não pode fazer qualquer coisa assim:

public class ConcreteClass : BaseClass
{
    public override int Bar
    {
        get;
        private set;
    }
}

IMO, C # não deve restringir tais cenários. É da responsabilidade do programador para usá-lo em conformidade.

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-gravação, apenas um dos quais pode existir com uma determinada assinatura em um determinado contexto; propriedades só pode ser substituído por propriedades identicamente-declarados. Para fazer o que quiser seria necessário para criar duas propriedades com o mesmo nome e assinatura - um dos quais foi só de leitura, e um dos quais era leitura e escrita .

Pessoalmente, eu desejo que todo o conceito de "propriedades" poderia ser abolida, exceto que a sintaxe da propriedade-ish poderia ser usado como açúcar sintático para chamar "get" e "set" métodos. Isto não só facilitar a opção 'set add', mas também permitir a 'get' para retornar um tipo diferente de 'set'. Enquanto tal capacidade não seria usado terrivelmente muitas vezes, pode ser útil às vezes têm um 'ficar' método retornar um objeto de invólucro enquanto o 'set' poderia aceitar um invólucro ou reais de dados.

Aqui está uma solução alternativa, a fim de conseguir isso usando Reflexão:

var UpdatedGiftItem = // object value to update;

foreach (var proInfo in UpdatedGiftItem.GetType().GetProperties())
{
    var updatedValue = proInfo.GetValue(UpdatedGiftItem, null);
    var targetpropInfo = this.GiftItem.GetType().GetProperty(proInfo.Name);
    targetpropInfo.SetValue(this.GiftItem, updatedValue,null);
}

Desta forma, podemos definir o valor de objeto em uma propriedade que é somente leitura. Pode não funcionar em todos os cenários embora!

Você deve alterar o seu título da pergunta a qualquer detalhe que sua pergunta é apenas em relação ao substituir uma propriedade abstrata, ou que sua pergunta é em relação ao geralmente substituindo propriedade somente get de uma classe.


Se o ex- (substituindo uma propriedade abstrata)

Esse código é inútil. A classe base por si só não deve dizer-lhe que você é forçado a substituir uma propriedade Get-Only (Talvez uma Interface). Uma classe de base fornece a funcionalidade comum que pode necessitar de entrada específica de uma classe implementando. Portanto, a funcionalidade comum pode fazer chamadas para propriedades ou métodos abstratos. No caso dado, os métodos de funcionalidade comum deve estar se perguntando por que você substituir um método abstrato, tais como:

public int GetBar(){}

Mas se você não tem controle sobre isso, e a funcionalidade da classe base lê a partir de sua própria propriedade pública (estranho), em seguida, basta fazer isso:

public abstract class BaseClass
{
    public abstract int Bar { get; }
}

public class ConcreteClass : BaseClass
{
    private int _bar;
    public override int Bar
    {
        get { return _bar; }
    }
    public void SetBar(int value)
    {
        _bar = value;
    }
}

Eu quero salientar o comentário (estranho): Eu diria que a melhor prática é para uma classe para não usar suas próprias propriedades públicas, mas para usar seus campos particulares / protegidas quando eles existem. Portanto, este é um padrão melhor:

public abstract class BaseClass {
    protected int _bar;
    public int Bar { get { return _bar; } }
    protected void DoBaseStuff()
    {
        SetBar();
        //Do something with _bar;
    }
    protected abstract void SetBar();
}

public class ConcreteClass : BaseClass {
    protected override void SetBar() { _bar = 5; }
}

Se o último (substituindo uma classe propriedade somente get)

Cada propriedade não-abstrato tem um setter. Caso contrário, é inútil e você não deve se preocupar para usá-lo. A Microsoft não tem para que você possa fazer o que quiser. Razão de ser:. A levantadora existe em alguma forma ou de outra, e você pode realizar o que você quer Veerryy facilmente

A classe base, ou qualquer classe onde você pode ler uma propriedade com {get;}, tem ALGUNS tipo de setter expostos para essa propriedade. Os metadados será parecido com este:

public abstract class BaseClass
{
    public int Bar { get; }
}

Mas a implementação terá duas extremidades do espectro de complexidade:

Pelo menos Complex:

public abstract class BaseClass
{
    private int _bar;
    public int Bar { 
        get{
            return _bar;
        }}
    public void SetBar(int value) { _bar = value; }
}

A maioria Complex:

public abstract class BaseClass
{
    private int _foo;
    private int _baz;
    private int _wtf;
    private int _kthx;
    private int _lawl;

    public int Bar
    {
        get { return _foo * _baz + _kthx; }
    }
    public bool TryDoSomethingBaz(MyEnum whatever, int input)
    {
        switch (whatever)
        {
            case MyEnum.lol:
                _baz = _lawl + input;
                return true;
            case MyEnum.wtf:
                _baz = _wtf * input;
                break;
        }
        return false;
    }
    public void TryBlowThingsUp(DateTime when)
    {
        //Some Crazy Madeup Code
        _kthx = DaysSinceEaster(when);
    }
    public int DaysSinceEaster(DateTime when)
    {
        return 2; //<-- calculations
    }
}
public enum MyEnum
{
    lol,
    wtf,
}

Meu ponto é, de qualquer forma, você tem o setter exposta. No seu caso, você pode querer substituir int Bar porque você não quer a classe base para lidar com isso, não têm acesso a uma revisão como ele está lidando com isso, ou foram incumbidos de Hax algum código verdadeiro quick'n'dirty contra o seu vai.

Em ambos estes e ex (Conclusão)

Long-Short Story: Não é necessário para a Microsoft para mudar nada. Você pode escolher como sua classe de implementação está configurado e, sans o construtor, use tudo ou nada da classe base.

Solução para apenas um pequeno subconjunto de casos de utilização, mas, no entanto,:. Em C # 6.0 "somente de leitura" levantador é adicionado automaticamente para propriedades somente getter substituídos

public abstract class BaseClass
{
    public abstract int Bar { get; }
}

public class ConcreteClass : BaseClass
{
    public override int Bar { get; }

    public ConcreteClass(int bar)
    {
        Bar = bar;
    }
}

Devido ao nível IL, uma propriedade de leitura / escrita traduz em métodos de dois (getter e setter).

Ao substituir, você tem que manter suporte a interface subjacente. Se você pode adicionar um setter, você seria efetivamente adicionando um novo método, que permanecem invisíveis para o mundo exterior, tanto quanto da interface de seus aulas estava preocupado.

É verdade, adicionando um novo método não seria quebrar a compatibilidade per se, mas uma vez que permanecem escondidos, a decisão para não permitir isso faz sentido.

Porque isso iria quebrar o conceito de encapsulamento e implementação esconderijo. Considere o caso quando você cria uma classe, enviá-lo e, em seguida, o consumidor de sua classe torna-se capaz de definir uma propriedade para o qual você originalmente fornecer apenas um getter. Ele efetivamente iria perturbar quaisquer invariantes de sua classe que você pode depender de sua implementação.

Porque uma classe que tem uma propriedade só de leitura (sem setter), provavelmente tem uma boa razão para isso. Pode não haver qualquer armazenamento de dados subjacente, por exemplo. Que lhe permite criar um setter quebra o contrato estabelecido pela classe. É apenas mau OOP.

Uma propriedade só de leitura na classe base indica que esta propriedade representa um valor que pode sempre ser determinada a partir de dentro da classe (por exemplo, um valor de enumeração correspondentes do contexto (DB) de um objecto). Assim, o responsibillity de determinar as estadias de valor dentro da classe.

Adicionando um setter causaria um problema estranho aqui: Um erro de validação deve ocorrer se você definir o valor para qualquer outra coisa do que o único valor possível que já tem.

Regras muitas vezes têm exceções, no entanto. É bem possível que, por exemplo, em uma classe derivada do contexto restringe os possíveis valores enum para 3 de 10, no entanto, o usuário deste objeto ainda precisa decidir qual é o correto. A classe derivada precisa delegar a responsibillity de determinar o valor para o usuário deste objeto. importante perceber é que o usuário deste objeto deve estar bem ciente desta excepção e assumir a responsibillity para definir o valor correto.

A minha solução neste tipo de situações seria deixar a propriedade somente leitura e adicionar uma nova propriedade de leitura e escrita para a classe derivada para apoiar a exceção. A substituição da propriedade original irá simplesmente devolver o valor da nova propriedade. A nova propriedade pode ter um nome próprio indicando o contexto desta excepção corretamente.

Este também suporta a observação válida: "torná-lo tão duro quanto possível para mal-entendidos a surgir" por Gishu

Isto não é impossível. Você simplesmente tem que usar a "nova" palavra-chave em sua propriedade. Por exemplo,

namespace {
    public class Base {
        private int _baseProperty = 0;

        public virtual int BaseProperty {
            get {
                return _baseProperty;
            }
        }

    }

    public class Test : Base {
        private int _testBaseProperty = 5;

        public new int BaseProperty {
            get {
                return _testBaseProperty;
            }
            set {
                _testBaseProperty = value;
            }
        }
    }
}

Parece que esta abordagem satisfaz ambos os lados desta discussão. Usando "novos" quebra o contrato entre a implementação da classe base e a implementação subclasse. Isto é necessário quando uma classe pode ter vários contratos (quer através de uma interface ou classe base).

Espero que isso ajude

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top