2016-04-28 3 views
4

J'ai un HashSet<string> privé qui est le champ de support d'une propriété en lecture seule qui devrait renvoyer une collection en lecture seule telle que les appelants ne peuvent pas modifier la collection. J'ai donc essayé:Comment créer une ReadOnlyCollection à partir d'un HashSet sans copier les éléments?

public class MyClass 
{ 
    private readonly HashSet<string> _referencedColumns; 

    public ICollection<string> ReferencedColumns { 
     get { return new ReadOnlyCollection<string>(_referencedColumns); } 
    } 

Cela ne compile pas comme ReadOnlyCollection accepte un IList<T> qui n'implememted par HashSet<T>. Y at-il un autre emballage que je peux utiliser pour me sauver de copier les éléments? Pour mon but il suffit de juste retourner quelque chose mettant en œuvre ICollection<T> (au lieu de IList<T>) qui est mis en œuvre par le HashSet<T>.

+0

S'il est important appelants tha Thet ne peut pas modifier le retour, jetez un oeil à [Immutability et ReadOnlyCollection ] (https://blogs.msdn.microsoft.com/jaredpar/2008/04/22/immutability-and-readonlycollectiont/) et peut-être [cette question (http : //stackoverflow.com/questions/285323/best-practice-how-to-expose-a-read-only-icollection) – stuartd

Répondre

7

Envisagez d'exposer la propriété au type IReadOnlyCollection<> à la place, ce qui fournira une vue en lecture seule du HashSet<>. C'est une manière efficace d'implémenter ceci, puisque la propriété getter ne nécessitera pas de copie de la collection sous-jacente.

Cela n'empêche pas que quelqu'un transtype la propriété en HashSet<> et la modifie. Si cela vous intéresse, considérez return _referencedColumns.ToList() dans la propriété getter, qui créera une copie de votre ensemble sous-jacent.

+0

Merci. Donc, n'y a-t-il pas de wrapper qui sauve la surcharge de copie et qui ne peut pas être renvoyé? – Dejan

+6

Juste pour les enregistrements: la conversion en 'IReadOnlyCollection <>' fonctionne uniquement avec .NET 4.6 et supérieur (voir: http://stackoverflow.com/a/32762752/331281). – Dejan

+0

Bon appel. Cela utilisera toujours le comportement indexé de 'HashSet', bien que cela ne soit plus évident. Les appels à IReadOnlyCollection.Contains() reprennent fortement une implémentation en temps linéaire, même si ce n'est pas forcément le cas. – Timo

4

Vous pouvez utiliser le décorateur suivant pour envelopper l'ensemble de hachage et retourner un ICollection<T> qui est en lecture seule (la IsReadOnly propriété renvoie true et les méthodes de modification jeter un NotSupportedException tel que spécifié dans le contrat de ICollection<T>):

public class MyReadOnlyCollection<T> : ICollection<T> 
{ 
    private readonly ICollection<T> decoratedCollection; 

    public MyReadOnlyCollection(ICollection<T> decorated_collection) 
    { 
     decoratedCollection = decorated_collection; 
    } 

    public IEnumerator<T> GetEnumerator() 
    { 
     return decoratedCollection.GetEnumerator(); 
    } 

    IEnumerator IEnumerable.GetEnumerator() 
    { 
     return ((IEnumerable) decoratedCollection).GetEnumerator(); 
    } 

    public void Add(T item) 
    { 
     throw new NotSupportedException(); 
    } 

    public void Clear() 
    { 
     throw new NotSupportedException(); 
    } 

    public bool Contains(T item) 
    { 
     return decoratedCollection.Contains(item); 
    } 

    public void CopyTo(T[] array, int arrayIndex) 
    { 
     decoratedCollection.CopyTo(array, arrayIndex); 
    } 

    public bool Remove(T item) 
    { 
     throw new NotSupportedException(); 
    } 

    public int Count 
    { 
     get { return decoratedCollection.Count; } 
    } 

    public bool IsReadOnly 
    { 
     get { return true; } 
    } 
} 

Et vous pouvez l'utiliser comme ceci:

public class MyClass 
{ 
    private readonly HashSet<string> _referencedColumns; 

    public ICollection<string> ReferencedColumns { 
     get { return new MyReadOnlyCollection<string>(_referencedColumns); } 
    } 
    //... 

S'il vous plaît noter que cette solution ne sera pas prendre un instantané de la HashSet, au lieu qu'il tiendra une référence au HashSet. Cela signifie que la collection retournée contiendra une version en direct de HashSet, c'est-à-dire, si le HashSet est modifié, le consommateur qui a obtenu la collection en lecture seule avant que la modification puisse voir la modification.

+0

Merci! Bien sûr, je peux écrire le mien. Je me demandais si la BCL avait quelque chose dans sa poche. BTW, serait-il préférable que votre 'MyReadOnlyCollection' implémente' IReadOnlyCollection <> '? – Dejan

+0

De rien. Cela dépend du consommateur. Préfère-t-il 'IReadOnlyCollection '? –

+0

'IReadOnlyCollection ' est suffisant et documente parfaitement à ses appelants à quoi s'attendre. J'aurais dû l'utiliser en premier lieu dans ma question, mais je ne changerai pas cela maintenant pour garder l'historique. – Dejan

2

Bien que il n'est pas readonly, Microsoft a publié un package NuGet appelé System.Collections.Immutable qui contient un ImmutableHashSet<T> qui implémente IImmutableSet<T> qui étend IReadOnlyCollection<T>

Échantillon d'utilisation rapide:

public class TrackedPropertiesBuilder : ITrackedPropertiesBuilder 
{ 
    private ImmutableHashSet<string>.Builder trackedPropertiesBuilder; 

    public TrackedPropertiesBuilder() 
    { 
     this.trackedPropertiesBuilder = ImmutableHashSet.CreateBuilder<string>(); 
    } 

    public ITrackedPropertiesBuilder Add(string propertyName) 
    { 
     this.trackedPropertiesBuilder.Add(propertyName); 
     return this; 
    } 

    public IImmutableSet<string> Build() 
     => this.trackedPropertiesBuilder.ToImmutable(); 
}