it-swarm-eu.dev

Proč je nemožné přepsat vlastnost getter-only a přidat setter?

Proč není povolen následující kód C #:

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': nelze přepsat, protože 'BaseClass.Bar' nemá nastavitelný přístupový prvek sady

130
ripper234

Protože zapisovatel Baseclass výslovně deklaroval, že Bar musí být pouze pro čtení. Nedává smysl pro odvození této smlouvy a její čtení a zápis.

Jsem s Microsoftem na tomto.
Řekněme, že jsem nový programátor, kterému bylo řečeno, aby kódoval proti derivaci Baseclass. i psát něco, co předpokládá, že Bar nemůže být zapsán (protože Baseclass výslovně uvádí, že se jedná o vlastnost získat pouze.). např.

public class BarProvider
{ BaseClass _source;
  Bar _currentBar;

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

  public Bar getBar()
  { return _currentBar;  }
}

Vzhledem k tomu, Bar nelze nastavit jako rozhraní BaseClass, BarProvider předpokládá, že ukládání do mezipaměti je bezpečná věc - Protože Bar nelze změnit. Pokud však bylo v derivaci možné nastavit tuto třídu, mohla by tato třída sloužit jako zastaralé hodnoty, pokud někdo objekt externě upravil vlastnost objektu _source. Bodem je být "Be Open, vyhnout se záludným věcem a překvapujícím lidem"

Update: Ilya Ryzhenkov se ptá: "Proč rozhraní nehrají podle stejných pravidel?" Hmm .. tohle je muddier, jak si o tom myslím.
Rozhraní je smlouva, která říká „očekávejte, že implementace bude mít vlastnost čtení s názvem Bar“. Osobně Je mnohem méně pravděpodobné, že se tento předpoklad pro čtení, jen když jsem viděl rozhraní. Když vidím na rozhraní pouze vlastnost get-only, přečetl jsem si ji jako 'Jakákoliv implementace by vystavila tento atribut Bar' ... na základní třídě, na kterou klikne, 'Bar je vlastnost jen pro čtení'. Samozřejmě technicky neporušujete smlouvu. Takže máte pravdu v určitém smyslu. Blízko bych řekl, že je to co nejtěžší pro nedorozumění.

1
Gishu

Myslím, že hlavním důvodem je jednoduše to, že syntaxe je příliš explicitní, aby mohla fungovat jinak. Tento kód:

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

je zcela jasné, že get a set jsou přepsány. V základní třídě není set, takže kompilátor si stěžuje. Stejně jako nemůžete přepsat metodu, která není definována v základní třídě, nemůžete přepsat ani setter.

Můžete říci, že kompilátor by měl odhadnout váš záměr a aplikovat pouze přepsání na metodu, která může být přepsána (tj. Getter v tomto případě), ale to jde proti jednomu z principů návrhu C # - že překladač nesmí odhadnout vaše záměry , protože to může hádat špatně, aniž byste věděli.

Myslím, že následující syntaxe by mohla být pěkná, ale jak říká Eric Lippert, implementace i menší funkce, jako je tato, je stále velkým úsilím ...

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

nebo, pro autoimplementované vlastnosti,

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

Dnes jsem narazil na ten samý problém a myslím si, že to má velmi platný důvod. 

Nejdřív bych chtěl namítnout, že mít vlastnost get-only se nemusí nutně překládat jen pro čtení. I interpretovat to jako "Z tohoto rozhraní/abtract můžete získat tuto hodnotu", to neznamená, že některé provedení této rozhraní/abstraktní třídy zvyklý potřebovat uživatele/program, aby tuto hodnotu explicitně nastavit. Abstraktní třídy slouží k implementaci části potřebné funkce. Nevidím vůbec žádný důvod, proč by zděděná třída nemohla přidat setr bez porušení smluv.

Následuje zjednodušený příklad toho, co jsem dnes potřeboval. Skončil jsem s tím, že v mém rozhraní musím přidat setr, abych to vyřešil. Důvodem pro přidání setteru a nepřidání, řekněme, metody SetProp je, že jedna konkrétní implementace rozhraní použila DataContract/DataMember pro serializaci Propu, což by bylo zbytečně komplikované, kdybych musel přidat další vlastnost jen pro tento účel. serializace.

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

Vím, že je to jen "můj 2 centy" post, ale mám pocit, že s originálním plakátem a snaží se racionalizovat, že je to dobrá věc, se mi zdá zvláštní, zejména vzhledem k tomu, že stejná omezení neplatí, když zdědíte přímo z rozhraní.

