2010-04-05 3 views
2

J'ai une classe qui a deux collections HashSet<String> en tant que membres privés. Les autres classes de mon code aimeraient pouvoir parcourir ces HashSets et lire leur contenu. Je ne veux pas écrire un getter standard car une autre classe pourrait encore faire quelque chose comme myClass.getHashSet().Clear(); Existe-t-il un autre moyen d'exposer les éléments de mes HashSets à l'itération sans exposer la référence au HashSet lui-même? J'aimerais pouvoir le faire d'une manière compatible avec chaque boucle.(C#) itérer sur un membre de collection privée en lecture seule

+0

Merci, tout le monde, esp. Strager pour la réponse la plus complète, et Jon Skeet pour nettoyer mon OP. – DGH

Répondre

3

Expose une IEnumerable<T> propriété:

public IEnumerable<whatevertype> MyHashSet { 
    get { 
     return this.myHashSet; 
    } 
} 

Bien sûr, l'utilisateur de ce code peut lancer cette IEnumerable<T> à un HashSet<T> et éléments modifier, afin d'être sur le côté sûr (tout mal performance), vous pouvez faire:

public IEnumerable<whatevertype> MyHashSet { 
    get { 
     return this.myHashSet.ToArray(); 
    } 
} 

ou:

public IEnumerable<whatevertype> MyHashSet { 
    get { 
     foreach(var item in this.myHashSet) { 
      yield return item; 
     } 
    } 
} 

Une méthode plus performante de protection, mais moins pratique à l'appelant, est de retourner un IEnumerator<T>:

public IEnumerator<whatevertype> GetMyHashSetEnumerator() { 
    return this.myHashSet.GetEnumerator(); 
} 
+0

Pourquoi même présenter l'option 'ToArray'? Le bloc d'itérateur est plus performant et ne consomme pas de mémoire supplémentaire (autre que pour l'installation de la classe stub générée par le compilateur.) –

+0

Je pense que l'approche yield est la plus propre, car elle est paresseuse et ne devrait pas utiliser plus de mémoire. Je ne l'ai pas mesuré cependant. –

+0

Question intéressante: est ce que 'yield return' fonctionne plus vite que' ToArray() '? –

3

Ajouter une méthode/propriété comme celui-ci pour éviter d'exposer le récipient réel:

public IEnumerable EnumerateFirst() 
{ 
    foreach(var item in hashSet) 
     yield return item; 
} 
+1

Mieux, car cela arrête/les appelants vraiment odieux renvoyant IEnumerable à un HashSet. Cependant, vous ne tapez pas fortement IEnumerable, ce qui n'est peut-être pas une bonne chose. –

+1

+1. Alors que d'autres préconisent d'exposer le membre uniquement comme un «IEnumerable » (et cela, au sens strict, accomplir ce que vous voulez), ce n'est pas une pratique sûre. Alors que l'objet lui-même n'est exposé qu'en tant qu'interface, rien n'empêche un développeur entreprenant de renvoyer votre objet à 'HashSet'. La pratique ici est plus sûre, puisque vous n'exposez jamais la référence 'HashSet' réelle. –

+0

... mais c'est plus lent, malheureusement. – strager

-1

Marque votre getter expose le HashSet comme IEnumerable.

private HashSet<string> _mine; 

public IEnumerable<string> Yours 
{ 
    get { return _mine; } 
} 

Si le type générique est mutable, alors que peut encore être modifié, mais aucun élément peuvent être ajoutés ou retirés de votre HashSet.

+1

Oui, ils peuvent - l'appelant doit juste revenir à 'HashSet '. –

+0

Ok, mais il n'y a aucune garantie de sécurité à aucun égard. Même si _mine est un membre d'instance privée, vous pouvez l'utiliser en utilisant la réflexion. Si le consommateur choisit de violer le contrat fourni par l'API, alors il doit accepter la conséquence de le faire. En l'exposant comme 'IEnumerable ', vous indiquez clairement que le consommateur ne devrait que répéter, et ne pas modifier votre collection. Est-ce pas assez bon? –

+0

Par convention commune, pas habituellement. Il y a une grande différence entre l'accès aux membres privés par diffusion et par réflexion. –

0

Vous pouvez également fournir une séquence comme ceci:

public IEnumerable<string> GetHashSetOneValues() 
{ 
    foreach (string value in hashSetOne) 
     yield return value; 
} 

Cette méthode peut alors être appelée dans une boucle foreach:

foreach (string value in myObject.GetHashSetOneValues()) 
    DoSomething(value); 
3

Vous pouvez également utiliser la méthode Select pour créer une enveloppe de ne peut pas être rejetterait à HashSet<T>:

public IEnumerable<int> Values 
{ 
    get { return _values.Select(value => value); 
} 

Th évite d'itéter plus de deux fois _values, comme vous le feriez avec .ToArray(), mais conserve l'implémentation sur une seule ligne propre.

+1

Les grands esprits se ressemblent :) –

+0

Hmm, intéressant ... – strager

6

En supposant que vous utilisez .NET 3.5, une alternative à l'écriture du code de rendement vous-même est d'appeler une méthode LINQ. Par exemple:

public IEnumerable<string> HashSet 
{ 
    get { return privateMember.Select(x => x); } 
} 

ou

public IEnumerable<string> HashSet 
{ 
    get { return privateMember.Skip(0); } 
} 

Il existe différents opérateurs de LINQ qui pourraient être utilisés comme celui-ci - en utilisant Skip(0) est probablement le plus efficace, comme après la première « sauter 0 valeurs » boucle, il est probablement juste la boucle foreach/yield return montrée dans les autres réponses. La version Select appellera le délégué de projection no-op pour chaque élément généré.Les chances que cette différence soit significative sont astronomiquement faibles, cependant - je suggère que vous alliez avec ce qui rend le code le plus clair pour vous.

+0

+1. J'irais avec 'Skip' pour éviter les frais inutiles de lambda. Bien que ce ne soit pas très important, cela ne vous apporte vraiment rien. –

+0

+1; 'Skip (0)' est une excellente idée! – strager

0

Cela peut être un peu trop tard pour la fête mais la façon la plus simple aujourd'hui serait d'utiliser Linq. Au lieu d'écrire

public IEnumerable<string> GetValues() 
{ 
    foreach(var elem in list) 
     yield return elem; 
} 

vous pouvez écrire

public IEnumerable<string> GetValues() => list; 
Questions connexes