C # Comment faire getters publics et setters et des méthodes privées pour une collection?

StackOverflow https://stackoverflow.com/questions/1080644

  •  22-08-2019
  •  | 
  •  

Question

Je voudrais avoir une classe « A » avec un (par exemple) collection SortedList propriété « SrtdLst », et à l'intérieur de cette classe « A » permettre l'ajout ou la soustraction des points « SrtdLst ». Mais dans une instance de la classe « A », ne permettent d'obtenir ou de définir le contenu des articles, de ne pas ajouter de nouveaux éléments ou soustraire ceux qui existent déjà. Dans le code:

class A
{
    public SortedList<string, string> SrtdLst = new SortedList<string, string>();

    public A()
    {
        // This must work:
        SrtdLst.Add("KeyA", "ValueA");
        // This too:
        SrtdLst["KeyA"] = "ValueAAA";
    }
}

class B
{
    public A a = new A();
    public B()
    {
        // I want the following code to fail:
        a.SrtdLst.Add("KeyB", "ValueB");
        // But this must work:
        a.SrtdLst["KeyA"] = "ValueBBB";
   }
}

Mise à jour: Je veux créer une classe comme System.Data.SqlClient.SqlCommand. Pour les procédures stockées, vous pouvez utiliser les membres « DeriveParameters » qui remplit une collection de « paramètres », de sorte que la valeur de chaque élément peut être modifié.

Comment cela peut-il être fait?

Était-ce utile?

La solution

Si vous voulez interdire au moment de la compilation des opérations de modification, vous avez besoin d'une solution de type sécurisé.

Déclarer une interface pour les opérations publiques autorisées. Utilisez cette interface comme type de propriété.

public interface IReadOnlyList<T>
{
    T this[int index] { get; }

    int Count { get; }
}

Ensuite, déclarez une classe qui implémente cette interface et hérite de la classe de collection standard.

public class SafeList<T> : List<T>, IReadOnlyList<T> { }

En supposant que vous obtenez la définition d'interface droite, vous aurez pas besoin de mettre en œuvre quoi que ce soit à la main, comme la classe de base fournit déjà les implémentations.

Utilisez cette classe dérivée comme le type du champ qui stocke la valeur de la propriété.

public class A
{
    private SafeList<string> _list = new SafeList<string>();

    public IReadOnlyList<string>
    {
        get { return _list; }
    }
}

Dans la classe A, vous pouvez utiliser directement _list, et ainsi modifier le contenu. Les clients d'une classe ne seront en mesure d'utiliser le sous-ensemble des opérations disponibles via IReadOnlyList<T>.

Pour votre exemple, vous utilisez SortedList au lieu de la liste, de sorte que l'interface doit probablement être

public interface IReadOnlyDictionary<K, V> : IEnumerable<KeyValuePair<K, V>>
{
    V this[K index] { get; }        
}

Je l'ai fait hériter IEnumerable aussi bien, qui est en lecture seule de toute façon, est donc parfaitement sûr. La classe sûre serait alors:

public class SafeSortedList<K, V> : SortedList<K, V>, IReadOnlyDictionary<K, V> { }

Mais sinon il est la même idée.

Mise à jour: il suffit de noter que (pour une raison quelconque, je ne peux pas comprendre) que vous ne voulez pas interdire les opérations de modification - vous voulez juste d'interdire certaines opérations de modification. Très étrange, mais il est toujours la même solution. Quelles que soient les opérations que vous voulez autoriser, « les ouvrir » dans l'interface:

public interface IReadOnlyDictionary<K, V> : IEnumerable<KeyValuePair<K, V>>
{
    V this[K index] { get; set; }        
}

Bien sûr, c'est le mauvais nom pour l'interface maintenant ... pourquoi diable voudriez-vous interdire l'ajout par Ajouter mais pas l'interdire par l'indexeur? (L'indexeur peut être utilisé pour ajouter des éléments, tout comme la boîte de méthode Add.)

Mise à jour

De votre commentaire, je pense que vous voulez dire que vous voulez autoriser l'affectation à la valeur d'une paire de clés existante / valeur, mais désavouer l'affectation à une clé inconnue auparavant. Il est évident que les clés sont spécifiées lors de l'exécution par des chaînes, il n'y a aucun moyen de rattraper qu'au moment de la compilation. Ainsi, vous pouvez aussi bien aller pour le contrôle d'exécution:

public class FixedSizeDictionaryWrapper<TKey, TValue> : IDictionary<TKey, TValue>
{
    IDictionary<TKey, TValue> _realDictionary;

    public FixedSizeDictionaryWrapper(IDictionary<TKey, TValue> realDictionary)
    {
        _realDictionary = realDictionary;
    }

    public TValue this[TKey key]
    {
        get { return _realDictionary[key]; }

        set 
        {
            if (!_realDictionary.Contains(key))
                throw new InvalidOperationException();

            _realDictionary[key] = value;
        }
    }

    // Implement Add so it always throws InvalidOperationException

    // implement all other dictionary methods to forward onto _realDictionary
}

Chaque fois que vous avez un dictionnaire ordinaire et que vous voulez le remettre à une méthode que vous ne faites pas confiance à mettre à jour les valeurs existantes, l'envelopper dans un de ces derniers.

Autres conseils

EDIT: réponse originale est ci-dessous. Comme Earwicker souligne, je ne l'avais pas remarqué que vous ne sont pas demandant qu'il soit en lecture seule - juste pour empêcher l'opération de Add. Cela ne semble pas être une bonne idée pour moi, comme la seule différence entre Add et l'indexeur compositeur est que Add lance une exception si l'élément est déjà présent. Cela pourrait facilement être falsifiée par l'appelant de toute façon.

Pourquoi voulez-vous de limiter cette seule opération?


Réponse originale

Pour une chose, ne pas utiliser les champs publics. C'est un moyen infaillible pour des problèmes.

On dirait que vous voulez une classe d'emballage en lecture seule autour d'un IDictionary arbitraire. Vous pouvez avoir une propriété publique qui retourne l'emballage, alors que vous accédez à la variable privée à partir de votre classe. Par exemple:

class A
{
    private SortedList<string, string> sortedList = new SortedList<string, string>();

    public IDictionary<string, string> SortedList 
    {
        get { return new ReadOnlyDictionaryWrapper(sortedList);
    }

    public A()
    {
        sortedList.Add("KeyA", "ValueA");
        sortedList["KeyA"] = "ValueAAA";
    }
}

Maintenant, vous avez juste à trouver une implémentation ReadOnlyDictionary ... Je ne peux pas le mettre en œuvre en ce moment, mais je reviendrai plus tard si nécessaire ...

Il suffit de faire la liste privée, et l'exposer comme indexeur:

class A {

   private SortedList<string, string> _list;

   public A() {
      _list = new SortedList<string, string>()
   }

   public string this[string key] {
      get {
         return _list[key];
      }
      set {
         _list[key] = value;
      }
   }

}

Maintenant, vous ne pouvez accéder aux éléments en utilisant l'index:

a["KeyA"] = "ValueBBB";

Cependant, comme l'indexeur de la liste permet la création de nouveaux éléments, vous devez ajouter du code dans l'indexeur pour éviter que si vous ne voulez pas faire possible.

Si les touches sont connues en dehors de la classe, vous pouvez ajouter une ChangeItem (clé, newValue) et ReadItem (clé) à votre classe d'emballage. Ensuite, garder le privé SortedList à la classe.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top