Pourquoi est-il impossible de remplacer une propriété en lecture seule et d'ajouter un setter? [fermé]


141

Pourquoi le code C # suivant n'est-il pas autorisé:

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': impossible de remplacer car 'BaseClass.Bar' n'a pas d'accesseur de jeu remplaçable


9
StackOverflow a une capacité limitée à répondre aux questions au nom de Microsoft. Pensez à reformuler votre question.
Jay Bazuzi

1
Incidemment, un remède simple à ce comportement ennuyeux dans .net est d'avoir une propriété en lecture seule non virtuelle Fooqui ne fait rien d'autre que envelopper une GetFoométhode abstraite protégée . Un type dérivé abstrait peut masquer la propriété avec une propriété de lecture-écriture non virtuelle qui ne fait rien d'autre que envelopper la GetFoométhode susmentionnée avec une SetFoométhode abstraite ; un type dérivé concret pourrait faire ce qui précède mais fournir des définitions pour GetFooet SetFoo.
supercat du

1
Juste pour ajouter du carburant au feu, C ++ / CLI le permet.
ymett

1
La possibilité d'ajouter un accesseur lors de la substitution d'une propriété a été proposée comme une modification pour une future version de C # ( github.com/dotnet/roslyn/issues/9482 ).
Brandon Bonds

1
Pour être franc, cette question est souvent soulevée lorsqu'il est nécessaire d'étendre un type de bibliothèque existant qui utilise le modèle suivant: classe de base en lecture seule, classe modifiable dérivée. Étant donné que la bibliothèque utilise déjà un mauvais modèle (tel que WPF), il est nécessaire de contourner ce mauvais modèle. La conférence ne vous évite pas d'avoir à mettre en œuvre une solution de contournement à la fin de la journée.
rwong

Réponses:


-4

Parce que l'auteur de Baseclass a explicitement déclaré que Bar doit être une propriété en lecture seule. Cela n'a aucun sens que les dérivations rompent ce contrat et le rendent en lecture-écriture.

Je suis avec Microsoft sur celui-ci.
Disons que je suis un nouveau programmeur à qui on a dit de coder avec la dérivation Baseclass. J'écris quelque chose qui suppose que Bar ne peut pas être écrit (puisque la classe de base déclare explicitement qu'il s'agit d'une propriété get only). Maintenant, avec votre dérivation, mon code peut casser. par exemple

public class BarProvider
{ BaseClass _source;
  Bar _currentBar;

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

  public Bar getBar()
  { return _currentBar;  }
}

Puisque Bar ne peut pas être défini selon l'interface BaseClass, BarProvider suppose que la mise en cache est une chose sûre à faire - Puisque Bar ne peut pas être modifié. Mais si set était possible dans une dérivation, cette classe pourrait servir des valeurs périmées si quelqu'un modifiait la propriété Bar de l'objet _source en externe. Le point étant `` Soyez ouvert, évitez de faire des choses sournoises et de surprendre les gens ''

Mise à jour : Ilya Ryzhenkov demande "Pourquoi les interfaces ne fonctionnent-elles pas selon les mêmes règles alors?" Hmm ... cela devient plus confus quand j'y pense.
Une interface est un contrat qui dit «s'attendre à ce qu'une implémentation ait une propriété de lecture nommée Bar». Personnellement, je suis beaucoup moins susceptible de faire cette hypothèse de lecture seule si j'ai vu une interface. Quand je vois une propriété en lecture seule sur une interface, je la lis comme «Toute implémentation exposerait cet attribut Bar» ... sur une classe de base, il clique comme «La barre est une propriété en lecture seule». Bien sûr, techniquement, vous ne rompez pas le contrat ... vous faites plus. Donc vous avez raison dans un sens ... Je terminerais en disant: «Faites en sorte qu'il soit aussi difficile que possible que des malentendus surgissent».


94
Je ne suis toujours pas convaincu. Même s'il n'y a pas de setter explicite, cela ne garantit pas que la propriété retournera toujours le même objet - il peut toujours être modifié par une autre méthode.
ripper234

34
Ne devrait-il pas en être de même pour les interfaces? Vous pouvez avoir une propriété en lecture seule sur l'interface et en lecture / écriture sur le type d'implémentation.
Ilya Ryzhenkov

