2009-07-03 6 views
3

Je voudrais avoir une classe "A" avec une propriété (par exemple) SortedList collection "SrtdLst", et à l'intérieur de cette classe "A" permettre l'addition ou la soustraction des éléments "SrtdLst". Mais dans une instance de la classe "A", permettez seulement d'obtenir ou de définir le contenu des éléments, pas d'ajouter de nouveaux éléments ou de soustraire les éléments existants. Dans le code:C# Comment rendre les getters et setters publics et les méthodes privées pour une collection?

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 le membre "DeriveParameters" qui remplit une collection de "Paramètres", afin que seule la valeur de chaque élément puisse être modifiée.

Comment cela peut-il être fait?

+5

S'il vous plaît dites-moi que vous n'utilisez pas la notation sans-voyelles-permis sur une base quotidienne! Je suppose que c'était juste pour cet exemple. Je fais avec la notation au travail toute la journée que j'appelle "une voyelle permise, mais ce n'est pas toujours la première!"; c'est horrible à lire. – jcollum

+1

Ouais, d'accord, mais c'est un vendredi après-midi ... – Alex

Répondre

7

Si vous voulez interdire les opérations de modification au moment de la compilation, vous avez besoin d'une solution de type sécurité. Déclarer une interface pour les opérations autorisées publiquement.

Utilisez cette interface comme type de propriété. Puis déclarez une classe qui implémente cette interface et hérite de la classe de collection standard.

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

    int Count { get; } 
} 

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 mises en œuvre.

Utilisez cette classe dérivée comme 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 modifier de manière le contenu. Les clients de classe A ne pourront utiliser que 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 parfaitement sûr . La classe de sécurité serait alors:

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

Mais sinon c'est la même idée. Mise à jour: juste remarqué que (pour une raison que je ne comprends pas) vous ne voulez pas interdire les opérations de modification - vous voulez juste interdire QUELQUES opérations de modification. Très étrange, mais c'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 via 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 clé existante/paire de valeurs, mais n'autorise pas l'affectation à une clé précédemment inconnue. Évidemment, comme les clés sont spécifiées à l'exécution par les chaînes, il n'y a aucun moyen d'attraper cela 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 remettre à une méthode que vous ne faites pas confiance à mettre à jour les valeurs existantes, l'envelopper dans un des celles-ci.

+0

Je veux interdire tout, mais j'ai pensé à interdire l'indexeur pour ajouter des éléments via setters, mais je ne savais pas comment interdire l'ajout par la méthode "Ajouter". Comme je l'ai dit dans mon commentaire précédent à Skeet, ce que je veux créer est une classe comme System.Data.SqlClient.SqlCommand pour les procédures stockées, avec un membre comme "DeriveParameters" qui remplit une collection de "Paramètres", donc seulement le La valeur de chaque élément peut être modifiée. – Alex

+0

Voir la mise à jour supplémentaire. –

+0

FYI .NET 4.5 ont exactement ces interfaces IReadOnlyList et IReadOnlyDictionary

6

EDIT: La réponse originale est ci-dessous. Comme earwicker souligne, je n'avais pas remarqué que vous ne sont pas demandant qu'il soit en lecture seule - juste pour empêcher l'opération Add. Cela ne me semble pas une bonne idée, car la seule différence entre Add et l'indexeur-setter est que Add lève une exception si l'élément est déjà présent. Cela pourrait facilement être truqué par l'appelant de toute façon. Pourquoi voulez-vous restreindre juste une opération en particulier?


réponse originale

D'une part, ne pas utiliser les champs publics. C'est un moyen infaillible de rencontrer des problèmes. Il semble que vous vouliez une classe wrapper en lecture seule autour d'un IDictionary arbitraire. Vous pouvez alors avoir une propriété publique qui renvoie l'encapsuleur, tandis que vous accédez à la variable privée depuis 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 mise en œuvre ReadOnlyDictionary ... Je ne peux pas le mettre en œuvre en ce moment, mais je reviendrai plus tard si nécessaire ...

+0

Merde vous et votre omniprésence skeet: P – annakata

+0

Je sais! Obtenez un emploi Skeet! : P – jcollum

+0

Je n'aime pas détecter les erreurs à l'exécution, donc ma réponse utilise le système de type statique à la place. Mais l'autre problème (je ne l'ai pas remarqué au début non plus!) Est que l'OP ne demande pas qu'il soit en lecture seule. Ils veulent simplement interdire la méthode Add, tout en permettant l'affectation via indexeur (qui peut également ajouter des éléments). Très étrange ... –

0

Si les clés sont connues en dehors de la classe, vous pouvez ajouter un ChangeItem (key, newValue) et ReadItem (key) à votre classe wrapper. Puis gardez le SortedList privé à la classe.

2

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 devrez ajouter du code dans l'indexeur pour éviter que cela ne soit possible si vous ne le souhaitez pas.