Dlaczego nie można przesłonić właściwości tylko pobierającej i dodać metodę ustawiającą? [Zamknięte]

141

Dlaczego następujący kod C # nie jest dozwolony:

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”: nie można przesłonić, ponieważ „BaseClass.Bar” nie ma nadpisywalnego akcesorium zestawu

ripper234
źródło
9
StackOverflow ma ograniczone możliwości odpowiadania na pytania w imieniu firmy Microsoft. Rozważ przeformułowanie pytania.
Jay Bazuzi,
1
Nawiasem mówiąc, prostym lekarstwem na to irytujące zachowanie w .net jest posiadanie niewirtualnej właściwości tylko do odczytu, Fooktóra nie robi nic poza opakowaniem chronionej GetFoometody abstrakcyjnej . Abstrakcyjny typ pochodny może zasłaniać właściwość niewirtualną właściwością do odczytu i zapisu, która nie robi nic poza zawijaniem wspomnianej GetFoometody wraz z SetFoometodą abstrakcyjną ; konkretny typ pochodny mógłby zrobić powyższe, ale dostarczyć definicje dla GetFooi SetFoo.
supercat
1
C ++ / CLI na to pozwala tylko po to, aby dodać paliwa do ognia.
ymett
1
Możliwość dodania akcesora podczas zastępowania właściwości została zaproponowana jako zmiana dla przyszłej wersji C # ( github.com/dotnet/roslyn/issues/9482 ).
Brandon Bonds
1
Szczerze mówiąc, to pytanie jest często zadawane, gdy trzeba rozszerzyć istniejący typ biblioteki, który używa następującego wzorca: klasa bazowa tylko do odczytu, pochodna klasa zmienna. Ponieważ biblioteka używa już złego wzorca (takiego jak WPF), istnieje potrzeba obejścia tego złego wzorca. Wykład nie uchroni nikogo przed koniecznością obejścia problemu pod koniec dnia.
rwong

Odpowiedzi:

-4

Ponieważ autor klasy Baseclass wyraźnie zadeklarował, że Bar musi być właściwością tylko do odczytu. Nie ma sensu, aby derywaty przerywały ten kontrakt i zmuszały go do odczytu i zapisu.

Jestem w tej sprawie z Microsoftem.
Powiedzmy, że jestem nowym programistą, któremu powiedziano, że mam kodować w oparciu o wyprowadzenie klasy podstawowej. piszę coś, co zakłada, że ​​Bar nie może zostać zapisany (ponieważ klasa bazowa wyraźnie stwierdza, że ​​jest to właściwość tylko do pobrania). Teraz, po twoim wyprowadzeniu, mój kod może się zepsuć. na przykład

public class BarProvider
{ BaseClass _source;
  Bar _currentBar;

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

  public Bar getBar()
  { return _currentBar;  }
}

Ponieważ Bar nie może być ustawiony zgodnie z interfejsem BaseClass, BarProvider zakłada, że ​​buforowanie jest rzeczą bezpieczną - ponieważ Bar nie może być modyfikowany. Ale jeśli set był możliwy w wyprowadzeniu, ta klasa może obsługiwać nieaktualne wartości, jeśli ktoś zmodyfikował zewnętrznie właściwość Bar obiektu _source. Chodzi o to, że „ bądź otwarty, unikaj robienia podstępnych rzeczy i zaskakiwania ludzi

Aktualizacja : Ilya Ryzhenkov pyta „Dlaczego więc interfejsy nie działają według tych samych zasad?” Hmm ... kiedy o tym myślę, robi się to bardziej mętne.
Interfejs jest kontraktem, który mówi „spodziewaj się, że implementacja będzie miała właściwość odczytu o nazwie Bar”. Osobiście znacznie rzadziej przyjmę założenie tylko do odczytu, gdy zobaczę interfejs. Kiedy w interfejsie widzę właściwość tylko do pobierania, odczytuje się ją jako „Każda implementacja ujawniłaby ten atrybut Bar” ... w klasie bazowej, którą klika jako „Bar jest właściwością tylko do odczytu”. Oczywiście technicznie rzecz biorąc, nie zrywasz kontraktu… robisz więcej. Więc w pewnym sensie masz rację. Na zakończenie powiedziałbym: „utrudnij jak najbardziej nieporozumienie”.