49
Je ne suis pas d'accord: la classe de base a seulement déclaré la propriété comme n'ayant pas de setter; ce n'est pas la même chose que la propriété en lecture seule. "Lecture seule" n'est que la signification que vous lui attribuez. Il n'y a pas du tout de garanties pertinentes intégrées dans C #. Vous pouvez avoir une propriété get-only qui change à chaque fois, ou une propriété get / set où le setter lève un InvalidOperationException("This instance is read-only").
Roman Starkov

21
En regardant les getters et les setters comme des méthodes, je ne vois pas pourquoi cela devrait être une "rupture de contrat" ​​pas plus que l'ajout d'une nouvelle méthode. Un contrat de classe de base n'a rien à voir avec les fonctionnalités non présentes, mais uniquement avec ce qui est présent.
Dave Cousineau

37
-1 Je ne suis pas d'accord avec cette réponse et je la trouve erronée. Puisqu'une classe de base définit le comportement auquel toute classe doit se conformer (voir le principe de substitution de Liskov), mais ne se limite pas (et ne devrait pas) se limiter à ajouter un comportement. Une classe de base peut seulement définir ce que le comportement doit être, et elle ne peut pas (et ne doit pas) spécifier ce que le comportement «ne doit pas être» (c'est-à-dire «ne doit pas être accessible en écriture à des niveaux concrets)».
Marcel Valdez Orozco

39

Je pense que la raison principale est simplement que la syntaxe est trop explicite pour que cela fonctionne d'une autre manière. Ce code:

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

est assez explicite que le getet le setsont des remplacements. Il n'y a pas setdans la classe de base, donc le compilateur se plaint. Tout comme vous ne pouvez pas remplacer une méthode qui n'est pas définie dans la classe de base, vous ne pouvez pas non plus remplacer un setter.

Vous pourriez dire que le compilateur devrait deviner votre intention et n'appliquer le remplacement qu'à la méthode qui peut être surchargée (c'est-à-dire le getter dans ce cas), mais cela va à l'encontre de l'un des principes de conception C # - que le compilateur ne doit pas deviner vos intentions , car il peut deviner mal sans que vous le sachiez.

Je pense que la syntaxe suivante pourrait faire l'affaire, mais comme Eric Lippert n'arrête pas de le dire, mettre en œuvre même une fonctionnalité mineure comme celle-ci représente toujours un effort majeur ...

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

ou, pour les propriétés auto-implémentées,

public int MyProperty { override get; set; }

19

Je suis tombé sur le même problème aujourd'hui et je pense avoir une raison très valable de vouloir cela.

Tout d'abord, j'aimerais dire que le fait d'avoir une propriété get-only ne se traduit pas nécessairement en lecture seule. Je l'interprète comme "A partir de cette interface / abtract, vous pouvez obtenir cette valeur", cela ne signifie pas qu'une implémentation de cette interface / classe abstraite n'aura pas besoin de l'utilisateur / du programme pour définir cette valeur explicitement. Les classes abstraites servent à implémenter une partie des fonctionnalités nécessaires. Je ne vois absolument aucune raison pour laquelle une classe héritée ne pourrait pas ajouter un setter sans violer aucun contrat.

Ce qui suit est un exemple simplifié de ce dont j'avais besoin aujourd'hui. J'ai fini par devoir ajouter un setter dans mon interface juste pour contourner cela. La raison de l'ajout du setter et de ne pas ajouter, disons, une méthode SetProp est qu'une implémentation particulière de l'interface a utilisé DataContract / DataMember pour la sérialisation de Prop, ce qui aurait été rendu inutilement compliqué si j'avais dû ajouter une autre propriété juste à cette fin de sérialisation.

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

Je sais que ce n'est qu'un article "mes 2 cents", mais je pense qu'avec l'affiche originale et essayer de rationaliser que c'est une bonne chose me semble étrange, d'autant plus que les mêmes limitations ne s'appliquent pas lors de l'héritage direct d'un interface.

De plus, la mention d'utiliser new au lieu de override ne s'applique pas ici, cela ne fonctionne tout simplement pas et même si c'était le cas, cela ne vous donnerait pas le résultat souhaité, à savoir un getter virtuel tel que décrit par l'interface.


2
Ou, dans mon cas, {get; ensemble privé; }. Je ne viole aucun contrat car l'ensemble reste privé et ne concerne que la classe dérivée.
Machtyn

16

C'est possible

tl; dr - Vous pouvez remplacer une méthode get-only avec un setter si vous le souhaitez. C'est fondamentalement juste:

  1. Créez une newpropriété qui a à la fois un getet un en setutilisant le même nom.

  2. Si vous ne faites rien d'autre, l'ancienne getméthode sera toujours appelée lorsque la classe dérivée sera appelée via son type de base. Pour résoudre ce problème, ajoutez une abstractcouche intermédiaire qui utilise overridel'ancienne getméthode pour la forcer à renvoyer le getrésultat de la nouvelle méthode.

Cela nous permet de remplacer les propriétés par get/ setmême s'il en manquait un dans leur définition de base.

En prime, vous pouvez également modifier le type de retour si vous le souhaitez.

  • Si la définition de base était get-only, vous pouvez utiliser un type de retour plus dérivé.

  • Si la définition de base était set-only, vous pouvez utiliser un type de retour moins dérivé.

  • Si la définition de base était déjà get/ set, alors:

    • vous pouvez utiliser un type de retour plus dérivé si vous le faites set-only;

    • vous pouvez utiliser un type de retour moins dérivé si vous le getdéfinissez -only.

Dans tous les cas, vous pouvez conserver le même type de retour si vous le souhaitez. Les exemples ci-dessous utilisent le même type de retour pour plus de simplicité.

Situation: getpropriété préexistante uniquement

Vous avez une structure de classe que vous ne pouvez pas modifier. C'est peut-être juste une classe ou c'est un arbre d'héritage préexistant. Dans tous les cas, vous souhaitez ajouter une setméthode à une propriété, mais vous ne pouvez pas.

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ème: ne peut pas overridele get-seulement avec get/set

Vous voulez overrideavec une propriété get/ set, mais elle ne se compilera pas.

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

Solution: utilisez une abstractcouche intermédiaire

Bien que vous ne puissiez pas directement overrideavec une propriété get/ set, vous pouvez :

  1. Créez une propriété new get/ setavec le même nom.

  2. overridel'ancienne getméthode avec un accesseur à la nouvelle getméthode pour assurer la cohérence.

Donc, vous écrivez d'abord la abstractcouche intermédiaire:

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

Ensuite, vous écrivez la classe qui ne serait pas compilée plus tôt. Il compilera cette fois parce que vous n'êtes pas en fait overridela getpropriété -only; à la place, vous le remplacez à l'aide du newmot - clé.

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

Résultat: tout fonctionne!

De toute évidence, cela fonctionne comme prévu 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.

Tout fonctionne toujours comme prévu lors de la visualisation Dcomme l'une de ses classes de base, par exemple Aou B. Mais, la raison pour laquelle cela fonctionne pourrait être un peu moins évidente.

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.

Cependant, la définition de la classe de base de getest toujours remplacée par la définition de la classe dérivée de get, elle est donc toujours totalement cohérente.

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.

Discussion

Cette méthode vous permet d'ajouter des setméthodes aux getpropriétés -only. Vous pouvez également l'utiliser pour faire des choses comme:

  1. Changez n'importe quelle propriété en une propriété get-only, set-only ou get-and- set, indépendamment de ce qu'elle était dans une classe de base.

  2. Modifiez le type de retour d'une méthode dans les classes dérivées.

Les principaux inconvénients sont qu'il y a plus de codage à faire et un supplément abstract classdans l'arbre d'héritage. Cela peut être un peu gênant avec les constructeurs qui prennent des paramètres car ceux-ci doivent être copiés / collés dans la couche intermédiaire.


A donné sa propre question: stackoverflow.com/questions/22210971
Nat

Belle approche (+1). Je doute cependant que cela fonctionne correctement avec le cadre d'entité.
Mike de Klerk

9

Je suis d'accord que ne pas pouvoir remplacer un getter dans un type dérivé est un anti-pattern. Lecture seule spécifie le manque d'implémentation, pas un contrat d'une pure fonctionnelle (implicite par la réponse du vote le plus élevé).

Je soupçonne que Microsoft avait cette limitation soit parce que la même idée fausse a été promue, soit peut-être à cause d'une simplification de la grammaire; cependant, maintenant que la portée peut être appliquée pour obtenir ou définir individuellement, peut-être pouvons-nous espérer que le remplacement le sera également.

L'idée fausse indiquée par la réponse du vote le plus élevé, selon laquelle une propriété en lecture seule devrait d'une manière ou d'une autre être plus «pure» qu'une propriété en lecture / écriture est ridicule. Regardez simplement de nombreuses propriétés courantes en lecture seule dans le framework; la valeur n'est pas une constante / purement fonctionnelle; par exemple, DateTime.Now est en lecture seule, mais tout sauf une valeur fonctionnelle pure. Une tentative de «mettre en cache» une valeur d'une propriété en lecture seule en supposant qu'elle retournera la même valeur la prochaine fois est risquée.

Dans tous les cas, j'ai utilisé l'une des stratégies suivantes pour surmonter cette limitation; les deux sont loin d'être parfaits, mais vous permettront de boiter au-delà de cette carence linguistique:

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

DateTime. un effet secondaire de toute autre méthode, je ne le considérerais pas non plus ici).
supercat

1
Salut supercat. Vous avez raison sur les effets secondaires observables de l'extérieur qui empêchent une fonction d'être pure, mais l'autre contrainte est qu'une valeur de résultat de fonction pure ne doit dépendre que des paramètres. Une propriété statique pure, en tant que fonction, doit donc être à la fois immuable et constante. Un compilateur sophistiqué peut remplacer tous les appels ultérieurs à une fonction pure sans paramètres par le résultat renvoyé par le premier appel.
T.Tobler

Vous avez raison de dire que ma terminologie était imprécise, mais je pense que le fait que quelque chose soit une propriété en lecture seule plutôt qu'une méthode dépend d'un manque d'effets secondaires, plutôt que de pureté.
supercat

2

Vous pourriez peut-être contourner le problème en créant une nouvelle propriété:

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

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

4
Mais vous ne pouvez le faire que lors de l'implémentation d'une interface, pas lors de l'héritage d'une classe de base. Vous devrez choisir un autre nom.
svick

Je suppose que l'exemple de classe implémente IBase (quelque chose comme l'interface publique IBase {int Bar {get;}}), sinon dans l'implémentation de la classe, int IBase.Bar {...} n'est pas autorisé. Ensuite, vous pouvez simplement créer une propriété standard Bar avec à la fois get et set, si la propriété Bar de l'interface ne nécessite pas d'ensemble.
Ibrahim ben Salah

