web-dev-qa-db-fra.com

Pourquoi est-il impossible de remplacer une propriété en lecture seule et d'ajouter un séparateur?

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' ne dispose pas d'un accesseur défini pouvant être remplacé

130
ripper234

Parce que l'auteur de Baseclass a explicitement déclaré que Bar doit être une propriété en lecture seule. Cela n'a pas de 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 il a été demandé de coder contre la dérivation Baseclass. J'écris quelque chose qui suppose que Bar ne peut pas être écrit (puisque la classe de base indique explicitement qu'il s'agit d'une propriété à obtenir uniquement) ... .. Maintenant, avec votre dérivation, mon code peut tomber en panne. 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 conformément à l'interface BaseClass, BarProvider suppose que la mise en cache est une opération sûre, car 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?" Hum… ça devient de plus en plus boueux quand j'y pense.
Une interface est un contrat qui dit: "attendez-vous à une implémentation d'avoir une propriété de lecture nommée Bar". Personnellement Je suis beaucoup moins susceptible de faire cette hypothèse de lecture seule si je voyais une interface. Lorsque je vois une propriété get-only sur une interface, je la lis comme suit: "Toute implémentation exposerait cet attribut Bar" ... sur une classe de base, il clique sur le bouton "Bar est une propriété en lecture seule". Bien sûr, techniquement, vous ne rompez pas le contrat… vous en faites plus. Donc, vous avez raison en un sens… Je terminerais en disant: «évitez autant que possible les malentendus».

1
Gishu

Je pense que la raison principale est simplement que la syntaxe est trop explicite pour que cela fonctionne autrement. Ce code:

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

est tout à fait explicite que get et set sont des substitutions. Il n'y a pas set dans 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 appliquer uniquement le remplacement à la méthode qui peut être remplacée (c'est-à-dire le getter dans ce cas), mais cela va à l'encontre d'un des principes de conception de C #, à savoir que le compilateur ne doit pas deviner vos intentions. , parce qu’il peut deviner mal à votre insu.

Je pense que la syntaxe suivante pourrait bien fonctionner, mais comme le répète Eric Lippert, la mise en œuvre même d'une fonctionnalité mineure comme celle-ci demande encore beaucoup d'efforts ...

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

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

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

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 signifie pas nécessairement en lecture seule. Je l’interprète comme "De cette interface/abtract, vous pouvez obtenir cette valeur", cela ne signifie pas qu’une implémentation de cette classe/interface abstraite n’aura pas besoin de l’utilisateur/du programme pour définir explicitement cette valeur. Les classes abstraites servent à implémenter une partie de la fonctionnalité requise. Je ne vois absolument aucune raison pour laquelle une classe héritée ne pourrait pas ajouter un séparateur 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 le problème. La raison pour laquelle ajouter le setter et ne pas ajouter, par exemple, une méthode SetProp est qu'une implémentation particulière de l'interface utilisait DataContract/DataMember pour la sérialisation de Prop, ce qui aurait été compliqué inutilement si je devais ajouter une autre propriété uniquement dans le but 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 c’est juste un message "mes 2 centimes", mais j’ai le sentiment avec l’affiche originale et essayer de justifier que c’est une bonne chose me semble étrange, surtout si l’on considère que les mêmes limitations ne s’appliquent pas lorsqu’on hérite directement interface.

De plus, la mention sur l'utilisation de new au lieu de override ne s'applique pas ici, cela ne fonctionne tout simplement pas et même si cela se produisait, le résultat souhaité ne serait pas obtenu, à savoir un getter virtuel tel que décrit par l'interface.

19
JJJ

C'est possible

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

  1. Créez une propriété new qui a à la fois une get et une set utilisant le même nom.

  2. Si vous ne faites rien d'autre, l'ancienne méthode get 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 couche intermédiaire abstract qui utilise override sur l'ancienne méthode get pour le forcer à renvoyer le résultat de la nouvelle méthode get.

Cela nous permet de remplacer les propriétés avec getset mê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- uniquement, vous pouvez utiliser un type de retour plus dérivé.

  • Si la définition de base était set- uniquement, vous pouvez nous 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é _/ifvous le rendez set- uniquement;

    • vous pouvez utiliser un type de retour moins dérivésivous le rendez get- uniquement.

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 des raisons de simplicité.

Situation: propriété get- préexistante

Vous avez une structure de classe que vous ne pouvez pas modifier. Peut-être que c'est juste une classe, ou c'est un arbre d'héritage préexistant. Quoi qu'il en soit, vous voulez ajouter une méthode set à 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: Impossible override la get- uniquement avec getset

Vous voulez override avec une propriété get/set, mais cela 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: utiliser une couche intermédiaire abstract

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

  1. Créez une propriété newget/set avec le même nom.

  2. override l'ancienne méthode get avec un accesseur à la nouvelle méthode get pour assurer la cohérence.

