Question

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

  1. propriété publique avec un getteur qui renvoie une référence à la variable privée
  2. méthodes explicites get_objlist et set_objlist qui renvoient et créent des objets nouveaux ou clonés à chaque fois
  3. Explicit get_objlist qui renvoie un ienumerator et un set_objlist qui prend ienumerator

Cela fait-il une différence si la collection est un tableau (c'est-à-dire objList.Clone()) ou une liste ?

Si renvoyer la collection réelle comme référence est si mauvais car cela crée des dépendances, alors pourquoi renvoyer une 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 "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 rompent-elles pas la sérialisation ?Est-ce un piège ou devez-vous implémenter une sérialisation personnalisée chaque fois que vous avez une propriété de collection ?

Le générique ReadOnlyCollection semble être un bon compromis pour un usage général.Il enveloppe une IList et en restreint l'accès.Peut-être que cela aide en cas de fuites de mémoire et de sérialisation.Cependant, il a encore problèmes de dénombrement

Peut-être que ça dépend.Si vous ne vous souciez pas que la collection soit modifiée, exposez-la simplement en tant qu'accesseur public sur une variable privée selon #1.Si vous ne voulez pas que d'autres programmes modifient la collection, alors les numéros 2 et/ou 3 sont meilleurs.

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

Était-ce utile?

La solution

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

1) Si les utilisateurs souhaitent ajouter et supprimer des éléments de la collection d'un objet, une simple propriété de collection en lecture seule est la meilleure (option n°1 de la question d'origine) :

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

Cette stratégie est utilisée pour Items collections sur WindowsForms et WPF ItemsControl contrôles, où les utilisateurs ajoutent et suppriment les éléments qu’ils souhaitent que le contrôle affiche.Ces contrôles publient la collection réelle et utilisent des rappels ou des écouteurs d'événements pour suivre les éléments.

WPF expose également certaines collections paramétrables pour permettre aux utilisateurs d'afficher une collection d'éléments qu'ils contrôlent, tels que le ItemsSource propriété sur ItemsControl (option n°3 de la question initiale).Cependant, il ne s’agit pas d’un cas d’utilisation courant.


2) Si les utilisateurs liront uniquement les données conservées par l'objet, vous pouvez alors utiliser une collection en lecture seule, comme Chicaneux 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_;
  }
}

Noter que ReadOnlyCollection<T> fournit une vue en direct de la collection sous-jacente, vous n'avez donc besoin de 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 à des utilisateurs plus avancés, vous pouvez à la place encapsuler l'accès à la collection via 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 donne également accès à tous les membres sans exposer la collection interne.Cependant, cela nécessite que la collection reste inchangée, car les classes de collection 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 renvoyer une copie de la collection.


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

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 via une propriété, car vous ne pourrez pas savoir quand les utilisateurs le modifient.Pour permettre la modification du tableau, vous pouvez soit ajouter un correspondant SetMyArray( T[] array ) méthode, ou utilisez 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 BCL :)

Autres conseils

Je choisis généralement 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 jouer, j'expose simplement la variable privée selon la méthode n°1 car elle évite d'écrire votre propre API, mais j'ai tendance à éviter cela dans le code de production.

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

Pourquoi suggérez-vous que l'utilisation de ReadOnlyCollection(T) soit un compromis ?Si vous avez toujours besoin de recevoir des notifications de modification sur la liste IList originale, vous pouvez également utiliser un ReadOnlyObservableCollection(T) pour emballer votre collection.Est-ce que cela constituerait moins un compromis dans votre scénario ?

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

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

ReadOnlyCollection présente toujours l'inconvénient que le consommateur ne peut pas être sûr que la collection originale ne sera pas modifiée à un moment inopportun.Au lieu de cela, vous pouvez utiliser Collections immuables.Si vous devez effectuer une modification, en changeant l'original, vous recevez une copie modifiée.La façon dont il est implémenté est compétitive par rapport aux performances des collections mutables.Ou encore mieux si vous n'avez pas besoin de copier l'original plusieurs fois pour apporter ensuite un certain nombre de modifications différentes (incompatibles) à chaque copie.

Je recommande d'utiliser le nouveau IReadOnlyList<T> et IReadOnlyCollection<T> Interfaces pour exposer une collection (nécessite .NET 4.5).

Exemple:

public class AddressBook
{
    private readonly List<Contact> contacts;

    public AddressBook()
    {
        this.contacts = new List<Contact>();
    }

    public IReadOnlyList<Contact> Contacts { get { return contacts; } }

    public void AddContact(Contact contact)
    {
        contacts.Add(contact);
    }

    public void RemoveContact(Contact contact)
    {
        contacts.Remove(contact);
    }
}

Si vous devez garantir que la collection ne peut pas être manipulée de l'extérieur, envisagez ReadOnlyCollection<T> ou les nouvelles collections Immutable.

Éviter en utilisant l'interface IEnumerable<T> pour exposer une collection.Cette interface ne définit aucune garantie quant au bon fonctionnement des énumérations multiples.Si IEnumerable représente une requête, chaque énumération exécute à nouveau la requête.Les développeurs qui obtiennent une instance de IEnumerable ne savent pas si elle représente une collection ou une requête.

Plus d'informations sur ce sujet peuvent être lues ici Page wiki.

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