Gishu
źródło
94
Wciąż nie jestem przekonany. Nawet jeśli nie ma jawnego ustawiacza, nie gwarantuje to, że właściwość zawsze zwróci ten sam obiekt - nadal można go zmienić inną metodą.
ripper234
34
Czy zatem to samo nie powinno dotyczyć interfejsów? Możesz mieć właściwość readonly na interfejsie i odczytywać / zapisywać na typie implementującym.
Ilya Ryzhenkov
49
Nie zgadzam się: klasa bazowa jedynie deklarowała właściwość jako nie posiadającą metody ustawiającej; to nie to samo, co właściwość tylko do odczytu. „Tylko do odczytu” to tylko znaczenie, które mu przypisujesz. Nie ma żadnych odpowiednich gwarancji wbudowanych w C # w ogóle. Możesz mieć właściwość tylko do pobierania, która zmienia się za każdym razem, lub właściwość get / set, w której ustawiacz zgłasza InvalidOperationException("This instance is read-only").
Roman Starkov
21
Patrząc na metody pobierające i ustawiające jako metody, nie rozumiem, dlaczego miałoby to być „zerwaniem umowy” bardziej niż dodanie nowej metody. Kontrakt klasy bazowej nie ma nic wspólnego z tym, czego nie ma, a jedynie z tym, co jest obecne.
Dave Cousineau
37
-1 Nie zgadzam się z tą odpowiedzią i uważam ją za mylącą. Ponieważ klasa bazowa definiuje zachowanie, którego musi przestrzegać każda klasa (patrz zasada podstawienia Liskova), ale nie ogranicza (i nie powinna) ograniczać dodawania zachowania. Klasa bazowa może jedynie definiować, jakie musi być zachowanie, i nie może (i nie powinna) określać, jakiego zachowania „nie wolno” (tj. „Nie może być zapisywalne na konkretnych poziomach)”.
Marcel Valdez Orozco
39

Myślę, że głównym powodem jest po prostu to, że składnia jest zbyt wyraźna, aby działać w inny sposób. Ten kod:

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

jest dość wyraźne, że zarówno opcje, jak geti setsą nadpisane. W setklasie bazowej nie ma , więc kompilator narzeka. Tak jak nie można przesłonić metody, która nie jest zdefiniowana w klasie bazowej, tak samo nie można przesłonić metody ustawiającej.

Możesz powiedzieć, że kompilator powinien odgadnąć twój zamiar i zastosować nadpisanie tylko do metody, którą można przesłonić (tj. Getter w tym przypadku), ale jest to sprzeczne z jedną z zasad projektowania C # - że kompilator nie może odgadnąć twoich zamiarów , ponieważ może zgadnąć źle bez Twojej wiedzy.

Myślę, że następująca składnia może się nieźle sprawdzić, ale jak powtarza Eric Lippert, wdrożenie nawet niewielkiej funkcji, takiej jak ta, nadal wymaga dużego wysiłku ...

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

lub, w przypadku nieruchomości zaimplementowanych,

public int MyProperty { override get; set; }
Roman Starkov
źródło
19

Natknąłem się dzisiaj na ten sam problem i myślę, że mam bardzo ważny powód, by tego chcieć.

Najpierw chciałbym argumentować, że posiadanie właściwości tylko do odczytu niekoniecznie przekłada się na tylko do odczytu. Interpretuję to jako „Z tego interfejsu / abstraktu można uzyskać tę wartość”, co nie oznacza, że ​​niektóre implementacje tego interfejsu / klasy abstrakcyjnej nie będą wymagały od użytkownika / programu do jawnego ustawiania tej wartości. Klasy abstrakcyjne służą do realizacji części potrzebnej funkcjonalności. Nie widzę żadnego powodu, dla którego odziedziczona klasa nie mogłaby dodać setera bez naruszania jakichkolwiek umów.