1

Je peux comprendre tous vos points, mais effectivement, les propriétés automatiques de C # 3.0 deviennent inutiles dans ce cas.

Vous ne pouvez rien faire de tel:

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

OMI, C # ne devrait pas restreindre de tels scénarios. Il est de la responsabilité du développeur de l'utiliser en conséquence.


Je ne comprends pas votre point. Êtes-vous d'accord avec le fait que C # devrait autoriser l'ajout d'un setter, ou êtes-vous avec la plupart des autres personnes qui ont répondu à cette question?
ripper234

2
Comme je l'ai dit, l'OMI, C # devrait permettre d'ajouter un setter. Il est de la responsabilité du développeur de l'utiliser en conséquence.
Thomas Danecker

1

Le problème est que pour une raison quelconque, Microsoft a décidé qu'il devrait y avoir trois types de propriétés distincts: lecture seule, écriture seule et lecture-écriture, dont un seul peut exister avec une signature donnée dans un contexte donné; les propriétés ne peuvent être remplacées que par des propriétés déclarées de manière identique. Pour faire ce que vous voulez, il serait nécessaire de créer deux propriétés avec le même nom et la même signature - dont l'une était en lecture seule et l'autre en lecture-écriture.

Personnellement, je souhaite que tout le concept de "propriétés" puisse être aboli, sauf que la syntaxe de propriété pourrait être utilisée comme sucre syntaxique pour appeler les méthodes "get" et "set". Cela faciliterait non seulement l'option 'ajouter un ensemble', mais permettrait également à 'get' de renvoyer un type différent de 'set'. Bien qu'une telle capacité ne soit pas très souvent utilisée, il peut parfois être utile qu'une méthode «get» renvoie un objet wrapper tandis que le «set» peut accepter soit un wrapper, soit des données réelles.


