it-swarm-pt.tech

Por que é impossível substituir uma propriedade somente de getter e adicionar um setter?

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

130
ripper234

Porque o escritor do Baseclass declarou explicitamente que o Bar deve ser uma propriedade somente leitura. Não faz sentido para derivações quebrar este contrato e torná-lo read-write.

Estou com a Microsoft em um presente.
Digamos que sou um novo programador que foi instruído a codificar contra a derivação Baseclass. Eu escrevo algo que assume que Bar não pode ser escrito para (uma vez que o Baseclass declara explicitamente que é uma propriedade get only). Agora com 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;  }
}

Como o Bar não pode ser configurado de acordo com a interface BaseClass, o BarProvider assume que o cache é uma tarefa segura - já que o Bar não pode ser modificado. Mas se o conjunto fosse possível em uma derivação, essa classe poderia estar servindo valores obsoletos se alguém modificasse a propriedade Bar do objeto _source externamente. O ponto de ser 'Be Open, evite fazer coisas sorrateiras e pessoas surpreendentes'

Update : Ilya Ryzhenkov pergunta 'Por que as interfaces não seguem as mesmas regras?' . Hmm .. isso fica mais confuso quando penso nisso.
Uma interface é um contrato que diz 'espera que uma implementação tenha uma propriedade de leitura chamada Barra'. Pessoalmente Eu sou muito menos propenso a fazer essa suposição de somente leitura se eu vi uma interface. Quando vejo uma propriedade get-only em uma interface, eu a leio como 'Qualquer implementação exporia este atributo Bar' ... em uma classe base, ele clica 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 ... Eu fecharia dizendo 'faça o mais difícil possível para os mal entendidos surgirem'.

1
Gishu

Eu acho que o principal motivo é 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 quanto o set são substituídos. Não há set na classe base, então o compilador reclama. Assim como você não pode sobrescrever um método que não está definido na classe base, você não pode sobrescrever um setter.

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

Eu acho que a seguinte sintaxe pode fazer bem, mas como Eric Lippert continua dizendo, implementar até mesmo um recurso menor como esse ainda é uma grande quantidade de esforço ...

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

ou, para propriedades autoimplementadas,

public int MyProperty { override get; set; }
31
Roman Starkov

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

Primeiramente, gostaria de argumentar que ter uma propriedade get-only não se traduz necessariamente em somente leitura. Eu interpreto como "From this interface/abtract você pode obter esse valor", isso não significa que alguma implementação dessa interface/classe abstrata não precisará do usuário/programa para definir este valor explicitamente. 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, um exemplo simplificado do que eu precisava hoje. Acabei tendo que adicionar um setter na minha interface apenas para contornar isso. A razão para adicionar o setter e não adicionar, digamos, um método SetProp é que uma implementação particular da interface usou DataContract/DataMember para serialização de Prop, o que teria sido 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 post "meus 2 centavos", mas sinto que com o cartaz original e tentando racionalizar que isso é uma coisa boa parece estranho para mim, 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 substituir não se aplica aqui, ele simplesmente não funciona e mesmo se o fizesse não lhe daria o resultado desejado, ou seja, um getter virtual, conforme descrito pela interface.

19
JJJ

É possível

tl; dr - Você pode sobrescrever um método get-only com um setter se quiser. Basicamente é apenas:

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

  2. Se você não fizer mais nada, então o antigo método get ainda será chamado quando a classe derivada for chamada através de seu tipo base. Para corrigir isso, adicione uma camada intermediária abstract que use override no antigo método get para forçá-la a retornar o resultado do novo método get.

Isso nos permite substituir as propriedades com getset mesmo se elas não tivessem uma em sua definição base.

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

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

  • Se a definição de base for set- only, você poderá 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 _/ifvocê o torna set- only;

    • você pode usar um tipo de retorno menos derivadoifvocê o torna get- somente.

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: propriedade get- pré-existente

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- apenas com getset

Você deseja override com uma propriedade get/set, mas ela não será compilada.

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

Solução: use uma camada intermediária abstract

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

  1. Crie uma propriedade newget/set com o mesmo nome.

  2. override o método antigo get com um acessador para o novo método get para assegurar consistência.

Então, primeiro você escreve 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 compila antes. Ele irá compilar desta vez porque você não está realmente override 'na propriedade get- only; em vez disso, você está substituindo-o 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, isso funciona como planejado 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 como pretendido ao visualizar D como uma de suas classes base, por exemplo, A ou B. Mas a razão pela qual isso funciona pode ser um pouco menos óbvia.

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 de get ainda é basicamente substituída pela definição da classe derivada de get, portanto, ela 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