Poniżej znajduje się uproszczony przykład tego, czego potrzebowałem dzisiaj. Skończyło się na tym, że musiałem dodać setera do mojego interfejsu, aby to obejść. Powodem dodania settera i nie dodawania, powiedzmy, metody SetProp jest to, że jedna konkretna implementacja interfejsu wykorzystywała DataContract / DataMember do serializacji Prop, co byłoby niepotrzebnie skomplikowane, gdybym musiał dodać kolejną właściwość tylko w tym celu serializacji.

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

Wiem, że to tylko post „mój za 2 centy”, ale czuję, że w przypadku oryginalnego plakatu próba zracjonalizowania tego, że to dobra rzecz, wydaje mi się dziwna, zwłaszcza biorąc pod uwagę, że te same ograniczenia nie mają zastosowania w przypadku dziedziczenia bezpośrednio po berło.

Również wzmianka o używaniu new zamiast override nie ma tutaj zastosowania, po prostu nie działa, a nawet gdyby tak było, nie dałoby to pożądanego rezultatu, a mianowicie wirtualnego gettera opisanego przez interfejs.

JJJ
źródło
2
Lub, w moim przypadku, {get; zestaw prywatny; }. Nie naruszam żadnej umowy, ponieważ zestaw pozostaje prywatny i dotyczy tylko klasy pochodnej.
Machtyn
16

To jest możliwe

tl; dr - Jeśli chcesz, możesz zastąpić metodę ustawiającą tylko metodę pobierania. Po prostu:

  1. Utwórz newwłaściwość, która ma zarówno a, jak geti a, setużywając tej samej nazwy.

  2. Jeśli nie zrobisz nic innego, stara getmetoda będzie nadal wywoływana, gdy klasa pochodna zostanie wywołana przez jej typ podstawowy. Aby to naprawić, dodaj abstractwarstwę pośrednią, która używa overridestarej getmetody, aby zmusić ją do zwrócenia wyniku nowej getmetody.

To pozwala nam zastąpić właściwości z get/ setnawet jeśli ich brakowało w ich podstawowej definicji.

Jako bonus możesz także zmienić typ zwrotu, jeśli chcesz.

  • Jeśli podstawową definicją była get-only, można użyć bardziej pochodnego typu zwracanego.

  • Jeśli podstawową definicją było set-only, możesz użyć mniej pochodnego typu zwracanego.

  • Jeśli podstawową definicją była już get/ set, to:

    • możesz użyć bardziej pochodnego typu zwracanego, jeśliset ustawisz go -only;

    • możesz użyć mniej pochodnego typu zwracanego, jeśliget ustawisz go -only.

We wszystkich przypadkach możesz zachować ten sam typ zwrotu, jeśli chcesz. Poniższe przykłady używają tego samego typu zwracanego dla uproszczenia.

Sytuacja: istniejąca gettylko własność

Masz strukturę klas, której nie możesz modyfikować. Może to tylko jedna klasa lub jest to wcześniej istniejące drzewo dziedziczenia. W każdym razie chcesz dodać setmetodę do właściwości, ale nie możesz.

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

Problem: Can't overridethe- getonly with get/set

Chcesz overridez właściwością get/ set, ale nie zostanie ona skompilowana.

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

Rozwiązanie: użyj abstractwarstwy pośredniej

Chociaż nie można bezpośrednio overridez get/ setwłasności, to można :

  1. Utwórz new get/ setproperty o tej samej nazwie.

  2. overridestara getmetoda z akcesorium do nowej getmetody w celu zapewnienia spójności.

Więc najpierw napisz abstractwarstwę pośrednią:

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

Następnie piszesz klasę, która nie skompiluje się wcześniej. Skompiluje się tym razem, ponieważ w rzeczywistości nie jest overrideto getjedyna właściwość; zamiast tego zastępujesz go newsłowem kluczowym.

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

Wynik: wszystko działa!

Oczywiście działa to zgodnie z przeznaczeniem 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.

Wszystko nadal działa zgodnie z przeznaczeniem, gdy jest wyświetlane Djako jedna z jego klas bazowych, np . ALub B. Ale powód, dla którego to działa, może być nieco mniej oczywisty.

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.

Jednak definicja klasy bazowej getjest nadal ostatecznie zastępowana przez definicję klasy pochodnej get, więc nadal jest całkowicie spójna.

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.

Dyskusja