Také zmínka o použití nových namísto přepsání se zde nepoužije, prostě to nefunguje a ani kdyby to nebylo, nedalo by vám výsledek, který by byl žádoucí, totiž virtuální getter popsaný v rozhraní.

19
JJJ

Je to možné

tl; dr - Metodu get-only můžete přepsat pomocí setteru, pokud chcete. Je to v podstatě jen:

  1. Vytvořte new vlastnost, která má get a set pomocí stejného jména.

  2. Pokud nic jiného neuděláte, pak bude stará metoda get stále volána, když je odvozená třída volána přes svůj základní typ. Chcete-li to opravit, přidejte mezilehlou vrstvu abstract, která používá override na staré metodě get, aby ji donutila vrátit nový výsledek metody get.

To nám umožňuje přepsat vlastnosti s getset, i když v definici své základny chyběly.

Jako bonus můžete také změnit typ návratu, pokud chcete.

  • Pokud byla definice základny pouze get-, můžete použít návratový typ odvozený od více.

  • Pokud byla definice základny pouze set-, pak nám můžete poskytnout návratový typ méně odvozený.

  • Pokud byla definice báze již get/set, pak:

    • můžete použít více odvozený typ návratu _/ifyou make to set- only;

    • můžete použít méně odvozený typ návratuifyou make it get- only.

Ve všech případech můžete zachovat stejný typ návratu, pokud chcete. Níže uvedené příklady používají pro zjednodušení stejný typ návratu.

Situace: Předcházející get- pouze vlastnost

Máte nějakou strukturu třídy, kterou nemůžete změnit. Možná je to jen jedna třída, nebo je to již existující strom dědictví. Ať už je tomu tak, chcete do vlastnosti přidat metodu set, ale nemůžete.

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

Problém: Nelze overrideget- pouze s getset

Chcete override s get/set vlastnost, ale nebude kompilovat.

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

Řešení: Použijte mezilehlou vrstvu abstract

I když nemůžete přímo override s get/set vlastnost, můžete _/can:

  1. Vytvořte newget/set vlastnost se stejným názvem.

  2. override stará metoda get s přístupovým objektem nové metody get pro zajištění konzistence.

Nejprve tedy napíšete mezilehlou vrstvu 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; }
}

Pak napíšete třídu, která by nebyla kompilace dříve. Tento čas bude kompilován, protože ve skutečnosti nemáte override 'ing get- pouze vlastnost; místo toho jej nahrazujete klíčovým slovem 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; } }
}

Výsledek: Všechno funguje!

To samozřejmě funguje jako 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.

Při prohlížení D jako jedné ze základních tříd, např. A nebo B. Ale důvod, proč to funguje, může být o něco méně zřejmý.

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.

Definice get základní třídy je však nakonec potlačena definicí get odvozené třídy, takže je stále zcela konzistentní.

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.

Diskuse

Tato metoda umožňuje přidat set metody do vlastností get-. Můžete ji také použít k provádění podobných činností:

  1. Změňte libovolnou vlastnost na vlastnost get-, set- only nebo get- a -set bez ohledu na to, co to bylo v základní třídě.

  2. Změňte typ návratu metody v odvozených třídách.

Hlavní nevýhody spočívají v tom, že existuje více kódování a navíc abstract class ve stromu dědičnosti. To může být trochu nepříjemné u konstruktérů, kteří mají parametry, protože ty musí být kopírovány/vkládány do mezilehlé vrstvy.

12
Nat

Souhlasím s tím, že není možné přepsat getter v odvozeném typu je anti-vzor. Read-Only specifikuje nedostatek implementace, nikoliv kontrakt čistě funkčního (implikovaný odpovědí top hlasování).

Domnívám se, že Microsoft měl toto omezení buď proto, že byla propagována stejná mylná představa, nebo snad kvůli zjednodušení gramatiky; i když nyní lze tento rozsah aplikovat na získání nebo nastavení individuálně, možná můžeme doufat, že i nadřazení může být také.

Mylná představa, která je vyjádřena odpovědí na horní hlas, že vlastnost jen pro čtení by měla být nějak více „čistá“ než vlastnost čtení/zápis je směšná. Jednoduše se podívejte do mnoha běžných vlastností pouze pro čtení v rámci; hodnota není konstantní/čistě funkční; například DateTime.Now je jen pro čtení, ale nic jiného než čistá funkční hodnota. Pokus o 'mezipaměť' hodnotu vlastnosti pouze pro čtení za předpokladu, že příště vrátí stejnou hodnotu, je riskantní.

