2009-08-02 5 views
3

J'ai des méthodes qui retournent des collections privées à l'appelant et je veux empêcher l'appelant de modifier les collections retournées.Comment empêcher un appelant de méthode de modifier une collection retournée?

private readonly Foo[] foos; 

public IEnumerable<Foo> GetFoos() 
{ 
    return this.foos; 
} 

Au moment de la collection privée est un réseau fixe, mais à l'avenir la collection pourrait devenir une liste si la nécessité d'ajouter de nouveaux éléments au moment de l'exécution se fait sentir.

Il existe plusieurs solutions pour empêcher l'appelant de modifier la collection. Renvoyer IEnumerable<T> est la solution la plus simple, mais l'appelant peut toujours convertir la valeur renvoyée en IList<T> et modifier la collection. Le clonage des collections présente l'inconvénient évident qu'il existe deux collections qui peuvent évoluer indépendamment. Jusqu'à présent, j'ai considéré les options suivantes.

  1. Enveloppant la collection dans ReadOnlyCollection<T>.
  2. Renvoyer l'un des itérateurs LINQ définis par la classe Enumerable en effectuant une projection fictive comme list.Select(item => item). En fait je considère utiliser Where(item => true) parce que l'itérateur retourné semble plus léger.
  3. Ecriture d'un wrapper personnalisé.

Ce que je n'aime pas sur l'utilisation ReadOnlyCollection<T> est qu'il met en œuvre IList<T> et appelant Add() ou l'accès à l'indexeur entraînera des exceptions. Bien que cela soit absolument correct en théorie, presque aucun vrai code ne vérifie IList<T>.IsReadOnly ou IList<T>.IsFixedSize. L'utilisation des itérateurs LINQ - J'ai enveloppé le code dans une méthode d'extension MakeReadOnly() - empêche ce scénario, mais il a le goût d'un hack.

Écrire un emballage personnalisé? Réinventer la roue?

Des idées, des considérations ou d'autres solutions?


Bien que le marquage à cette question, j'ai découvert this Stack Overflow question je n'ai pas remarqué avant. Jon Skeet suggère d'utiliser le "LINQ hack", mais encore plus efficace en utilisant Skip(0).

+1

À moins qu'il y ait des problèmes de sécurité (appelants potentiellement hostiles), je ne m'inquiète généralement pas de la protection contre le lancement imprudent ou l'appel Add sur un ReadOnlyCollection. Les développeurs stupides peuvent également utiliser des éléments internes de réflexion et d'accès. Il est difficile de rendre le code idiot. L'objectif est de protéger contre l'utilisation innocente de l'interface publique. – TrueWill

Répondre

5

Malheureusement, il n'y a aucun moyen d'atteindre exactement ce que vous cherchez dans la version actuelle de la structure. Il n'a tout simplement pas de concept d'une collection indexable immutable/en lecture seule sur le type concret et le style d'interface.

Comme vous l'avez souligné, ReadOnlyCollection<T> fonctionne bien sur le côté de béton. Mais il n'y a pas d'interface correspondante à mettre en œuvre qui soit également statiquement en lecture seule.

Vous n'êtes plus seul choix est de ...

  • Définir votre propre classe de collection
  • Soit que la mise en œuvre IEnumerable<T> ou définir un besoin interface de lecture seule qui vos outils de collecte.Comment faire une copie profonde de l'objet renvoyé?
+0

J'ai accepté cette réponse parce qu'elle clarifie qu'il n'y a pas de support intégré. A décidé de rester avec Enumerable.Skip (0). –

+0

@ DanielBrückner: Quel avantage cela a-t-il de surenvelopper dans une ReadOnlyCollection et de lancer la conversion en IEnumerable ? Le fait que l'objet résultant puisse être converti en 'IList ' améliorera énormément les performances de certaines méthodes Linq comme 'Count' et' Last'. – supercat

0

[Il n'y aura donc pas d'effet sur la collection d'origine, même si l'appelant décide de modifier la copie revins]

+0

Une copie de la collection n'est pas souhaitable, car l'appelant obtiendrait simplement un instantané et ne remarquerait pas les modifications apportées à la collection. –

0

List et Array ont une méthode AsReadOnly:

public IEnumerable<Foo> GetFoos() 
{ 
    return Array.AsReadOnly(this.foos); 
    // or if it is a List<T> 
    // return this.foos.AsReadOnly(); 
} 
+0

Cette méthode retourne la collection enveloppée dans une ReadOnlyCollection et je n'aime pas que cette classe implémente IList comme décrit dans la question. –

0

Dans ce cas, créer un méthodes Insérez la classe pour accéder aux éléments individuels de la collection privée. Vous pouvez également réaliser l'indexeur (http://msdn.microsoft.com/en-us/library/6x16t2tx.aspx) sur la classe qui contient la collection privée.

+0

L'implémentation d'une méthode pour obtenir un seul élément a au moins deux inconvénients - une seconde méthode ou une propriété pour renvoyer le nombre d'éléments dans la collection est requise et tous les avantages de IEnumerable - foreach et LINQ - sont perdus. La création d'un indexeur nécessite une méthode ou une propriété renvoyant le nombre d'éléments, et certaines classes ont plusieurs méthodes qui retournent des collections. Cela opte pour les indexeurs. –

+0

Oui, bien sûr, cela dépend de vos tâches. Mais vous pouvez ajouter un champ qui renvoie le nombre d'éléments dans la collection privée. Vous pouvez également créer une classe interne qui réalisera la logique d'accès. – RollingStone

Questions connexes