Ta metoda umożliwia dodawanie setmetod gettylko do właściwości. Możesz go również używać do takich rzeczy, jak:

  1. Zmień dowolną właściwość na get-only, set-only lub get-and- setproperty, niezależnie od tego, jaka była w klasie bazowej.

  2. Zmień zwracany typ metody w klasach pochodnych.

Główną wadą jest to, że jest więcej do zrobienia kodowania i dodatkowy element abstract classw drzewie dziedziczenia. Może to być trochę denerwujące w przypadku konstruktorów, które przyjmują parametry, ponieważ muszą one zostać skopiowane / wklejone w warstwie pośredniej.

Nat
źródło
Zadałem to własne pytanie: stackoverflow.com/questions/22210971
Nat
Niezłe podejście (+1). Wątpię jednak, czy będzie to działać sprawnie z frameworkiem podmiotowym.
Mike de Klerk
9

Zgadzam się, że brak możliwości przesłonięcia metody pobierającej w typie pochodnym jest anty-wzorcem. Tylko do odczytu oznacza brak implementacji, a nie umowę o czystym funkcjonale (sugerowaną przez najwyższą odpowiedź głosową).

Podejrzewam, że Microsoft miał to ograniczenie albo z powodu promowania tego samego błędnego przekonania, albo może z powodu uproszczenia gramatyki; chociaż teraz ten zakres można zastosować do uzyskania lub ustawienia indywidualnie, być może możemy mieć nadzieję, że nadpisanie też będzie.

Błędne przekonanie wskazywane przez odpowiedź w najwyższym głosowaniu, że właściwość tylko do odczytu powinna być w jakiś sposób bardziej „czysta” niż właściwość do odczytu / zapisu, jest absurdalne. Wystarczy spojrzeć na wiele typowych właściwości tylko do odczytu we frameworku; wartość nie jest stałą / czysto funkcjonalną; na przykład DateTime.Now jest tylko do odczytu, ale nie jest czystą wartością funkcjonalną. Próba „buforowania” wartości właściwości tylko do odczytu przy założeniu, że następnym razem zwróci tę samą wartość, jest ryzykowna.

W każdym razie zastosowałem jedną z następujących strategii, aby pokonać to ograniczenie; oba są mniej niż doskonałe, ale pozwolą ci utykać poza tym brakiem języka:

   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;
   }
T.Tobler
źródło
DateTime.Now jest czysto funkcjonalny, ponieważ nie ma skutków ubocznych (fakt, że wykonanie zajmuje trochę czasu, można by nazwać efektem ubocznym, ale ponieważ czas wykonania - przynajmniej ograniczony i krótki - nie jest brany pod uwagę efekt uboczny jakiejkolwiek innej metody, tutaj też nie uważałbym jej za jedną).
supercat
1
Cześć supercat. Masz rację co do obserwowalnych zewnętrznie skutków ubocznych, które uniemożliwiają funkcji bycie czystą, ale innym ograniczeniem jest to, że wartość wyniku czystej funkcji musi zależeć tylko od parametrów. Czysta właściwość statyczna jako funkcja musi zatem być zarówno niezmienna, jak i stała. Zaawansowany kompilator może zastąpić wszystkie kolejne wywołania czystej funkcji bez parametrów wynikiem zwróconym przez pierwsze wywołanie.
T.Tobler,
Masz rację, że moja terminologia była nieprecyzyjna, ale myślę, że stosowność czegoś, co jest właściwością tylko do odczytu, a nie metodą, zależy raczej od braku skutków ubocznych niż czystości.
supercat
2

Być może możesz obejść ten problem, tworząc nową właściwość:

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

int IBase.Bar { 
  get { return Bar; }
}
Hallgrim
źródło
4
Ale możesz to zrobić tylko podczas implementowania interfejsu, a nie podczas dziedziczenia z klasy bazowej. Musiałbyś wybrać inną nazwę.
svick
Zakładam, że przykładowa klasa implementuje IBase (coś w rodzaju publicznego interfejsu IBase {int Bar {get;}}), w przeciwnym razie w implementacji class int IBase.Bar {...} jest niedozwolone. Następnie możesz po prostu utworzyć zwykłą właściwość Bar z zarówno get, jak i set, w przypadku gdy właściwość Bar z interfejsu nie wymaga zestawu.
Ibrahim ben Salah
1