0

Voici une solution de contournement pour y parvenir en utilisant 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);
}

De cette façon, nous pouvons définir la valeur d'objet sur une propriété en lecture seule. Cela ne fonctionnera peut-être pas dans tous les scénarios!


1
Êtes-vous sûr de pouvoir appeler un setter via une réflexion qui n'existe pas (comme cela se produit dans les propriétés getter uniquement décrites)?
OR Mapper

0

Vous devez modifier le titre de votre question afin de préciser que votre question concerne uniquement le remplacement d'une propriété abstraite ou que votre question concerne généralement le remplacement de la propriété get-only d'une classe.


Si le premier (remplaçant une propriété abstraite)

Ce code est inutile. Une classe de base seule ne devrait pas vous dire que vous êtes obligé de remplacer une propriété Get-Only (peut-être une interface). Une classe de base fournit des fonctionnalités communes qui peuvent nécessiter une entrée spécifique d'une classe d'implémentation. Par conséquent, la fonctionnalité commune peut appeler des propriétés ou des méthodes abstraites. Dans le cas donné, les méthodes de fonctionnalité courantes devraient vous demander de remplacer une méthode abstraite telle que:

public int GetBar(){}

Mais si vous n'avez aucun contrôle sur cela et que la fonctionnalité de la classe de base lit à partir de sa propre propriété publique (bizarre), alors faites simplement ceci:

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