Esse método permite adicionar métodos set às propriedades somente get-. Você também pode usá-lo para fazer coisas como:

  1. Altere qualquer propriedade em uma propriedade get- only, set- only ou get- e -set, independentemente do que estava em uma classe base.

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

As principais desvantagens são que há mais código para fazer e um abstract class extra na árvore de herança. Isso pode ser um pouco chato com os construtores que usam parâmetros porque precisam ser copiados/colados na camada intermediária.

12
Nat

Concordo que não ser capaz de substituir um getter em um tipo derivado é um antipadrão. Apenas leitura especifica falta de implementação, não um contrato de um funcional puro (implícito pela resposta de voto superior).

Eu suspeito que a Microsoft tinha essa limitação porque o mesmo equívoco foi promovido, ou talvez por causa da 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 possível.

O conceito errôneo indicado pela resposta top vote, de que uma propriedade somente leitura deveria ser mais "pura" do que uma propriedade de leitura/gravação é ridícula. Basta olhar para muitas propriedades comuns somente leitura no framework; o valor não é constante/puramente funcional; por exemplo, DateTime.Now é somente leitura, mas nada além de um valor funcional puro. Uma tentativa de "armazenar em cache" um valor de uma propriedade somente leitura assumindo que retornará o mesmo valor na próxima vez é arriscado.

De qualquer forma, usei uma das seguintes estratégias para superar essa limitação; ambos são menos que perfeitos, mas permitirão que você flua além dessa deficiência de linguagem:

   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;
   }
8
T.Tobler

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, sendo que apenas uma delas pode existir com uma determinada assinatura em um determinado contexto; propriedades só podem ser substituídas por propriedades declaradas identicamente. 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 delas era leitura-gravação.

Pessoalmente, desejo que todo o conceito de "propriedades" possa ser abolido, exceto que a sintaxe ish propriedade pode ser usada como açúcar sintático para chamar métodos "get" e "set". Isso não apenas facilitaria a opção 'add set', mas também permitiria que 'get' retornasse um tipo diferente de 'set'. Embora essa habilidade não seja usada com muita frequência, às vezes pode ser útil ter um método 'get' retornando um objeto wrapper enquanto o 'set' pode aceitar um wrapper ou dados reais.

1
supercat

Eu posso entender todos os seus pontos, mas efetivamente, as propriedades automáticas do C # 3.0 ficam inúteis nesse caso.

Você não pode fazer nada assim:

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

IMO, C # não deve restringir tais cenários. É da responsabilidade do desenvolvedor usá-lo de acordo.

1
Thomas Danecker

Você poderia contornar o problema criando uma nova propriedade:

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

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

Solução para apenas um pequeno subconjunto de casos de uso, mas mesmo assim: no C # 6.0, o configurador "readonly" é automaticamente incluído para as propriedades somente getter substituídas.

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

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

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

Você deve alterar o título da pergunta para detalhar que sua pergunta é somente sobre a substituição de uma propriedade abstrata ou se a sua pergunta é em geral sobrepor a propriedade somente para obtenção de uma classe.


Se o antigo (substituindo uma propriedade abstrata)

Esse código é inútil. Uma classe base sozinha não deve 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 propriedades abstratas ou métodos. No caso dado, os métodos comuns de funcionalidade devem estar pedindo para você substituir um método abstrato, 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), então faça o seguinte:

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 ressaltar o (estranho) comentário: 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 eles existirem. Então 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 a propriedade get-only de uma classe)

Toda propriedade não abstrata possui 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 quiser. Razão de ser: o setter existe de uma forma ou de outra, e você pode realizar o que você quer Veerryy facilmente.

A classe base, ou qualquer classe em que você possa ler uma propriedade com {get;}, possuiALGUMsort de setter exposto para aquela propriedade. Os metadados ficarão assim:

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

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

Pelo menos complexo:

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

Mais complexo:

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 exposto. No seu caso, você pode querer sobrescrever int Bar porque você não quer que a classe base lide com isso, não tenha acesso para revisar como ele está lidando com isso, ou foi incumbido de haxalhar algum código com rapidez. vai.

Em ambos os últimos e anteriores (conclusão)

Long-Story Short: Não é necessário que a Microsoft mude alguma coisa. Você pode escolher como sua classe de implementação é configurada e, sem o construtor, usar todos ou nenhum da classe base.

0
Suamere

Aqui está uma solução para conseguir isso usando o Reflection:

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);
}

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

0
user2514880