Rozumiem wszystkie twoje uwagi, ale w rzeczywistości automatyczne właściwości C # 3.0 stają się bezużyteczne w tym przypadku.

Nie możesz zrobić czegoś takiego:

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

IMO, C # nie powinno ograniczać takich scenariuszy. Deweloper jest odpowiedzialny za jego odpowiednie użycie.

Thomas Danecker
źródło
Nie rozumiem twojego punktu. Czy zgadzasz się, że C # powinien umożliwiać dodanie metody ustawiającej, czy jesteś z większością innych osób, które odpowiedziały na to pytanie?
ripper234
2
Jak powiedziałem, IMO, C # powinno pozwolić na dodanie metody ustawiającej. Deweloper jest odpowiedzialny za jego odpowiednie użycie.
Thomas Danecker,
1

Problem polega na tym, że z jakiegoś powodu Microsoft zdecydował, że powinny istnieć trzy różne typy właściwości: tylko do odczytu, tylko do zapisu i do odczytu i zapisu, z których tylko jedna może istnieć z daną sygnaturą w danym kontekście; właściwości mogą być zastępowane tylko przez identycznie zadeklarowane właściwości. Aby zrobić to, co chcesz, konieczne byłoby utworzenie dwóch właściwości o tej samej nazwie i podpisie - jedna z nich była tylko do odczytu, a druga do odczytu i zapisu.

Osobiście chciałbym, aby można było znieść całe pojęcie „właściwości”, z wyjątkiem tego, że składnia właściwości mogłaby być używana jako cukier składniowy do wywoływania metod „get” i „set”. Ułatwiłoby to nie tylko opcję „dodaj zestaw”, ale także umożliwiłoby „get” zwrócenie innego typu niż „zestaw”. Chociaż taka zdolność nie byłaby używana zbyt często, czasami może być przydatne, gdyby metoda „get” zwracała obiekt opakowania, podczas gdy „zestaw” mógł akceptować opakowanie lub rzeczywiste dane.

superkat
źródło
0

Oto obejście, aby to osiągnąć za 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);
}

W ten sposób możemy ustawić wartość obiektu dla właściwości, która jest tylko do odczytu. Może jednak nie działać we wszystkich scenariuszach!

user2514880
źródło
1
Czy na pewno możesz wywołać metodę ustawiającą za pomocą odbicia, które nie istnieje (jak ma to miejsce w opisanych właściwościach tylko do pobierania)?
LUB Mapper
0

Powinieneś zmienić tytuł pytania, aby albo wyszczególnić, że twoje pytanie dotyczy wyłącznie nadpisywania własności abstrakcyjnej, albo że twoje pytanie dotyczy generalnie przesłaniania właściwości klasy tylko do pobierania.


Jeśli pierwszy (przesłaniając abstrakcyjną właściwość)

Ten kod jest bezużyteczny. Sama klasa bazowa nie powinna mówić, że jesteś zmuszony do przesłonięcia właściwości Get-Only (być może interfejs). Klasa bazowa zapewnia typowe funkcje, które mogą wymagać określonych danych wejściowych z klasy implementującej. Dlatego wspólna funkcjonalność może wywoływać abstrakcyjne właściwości lub metody. W danym przypadku typowe metody funkcjonalności powinny prosić Cię o zastąpienie metody abstrakcyjnej, takiej jak:

public int GetBar(){}

Ale jeśli nie masz nad tym kontroli, a funkcjonalność klasy bazowej odczytuje z własnej właściwości publicznej (dziwne), po prostu zrób to:

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

Chcę zwrócić uwagę na (dziwny) komentarz: powiedziałbym, że najlepszą praktyką jest, aby klasa nie używała swoich własnych właściwości publicznych, ale używała swoich prywatnych / chronionych pól, gdy one istnieją. Więc to jest lepszy wzór:

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 to drugie (przesłaniając właściwość klasy tylko do pobierania)