Je tiens à souligner le commentaire (étrange): je dirais qu'une meilleure pratique est pour une classe de ne pas utiliser ses propres propriétés publiques, mais d'utiliser ses champs privés / protégés lorsqu'ils existent. C'est donc un meilleur modèle:

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

Si ce dernier (remplaçant la propriété get-only d'une classe)

Chaque propriété non abstraite a un setter. Sinon, c'est inutile et vous ne devriez pas vous en soucier. Microsoft n'est pas obligé de vous autoriser à faire ce que vous voulez. La raison étant: le setter existe sous une forme ou une autre, et vous pouvez accomplir ce que vous voulez Veerryy facilement.

La classe de base, ou une classe où vous pouvez lire une propriété avec {get;}, a SOME sorte de poseur de cette propriété exposée. Les métadonnées ressembleront à ceci:

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

Mais la mise en œuvre aura deux extrémités du spectre de la complexité:

Le moins complexe:

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

Le plus complexe:

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

Mon point étant, de toute façon, vous avez le passeur exposé. Dans votre cas, vous voudrez peut-être remplacer int Barparce que vous ne voulez pas que la classe de base le gère, que vous n'avez pas accès pour examiner comment elle le gère ou que vous avez été chargé de pirater du code très rapidement contre votre volonté. .

Dans les derniers et les anciens (Conclusion)

En bref: il n'est pas nécessaire que Microsoft change quoi que ce soit. Vous pouvez choisir comment votre classe d'implémentation est configurée et, sans le constructeur, utiliser tout ou aucune de la classe de base.


0

Solution pour seulement un petit sous-ensemble de cas d'utilisation, mais néanmoins: en C # 6.0, le setter "lecture seule" est automatiquement ajouté pour les propriétés écrasées en lecture seule.

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

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

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

-1

Parce que cela briserait le concept d'encapsulation et de dissimulation de la mise en œuvre. Considérez le cas lorsque vous créez une classe, l'expédiez, puis le consommateur de votre classe se rend capable de définir une propriété pour laquelle vous ne fournissez à l'origine qu'un getter. Cela perturberait efficacement tous les invariants de votre classe dont vous pouvez compter dans votre implémentation.


1
-1: Avoir un setter ne permet pas automatiquement à un descendant de casser les invariants. Le passeur ne peut toujours changer que des choses qui sont déjà modifiables par le descendant de toute façon.
Roman Starkov le

@RomanStarkov C'est vrai. Mais la question de ce poste est; Pourquoi ne peut pas ajouter un setter à une abstraction qui vous engage à ne pas le faire. Votre commentaire serait approprié dans la question opposée; Pourquoi Microsoft vous permet-il de créer une propriété ReadOnly dans une abstraction si le descendant a quand même le contrôle total de ses champs privés?
Suamere

+1: Cette question concerne les raisons pour lesquelles Microsoft ne vous permet pas de rompre un contrat. Bien que la raison de vos votes négatifs soit davantage la raison pour laquelle Microsoft autorise un contrat à inclure uniquement des propriétés en lecture seule. Cette réponse est donc correcte dans le contexte.
Suamere

@Suamere le contrat n'indique pas que la propriété ne peut pas être modifiée. Tout ce qu'il dit, c'est qu'il existe une propriété qui peut être lue. L'ajout d'un passeur ne rompra pas ce contrat. Vous pouvez l' interpréter comme une intention de créer une propriété en lecture seule, mais vous ne pouvez pas garantir en C # 6 qu'il n'y a aucun moyen de le changer, donc le contrat auquel vous pensez n'est pas réellement là. Pensez à une interface: elle peut proposer un contrat sur une propriété pouvant être obtenue (pas seulement!). Cette interface peut être implémentée par une propriété get + set très bien.
Roman Starkov

Vous abordez cela du point de vue de l'auteur concret. Il peut le changer à sa guise. Je l'aborde du point de vue de l'auteur de la classe de base -> En tant que consommateur de résultat final de la classe d'implémentation. L'auteur de la classe de base ne veut pas qu'un consommateur des futures classes concrètes puisse changer cette propriété sans que l'auteur de la classe concrète expose une méthode spéciale pour gérer au cas par cas. Parce que dans la classe de base, cette propriété est traitée comme immuable, et cela est communiqué en l'exposant uniquement en tant que telle.
Suamere

-1

Ce n'est pas impossible. Il vous suffit d'utiliser le mot-clé «nouveau» dans votre propriété. Par exemple,

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

Il semble que cette approche satisfait les deux côtés de cette discussion. L'utilisation de "new" rompt le contrat entre l'implémentation de la classe de base et l'implémentation de la sous-classe. Ceci est nécessaire lorsqu'une classe peut avoir plusieurs contrats (via l'interface ou la classe de base).

J'espère que cela t'aides


29
Cette réponse est erronée car la propriété marquée newne remplace plus celle de base virtuelle. En particulier, si la propriété de base est abstraite , comme dans le message d'origine, il est impossible d'utiliser le newmot - clé dans une sous-classe non abstraite car vous devez remplacer tous les membres abstraits.
Timwi

C'est phénoménalement dangereux car les résultats suivants renvoient des résultats différents bien qu'il s'agisse du même objet: Test t = new Test(); Base b = t; Console.WriteLine(t.BaseProperty)vous donne 5, Console.WriteLine(b.BaseProperty)vous donne 0, et lorsque votre successeur doit déboguer cela, vous feriez mieux de vous déplacer loin, très loin.
Mark Sowul

Je suis d'accord avec Mark, c'est extrêmement méchant. Si les deux implémentations de BaseProperty étaient soutenues par la même variable, ce serait bien à mon humble avis. IE: Rendre _baseProperty protégé et supprimer _testBaseProperty. De plus, AFAIK, l'implémentation dans Base n'a pas besoin d'être virtuelle puisque vous ne la remplacez pas réellement.
Steazy

La plupart des réponses aux questions peuvent avoir une opinion et une contribution valables. Mais à part les commentaires précédents, ma principale critique sur cette réponse est que je pense que la question réelle peut être littéralement sur les propriétés abstraites dans une base de code sur laquelle le consommateur n'a aucun contrôle. Ma critique est donc que cette réponse ne correspond peut-être pas à la question.
Suamere

-1

Une propriété en lecture seule dans la classe de base indique que cette propriété représente une valeur qui peut toujours être déterminée à partir de la classe (par exemple une valeur enum correspondant au contexte (db-) d'un objet). La responsabilité de déterminer la valeur reste donc au sein de la classe.

L'ajout d'un setter provoquerait ici un problème gênant: une erreur de validation devrait se produire si vous définissez la valeur sur autre chose que la seule valeur possible qu'elle possède déjà.

Cependant, les règles ont souvent des exceptions. Il est très bien possible que, par exemple, dans une classe dérivée, le contexte réduise les valeurs d'énumération possibles à 3 sur 10, mais l'utilisateur de cet objet doit encore décider lequel est correct. La classe dérivée doit déléguer la responsabilité de déterminer la valeur à l'utilisateur de cet objet. Il est important de comprendre que l'utilisateur de cet objet doit être bien conscient de cette exception et assumer la responsabilité de définir la valeur correcte.

Ma solution dans ce genre de situation serait de laisser la propriété en lecture seule et d'ajouter une nouvelle propriété en lecture-écriture à la classe dérivée pour prendre en charge l'exception. Le remplacement de la propriété d'origine renverra simplement la valeur de la nouvelle propriété. La nouvelle propriété peut avoir un nom approprié indiquant correctement le contexte de cette exception.

Cela confirme également la remarque valable: "faites en sorte qu'il soit aussi difficile que possible que des malentendus surgissent" par Gishu.


1
Aucune maladresse ne serait impliquée par une ReadableMatrixclasse (avec une propriété indexée en lecture seule à deux arguments) ayant des sous ImmutableMatrix- types et MutableMatrix. Un code qui n'a besoin que de lire une matrice, et ne se soucie pas de savoir s'il pourrait changer quelque temps dans le futur, pourrait accepter un paramètre de type ReadableMatrixet être parfaitement satisfait des sous-types mutables ou immuables.
supercat

Un setter supplémentaire pourrait modifier une variable privée dans la classe. La propriété dite «en lecture seule» de la classe de base déterminerait alors toujours la valeur «à partir de la classe» (en lisant la nouvelle variable privée). Je ne vois pas le problème ici. Regardez également, par exemple, ce qui List<T>.Countfait: il ne peut pas être attribué à, mais cela ne signifie pas qu'il est "en lecture seule" dans la mesure où il ne peut pas être modifié par les utilisateurs de la classe. Il peut très bien être modifié, bien qu'indirectement, en ajoutant et en supprimant des éléments de liste.
OR Mapper

-2

Parce qu'au niveau IL, une propriété en lecture / écriture se traduit en deux méthodes (getter et setter).

Lors du remplacement, vous devez continuer à prendre en charge l'interface sous-jacente. Si vous pouviez ajouter un setter, vous ajouteriez effectivement une nouvelle méthode, qui resterait invisible pour le monde extérieur, en ce qui concerne l'interface de vos classes.

Certes, l'ajout d'une nouvelle méthode ne briserait pas la compatibilité en soi, mais comme elle resterait cachée, la décision de l'interdire est parfaitement logique.


2
Je ne me soucie pas beaucoup du «monde extérieur» ici. Je souhaite ajouter des fonctionnalités à une classe, qui n'est toujours visible que pour le code qui connaît la classe spécifique et non la base.
ripper234

@ ripper234 Voir la réponse de Matt Bolt: si vous voulez rendre la nouvelle propriété visible uniquement aux utilisateurs de la classe dérivée, vous devriez utiliser à la newplace de override.
Dan Berindei

1
Cela ne reste pas caché. Vous êtes comme dire que vous ne pouvez pas ajouter une nouvelle méthode à une classe enfant parce que la nouvelle méthode sera "restée cachée du monde extérieur". Bien sûr, il reste caché du point de vue de la classe de base, mais il est accessible depuis les clients de la classe enfant. (C'est-à-dire pour affecter ce que le getter devrait retourner)
Sheepy

Sheepy, ce n'est pas du tout ce que je dis. Je veux dire, exactement comme vous l'avez souligné, cela resterait caché "du point de vue de la classe de base". C'est pourquoi j'ai ajouté "en ce qui concerne l' interface de vos classes ". Si vous sous-classez une classe de base, il est presque certain que le «monde extérieur» fera référence à vos instances d'objet avec le type de classe de base. S'ils utilisaient directement votre classe concrète, vous n'auriez pas besoin de compatibilité avec la classe de base et vous pourriez, par tous les moyens, utiliser newavec vos ajouts.
Ishmaeel

-2

Parce qu'une classe qui a une propriété en lecture seule (pas de setter) a probablement une bonne raison pour cela. Il se peut qu'il n'y ait pas de banque de données sous-jacente, par exemple. Le fait de vous permettre de créer un setter rompt le contrat établi par la classe. C'est juste une mauvaise POO.


Vote à la hausse. C'est très succinct. Pourquoi Microsoft devrait-il autoriser le consommateur d'une classe de base à rompre le contrat? Je suis d'accord avec cette version très courte de ma réponse trop longue et déroutante. Lol. Si vous êtes un consommateur d'une classe de base, vous ne devriez pas vouloir rompre le contrat, mais vous POUVEZ l'implémenter de deux manières différentes.
Suamere
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.