48 votes

Comment exposer une propriété de collection ?

Chaque fois que je crée un objet qui a une propriété de collection, je fais des allers-retours sur la meilleure façon de le faire ?

  1. propriété publique avec un getter qui renvoie une référence à la variable privée
  2. les méthodes explicites get_ObjList et set_ObjList qui retournent et créent des objets nouveaux ou clonés à chaque fois que
  3. explicite get_ObjList qui renvoie une IEnumerator et un set_ObjList qui prend un IEnumerator

Cela fait-il une différence si la collection est un tableau (par exemple, objList.Clone()) ou une liste ?

Si renvoyer la collection actuelle comme référence est si mauvais parce que cela crée des dépendances, alors pourquoi renvoyer n'importe quelle propriété comme référence ? Chaque fois que vous exposez un objet enfant en tant que référence, les éléments internes de cet enfant peuvent être modifiés sans que le parent ne le sache, à moins que l'enfant n'ait un événement de modification de propriété. Y a-t-il un risque de fuite de mémoire ?

Et, les options 2 et 3 ne cassent-elles pas la sérialisation ? S'agit-il d'un piège ou devez-vous mettre en œuvre une sérialisation personnalisée chaque fois que vous avez une propriété de collection ?

La collection générique ReadOnlyCollection semble être un bon compromis pour une utilisation générale. Il enveloppe une IList et en restreint l'accès. Peut-être cela aide-t-il les fuites de mémoire et la sérialisation. Cependant, il a toujours problèmes de dénombrement

Peut-être que ça dépend. Si vous ne vous souciez pas que la collection soit modifiée, alors exposez-la simplement comme un accesseur public sur une variable privée par numéro 1. Si vous ne voulez pas que d'autres programmes modifient la collection, alors les options 2 et/ou 3 sont préférables.

La question est implicitement de savoir pourquoi une méthode devrait être utilisée plutôt qu'une autre et quelles sont les ramifications sur la sécurité, la mémoire, la sérialisation, etc.

55voto

Emperor XLII Points 5493

La façon dont vous exposez une collection dépend entièrement de la façon dont les utilisateurs sont censés interagir avec elle.

1) Si les utilisateurs ajoutent et suppriment des éléments de la collection d'un objet, il est préférable d'utiliser une simple propriété de collection de type get-only (option 1 de la question initiale) :

private readonly Collection<T> myCollection_ = new ...;
public Collection<T> MyCollection {
  get { return this.myCollection_; }
}

Cette stratégie est utilisée pour le Items sur les collections WindowsForms et WPF ItemsControl où les utilisateurs ajoutent et suppriment les éléments qu'ils souhaitent voir apparaître dans le contrôle. Ces contrôles publient la collection réelle et utilisent des rappels ou des écouteurs d'événements pour assurer le suivi des éléments.

WPF expose également certaines collections paramétrables pour permettre aux utilisateurs d'afficher une collection d'éléments qu'ils contrôlent, comme l'élément ItemsSource la propriété sur ItemsControl (option n° 3 de la question initiale). Toutefois, ce n'est pas un cas d'utilisation courant.

2) Si les utilisateurs ne doivent lire que les données gérées par l'objet, alors vous pouvez utiliser une collection en lecture seule, comme Quibbleome suggéré :

private readonly List<T> myPrivateCollection_ = new ...;
private ReadOnlyCollection<T> myPrivateCollectionView_;
public ReadOnlyCollection<T> MyCollection {
  get {
    if( this.myPrivateCollectionView_ == null ) { /* lazily initialize view */ }
    return this.myPrivateCollectionView_;
  }
}

Notez que ReadOnlyCollection<T> fournit une vue en direct de la collection sous-jacente, de sorte que vous ne devez créer la vue qu'une seule fois.

Si la collection interne n'implémente pas IList<T> ou si vous souhaitez restreindre l'accès aux utilisateurs plus expérimentés, vous pouvez faire passer l'accès à la collection par un énumérateur :

public IEnumerable<T> MyCollection {
  get {
    foreach( T item in this.myPrivateCollection_ )
      yield return item;
  }
}

Cette approche est simple à mettre en œuvre et permet également d'accéder à tous les membres sans exposer la collection interne. Cependant, elle exige que la collection ne soit pas modifiée, car les classes de collection de la BCL lèveront une exception si vous essayez d'énumérer une collection après qu'elle ait été modifiée. Si la collection sous-jacente est susceptible de changer, vous pouvez soit créer un wrapper léger qui énumérera la collection en toute sécurité, soit retourner une copie de la collection.

3) Enfin, si vous devez exposer des tableaux plutôt que des collections de niveau supérieur, vous devez renvoyer une copie du tableau pour empêcher les utilisateurs de le modifier (option 2 de la question originale) :

private T[] myArray_;
public T[] GetMyArray( ) {
  T[] copy = new T[this.myArray_.Length];
  this.myArray_.CopyTo( copy, 0 );
  return copy;
  // Note: if you are using LINQ, calling the 'ToArray( )' 
  //  extension method will create a copy for you.
}

Vous ne devez pas exposer le tableau sous-jacent par le biais d'une propriété, car vous ne serez pas en mesure de savoir si les utilisateurs le modifient. Pour permettre la modification du tableau, vous pouvez soit ajouter une propriété de type SetMyArray( T[] array ) ou utiliser un indexeur personnalisé :

public T this[int index] {
  get { return this.myArray_[index]; }
  set {
    // TODO: validate new value; raise change event; etc.
    this.myArray_[index] = value;
  }
}

(bien sûr, en implémentant un indexeur personnalisé, vous dupliquerez le travail des classes de la BCL :)

3voto

Quibblesome Points 14441

J'opte généralement pour ceci, un getter public qui renvoie System.Collections.ObjectModel.ReadOnlyCollection :

public ReadOnlyCollection<SomeClass> Collection
{
    get
    {
         return new ReadOnlyCollection<SomeClass>(myList);
    }
}

Et des méthodes publiques sur l'objet pour modifier la collection.

Clear();
Add(SomeClass class);

Si la classe est censée être un référentiel avec lequel d'autres personnes peuvent s'amuser, je me contente d'exposer la variable privée selon la méthode n° 1, ce qui évite d'avoir à écrire sa propre API, mais j'ai tendance à éviter cette méthode dans le code de production.

0voto

Ryan Duffield Points 7602

Si vous cherchez simplement à exposer une collection sur votre instance, alors l'utilisation d'un getter/setter vers une variable membre privée me semble être la solution la plus raisonnable (votre première option proposée).

0voto

Telcontar Points 2329

Je suis un développeur java mais je pense que c'est la même chose pour le c#.

Je n'expose jamais une propriété de collection privée parce que d'autres parties du programme peuvent la modifier sans que le parent s'en aperçoive, de sorte que dans la méthode getter, je renvoie un tableau contenant les objets de la collection et que dans la méthode setter, j'appelle une méthode clearAll() sur la collection et ensuite un addAll()

0voto

jpierson Points 3871

Pourquoi pensez-vous que l'utilisation de ReadOnlyCollection(T) est un compromis ? Si vous avez toujours besoin d'obtenir des notifications de modification sur l'IList enveloppée d'origine, vous pouvez également utiliser une méthode de type Collection ReadOnlyObservable(T) pour emballer votre collection. Cela serait-il un moindre compromis dans votre cas ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X