Każda nieabstrakcyjna właściwość ma metodę ustawiającą. W przeciwnym razie jest bezużyteczny i nie powinieneś go używać. Microsoft nie musi pozwalać ci robić tego, co chcesz. Powód jest taki: seter istnieje w takiej czy innej formie i możesz łatwo osiągnąć to, co chcesz, Veerryy .

Klasa bazowa lub dowolna klasa, za pomocą której można odczytać właściwość {get;}, ma NIEKTÓRE rodzaje odsłoniętej metody ustawiającej dla tej właściwości. Metadane będą wyglądać następująco:

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

Ale implementacja będzie miała dwa końce spektrum złożoności:

Najmniej złożone:

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

Najbardziej złożone:

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

Chodzi mi o to, że tak czy inaczej wystawiasz rozgrywającego. W twoim przypadku możesz chcieć przesłonić, int Barponieważ nie chcesz, aby klasa bazowa sobie z tym poradziła, nie masz dostępu do przeglądu, jak to obsługuje, lub masz za zadanie wbić jakiś kod naprawdę szybko i niegrzecznie wbrew twojej woli .

W ostatnim i dawnym (wniosek)

Krótko mówiąc: Microsoft nie musi niczego zmieniać. Możesz wybrać sposób konfiguracji klasy implementującej i, bez konstruktora, użyć wszystkich lub żadnej klasy bazowej.

Suamere
źródło
0

Rozwiązanie tylko dla niewielkiego podzbioru przypadków użycia, niemniej jednak: w C # 6.0 metoda ustawiająca „tylko do odczytu” jest dodawana automatycznie dla przesłoniętych właściwości tylko pobierających.

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

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

    public ConcreteClass(int bar)
    {
        Bar = bar;
    }
}
lxa
źródło
-1

Ponieważ to złamałoby koncepcję hermetyzacji i ukrywania implementacji. Rozważ przypadek, kiedy tworzysz klasę, wysyłasz ją, a następnie konsument Twojej klasy może ustawić właściwość, dla której pierwotnie zapewniłeś tylko metodę pobierającą. Skutecznie zakłóciłoby to wszystkie niezmienniki twojej klasy, na których możesz polegać w swojej implementacji.

Piotr K.
źródło
1
-1: Posiadanie setera nie umożliwia automatycznie potomkowi przerwania jakichkolwiek niezmienników. Ustawiający może nadal zmieniać tylko rzeczy, które i tak są już zmienne przez potomka.
Roman Starkov
@RomanStarkov To prawda. Ale kwestia tego postu jest; Dlaczego nie możesz dodać ustawiacza do abstrakcji, który każe ci tego nie robić. Twój komentarz byłby odpowiedni w przeciwnym pytaniu; Dlaczego firma Microsoft zezwala na tworzenie właściwości ReadOnly w abstrakcji, jeśli potomek i tak ma pełną kontrolę nad swoimi polami prywatnymi?
Suamere
+1: To pytanie dotyczy tego, dlaczego Microsoft nie pozwala na zerwanie umowy. Chociaż powodem twoich głosów przeciw jest bardziej to, dlaczego Microsoft zezwala na zawarcie umowy tylko z właściwościami tylko do odczytu. Więc ta odpowiedź jest poprawna w kontekście.
Suamere
@Suamere umowa nie stwierdza, że ​​właściwości nie można zmienić. Stwierdza tylko, że istnieje właściwość, którą można odczytać. Dodanie setera nie zerwie tego kontraktu. Możesz zinterpretować to jako zamiar utworzenia właściwości tylko do odczytu, ale nie możesz zagwarantować w C # 6, że nie ma sposobu, aby to zmienić, stąd umowa, o której myślisz, tak naprawdę nie istnieje. Pomyśl o interfejsie: może oferować kontrakt na właściwość gettable (nie tylko get-only!). Ten interfejs można zaimplementować za pomocą właściwości get + set.
Roman Starkov
Podchodzisz do tego z perspektywy konkretnego autora. Może to zmienić, jak mu się podoba. Podchodzę do tego z perspektywy autora klasy bazowej -> Podchodzę jako końcowy konsument wyniku klasy implementującej. Autor klasy bazowej nie chce, aby jakikolwiek konsument przyszłych klas konkretnych mógł zmieniać tę właściwość bez ujawniania przez autora konkretnej klasy specjalnej metody do obsługi poszczególnych przypadków. Ponieważ w klasie bazowej ta właściwość jest traktowana jako niezmienna i jest przekazywana tylko przez ujawnienie jej jako takiej.
Suamere
-1