Donc, vous écrivez d’abord la couche intermédiaire 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; }
}

Ensuite, vous écrivez la classe qui ne compilerait pas plus tôt. Il sera compilé cette fois parce que vous n'êtes pas réellement override 'ing la propriété get- only; au lieu de cela, vous le remplacez à l'aide du mot clé 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; } }
}

Résultat: tout fonctionne!

Évidemment, cela fonctionne comme prévu pour 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 lorsque vous affichez D comme l'une de ses classes de base, par exemple. A ou B. Mais, la raison pour laquelle cela fonctionne peut-être un peu moins évident.

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 get est toujours finalement remplacée par la définition de la classe dérivée de get; elle est donc toujours parfaitement 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 méthodes set à get- only properties. Vous pouvez également l'utiliser pour faire des choses comme:

  1. Remplacez une propriété par une propriété get- only, set- only ou get- et -set, quelle que soit sa position dans une classe de base.

  2. Changez 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 abstract class supplémentaire dans l'arbre d'héritage. Cela peut être un peu gênant pour les constructeurs qui prennent des paramètres car ceux-ci doivent être copiés/collés dans la couche intermédiaire.

12
Nat

Je conviens que le fait de ne pas pouvoir ignorer un getter dans un type dérivé est un anti-motif. Read-Only spécifie l'absence d'implémentation, pas un contrat purement fonctionnel (sous-entendu par la réponse du vote en haut).

Je soupçonne que Microsoft avait cette limitation soit parce que la même idée fausse a été promue, soit peut-être pour simplifier la grammaire; Cependant, maintenant que cette possibilité peut être appliquée pour obtenir ou définir individuellement, nous pouvons peut-être espérer que la substitution le soit aussi.

L'idée fausse indiquée par la réponse du vote en haut, qu'une propriété en lecture seule devrait en quelque sorte être plus "pure" qu'une propriété en lecture/écriture est ridicule. Il suffit de regarder de nombreuses propriétés communes en lecture seule dans la structure; la valeur n'est pas une constante/purement fonctionnelle; Par exemple, DateTime.Now est en lecture seule, mais n’a rien d’une valeur fonctionnelle pure. Tenter de «mettre en cache» une valeur d'une propriété en lecture seule en supposant que la même valeur sera renvoyée la prochaine fois est risqué.

Dans tous les cas, j'ai utilisé l'une des stratégies suivantes pour surmonter cette limitation; les deux sont moins que parfaits, mais vous permettront de boiter au-delà de cette déficience 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;
   }
8
T.Tobler

Le problème est que, quelle que soit la raison, Microsoft a décidé de créer 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 - une en lecture seule et une en lecture-écriture.

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

1
supercat

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

Vous ne pouvez rien faire de pareil:

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

OMI, C # ne devrait pas restreindre de tels scénarios. Il incombe au développeur de l'utiliser en conséquence.

1
Thomas Danecker

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

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

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

Vous devez modifier le titre de votre question pour indiquer soit que votre question concerne uniquement la substitution d'une propriété abstraite, soit que votre question concerne la substitution générale de la propriété get-only d'une classe.


Si l'ancien (remplacement d'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 pouvant 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é communes 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 les fonctionnalités de la classe de base sont lues à partir de sa propre propriété publique (bizarre), procédez comme suit:

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 bonne pratique consiste pour une classe à ne pas utiliser ses propres propriétés publiques mais à utiliser ses champs privés/protégés lorsqu'ils existent. Donc, ceci est 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 (écrasant la propriété get-only d'une classe)

Chaque propriété non abstraite a un séparateur. Sinon, c'est inutile et vous ne devriez pas vous en soucier. Microsoft n'a pas à vous permettre de faire ce que vous voulez. Raison d'être: le poseur existe sous une forme ou une autre, et vous pouvez accomplir ce que vous voulez Veerryy facilement.

La classe de base, ou toute classe dans laquelle vous pouvez lire une propriété avec {get;}, aUN CERTAINune sorte de séparateur exposé pour cette propriété. 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 complexité:

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 poseur exposé. Dans votre cas, vous voudrez peut-être écraser int Bar parce que vous ne voulez pas que la classe de base le gère, que vous n’ayez pas accès à la façon dont elle le gère, ou qu’on vous a demandé de manipuler du code très rapide avec votre code. volonté.

En dernier et dernier (Conclusion)

Long-Story Short: Il n'est pas nécessaire que Microsoft change quoi que ce soit. Vous pouvez choisir le mode de configuration de votre classe d'implémentation et, sans le constructeur, utiliser tout ou rien de la classe de base.

0
Suamere

Voici un moyen de contourner ce problème 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 de l'objet sur une propriété en lecture seule. Pourrait ne pas fonctionner dans tous les scénarios cependant!

0
user2514880