V každém případě jsem použil jednu z následujících strategií k překonání tohoto omezení; oba jsou méně než dokonalé, ale dovolí vám kulhat mimo tento jazykový nedostatek:

   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

Problém je v tom, že Microsoft z jakéhokoli důvodu rozhodl, že by měly existovat tři odlišné typy vlastností: pouze pro čtení, pouze pro zápis a čtení a zápis, z nichž pouze jeden může existovat s daným podpisem v daném kontextu; vlastnosti mohou být přepsány pouze identicky deklarovanými vlastnostmi. K tomu, co chcete, by bylo nutné vytvořit dvě vlastnosti se stejným názvem a podpisem - jeden z nich byl jen pro čtení a jeden z nich byl čtení a zápis.

Osobně bych si přál, aby celý koncept „vlastností“ mohl být zrušen, kromě toho, že syntaxe vlastností-ish by mohla být použita jako syntaktický cukr pro volání metod „get“ a „set“. To by nejen usnadnilo volbu 'add set', ale také by umožnilo vrátit jiný typ z 'set'. I když by taková schopnost nebyla používána strašně často, někdy by bylo užitečné mít metodu 'get', která by vrátila objekt wrapperu, zatímco soubor 'set' by mohl přijímat buď wrapper nebo aktuální data.

1
supercat

Dokážu pochopit všechny vaše body, ale efektivně, automatické vlastnosti C # 3.0 jsou v tomto případě k ničemu.

Nemůžete dělat nic takového:

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

IMO, C # by takové scénáře neměly omezovat. Je odpovědností vývojáře, aby jej používal odpovídajícím způsobem.

1
Thomas Danecker

Problém byste možná mohli vyřešit vytvořením nové vlastnosti:

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

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

Řešení pro jen malou podmnožinu případů použití, ale přesto: v C # 6.0 je automaticky přidán "readonly" setter pro přepsané vlastnosti pouze pro getter.

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

Měli byste změnit svůj název otázky tak, aby byla podrobná informace o tom, že se jedná pouze o otázku, zda se jedná o přepsání abstraktní vlastnosti, nebo zda se jedná o otázku týkající se obecně převažujícího vlastnictví třídy.


Pokud dřívější (převažující abstraktní vlastnost)

Ten kód je k ničemu. Samotná základní třída by vám neměla říkat, že jste nuceni přepsat vlastnost Get-Only (snad rozhraní). Základní třída poskytuje společnou funkci, která může vyžadovat specifický vstup od implementační třídy. Společná funkce proto může volat do abstraktní vlastnosti nebo metody. V daném případě by společné metody funkčnosti měly vyžadovat, abyste přepsali abstraktní metodu, jako jsou:

public int GetBar(){}

Ale pokud na to nemáte žádnou kontrolu a funkce základní třídy čte z vlastního veřejného vlastnictví (divný), pak to udělejte takto:

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

Chci poukázat na (podivnou) poznámku: Řekl bych, že nejlepší praxe je, aby třída nepoužila své vlastní veřejné vlastnosti, ale aby využívala své soukromé/chráněné oblasti, pokud existují. Takže je to lepší vzor:

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

Je-li tato volba (přepsání vlastností pouze pro získání třídy)

Každá non-abstraktní vlastnost má setter. Jinak je to k ničemu a neměli byste ho používat. Společnost Microsoft vám nemusí umožnit dělat to, co chcete. Důvod je: setter existuje v nějaké formě nebo jiné, a můžete dosáhnout toho, co chcete Veerryy snadno.

Základní třída nebo jakákoli třída, ve které můžete číst vlastnost pomocí {get;}, máSOMEdruh vystaveného setteru pro tuto vlastnost. Metadata budou vypadat takto:

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

Implementace však bude mít dva konce spektra složitosti:

Nejmenší komplex:

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

Nejkomplexnější:

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,
}

Moje myšlenka je, ať už je to tak, že jste vystavovatele vystavili. Ve vašem případě budete chtít přepsat soubor int Bar, protože nechcete, aby základní třída s ním zacházela, neměla přístup ke kontrole toho, jak s ním pracuje, nebo byla pověřena haxem nějakého kódu. vůle.

V posledních i bývalých (Závěr)

Long-Story Short: Není nutné, aby Microsoft něco změnil. Můžete si vybrat, jak bude vaše implementační třída nastavena, a bez konstruktoru použít všechny nebo žádné základní třídy.

0
Suamere

Zde je pracovní postup, jehož cílem je dosáhnout pomocí 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);
}

Tímto způsobem můžeme nastavit hodnotu objektu na vlastnost, která je readonly. Nesmí však fungovat ve všech scénářích!

0
user2514880