To nie jest niemożliwe. Musisz po prostu użyć słowa kluczowego „nowe” w swojej usłudze. Na przykład,

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

Wydaje się, że takie podejście zadowala obie strony tej dyskusji. Użycie „nowego” powoduje zerwanie kontraktu między implementacją klasy bazowej a implementacją podklasy. Jest to konieczne, gdy klasa może mieć wiele kontraktów (za pośrednictwem interfejsu lub klasy bazowej).

Mam nadzieję że to pomoże

mbolt35
źródło
29
Ta odpowiedź jest nieprawidłowa, ponieważ oznaczona właściwość newnie zastępuje już wirtualnej właściwości podstawowej. W szczególności, jeśli właściwość base jest abstrakcyjna , tak jak w oryginalnym poście, nie jest możliwe użycie newsłowa kluczowego w nie-abstrakcyjnej podklasie, ponieważ musisz zastąpić wszystkich abstrakcyjnych członków.
Timwi,
Jest to fenomenalnie niebezpieczne, ponieważ poniższe zwracają różne wyniki pomimo tego, że jest to ten sam obiekt: Test t = new Test(); Base b = t; Console.WriteLine(t.BaseProperty)daje 5, Console.WriteLine(b.BaseProperty)daje 0, a kiedy twój następca musi to debugować, lepiej odejdź daleko, daleko.
Mark Sowul
Zgadzam się z Markiem, to jest wyjątkowo paskudne. Gdyby obie implementacje BaseProperty były obsługiwane przez tę samą zmienną, byłoby dobrze, chociaż IMHO. IE: Zabezpiecz _baseProperty i usuń _testBaseProperty. Ponadto, AFAIK implementacja w Base nie musi być wirtualna, ponieważ tak naprawdę jej nie zastępujesz.
Steazy
Większość odpowiedzi na pytania może mieć uzasadnioną opinię i wkład. Ale oprócz poprzednich komentarzy, moja główna krytyka dotycząca tej odpowiedzi jest taka, że ​​uważam, że rzeczywiste pytanie może dotyczyć dosłownie abstrakcyjnych właściwości w bazie kodu, nad którą konsument nie ma kontroli. Więc moja krytyka jest taka, że ​​ta odpowiedź może nie pasować do pytania.
Suamere,
-1

Właściwość tylko do odczytu w klasie bazowej wskazuje, że ta właściwość reprezentuje wartość, którą zawsze można określić z poziomu klasy (na przykład wartość wyliczenia pasująca do kontekstu (db-) obiektu). Zatem odpowiedzialność za określenie wartości pozostaje w obrębie klasy.

Dodanie metody ustawiającej spowodowałoby tutaj niezręczny problem: błąd walidacji powinien wystąpić, jeśli ustawisz wartość na cokolwiek innego niż pojedyncza możliwa wartość, którą już ma.

Jednak reguły często mają wyjątki. Jest bardzo możliwe, że na przykład w jednej klasie pochodnej kontekst zawęża możliwe wartości wyliczenia do 3 z 10, ale użytkownik tego obiektu nadal musi zdecydować, który z nich jest poprawny. Klasa pochodna musi delegować odpowiedzialność za określenie wartości na użytkownika tego obiektu. Ważne jest, aby zdać sobie sprawę, że użytkownik tego obiektu powinien być dobrze świadomy tego wyjątku i przyjąć na siebie odpowiedzialność za ustawienie prawidłowej wartości.

Moim rozwiązaniem w tego rodzaju sytuacjach byłoby pozostawienie właściwości tylko do odczytu i dodanie nowej właściwości do odczytu i zapisu do klasy pochodnej w celu obsługi wyjątku. Przesłonięcie oryginalnej właściwości po prostu zwróci wartość nowej właściwości. Nowa właściwość może mieć własną nazwę wskazującą poprawnie kontekst tego wyjątku.

Potwierdza to również słuszną uwagę: „utrudnij jak najbardziej nieporozumienia” Gishu.

Remco te Wierik
źródło
1
Nie nieporadność będzie dorozumiany przez ReadableMatrixklasę (z dwóch argumentów tylko do odczytu), mający własności indeksowane podtypy ImmutableMatrixi MutableMatrix. Kod, który musi tylko czytać matrycę i nie obchodzi go, czy może się zmienić w przyszłości, może akceptować parametr typu ReadableMatrixi być całkowicie zadowolony ze zmiennymi lub niezmiennymi podtypami.
supercat
Dodatkowy ustawiacz może modyfikować prywatną zmienną w klasie. Tak zwana właściwość „tylko do odczytu” z klasy bazowej nadal określałaby wartość „z poziomu klasy” (czytając nową zmienną prywatną). Nie widzę tam problemu. Spójrz też na przykład, co to List<T>.Countrobi: Nie można go przypisać, ale to nie znaczy, że jest „tylko do odczytu”, ponieważ nie może być modyfikowany przez użytkowników klasy. Można go bardzo dobrze modyfikować, choć pośrednio, przez dodawanie i usuwanie elementów listy.
LUB Mapper
-2

Ponieważ na poziomie IL właściwość odczytu / zapisu przekłada się na dwie metody (pobierającą i ustawiającą).

Podczas zastępowania musisz nadal wspierać podstawowy interfejs. Gdybyś mógł dodać ustawiającego, w rzeczywistości dodawałbyś nową metodę, która pozostałaby niewidoczna dla świata zewnętrznego, jeśli chodzi o interfejs twoich klas.

To prawda, że ​​dodanie nowej metody nie naruszyłoby kompatybilności jako takiej, ale ponieważ pozostałaby ona ukryta, decyzja o jej odrzuceniu ma sens.

Ishmaeel
źródło
2
Nie obchodzi mnie tutaj „świat zewnętrzny”. Chcę dodać funkcjonalność do klasy, która jest zawsze widoczna tylko dla kodu, który zna określoną klasę, a nie podstawową.
ripper234
@ ripper234 Zobacz odpowiedź Matta bolta: jeśli chcesz, aby nowa właściwość była widoczna tylko dla użytkowników klasy pochodnej, powinieneś użyć newzamiast override.
Dan Berindei
1
Nie pozostaje ukryty. Mówisz, że nie możesz dodać nowej metody do klasy podrzędnej, ponieważ nowa metoda będzie „pozostać ukryta przed światem zewnętrznym”. Oczywiście pozostaje ukryty z perspektywy klasy bazowej, ale jest dostępny dla klientów klasy podrzędnej. (To znaczy, aby wpłynąć na to, co getter powinien zwrócić)
Sheepy
Sheepy, wcale nie o tym mówię. Chodzi mi o to, że dokładnie tak, jak wskazałeś, pozostałoby to ukryte „z perspektywy klasy bazowej”. Dlatego dodałem „jeśli chodzi o interfejs twoich zajęć ”. Jeśli tworzysz podklasę klasy bazowej, jest prawie pewne, że „świat zewnętrzny” będzie odnosił się do instancji obiektów z typem klasy bazowej. Gdyby użyli bezpośrednio twojej konkretnej klasy, nie potrzebowałbyś kompatybilności z klasą bazową i mógłbyś, jak najbardziej, używać newz twoimi dodatkami.
Ishmaeel,
-2

Ponieważ klasa, która ma właściwość tylko do odczytu (bez metody ustawiającej), prawdopodobnie ma ku temu dobry powód. Na przykład może nie być żadnego bazowego magazynu danych. Umożliwienie stworzenia setera łamie kontrakt przedstawiony przez klasę. To po prostu złe OOP.

Joel Coehoorn
źródło
Głosowanie za. To jest bardzo zwięzłe. Dlaczego firma Microsoft miałaby pozwolić konsumentowi klasy bazowej na zerwanie umowy? Zgadzam się z tą bardzo krótką wersją mojej zbyt długiej i mylącej odpowiedzi. Lol. Jeśli jesteś konsumentem klasy bazowej, nie powinieneś chcieć zrywać umowy, ale MOŻESZ ją wdrożyć na kilka różnych sposobów.
Suamere