Как сделать коллекцию ReadOnlyCollection из HashSet без копирования элементов?
У меня есть личное HashSet<string>
которое является вспомогательным полем свойства только для чтения, которое должно возвращать коллекцию только для чтения, чтобы вызывающие не могли изменять коллекцию. Поэтому я попытался:
public class MyClass
{
private readonly HashSet<string> _referencedColumns;
public ICollection<string> ReferencedColumns {
get { return new ReadOnlyCollection<string>(_referencedColumns); }
}
Это не компилируется как ReadOnlyCollection
принимает IList<T>
который не реализован HashSet<T>
, Можно ли использовать другую обертку, чтобы уберечь меня от копирования предметов? Для моей цели достаточно просто вернуть что-то реализующее ICollection<T>
(вместо IList<T>
), который реализуется HashSet<T>
,
3 ответа
Рассмотрите возможность выставить свойство как тип IReadOnlyCollection<>
вместо этого, который обеспечит доступное только для чтения представление HashSet<>
, Это эффективный способ реализации этого, так как для свойства get не требуется копия базовой коллекции.
Это не помешает кому-то бросить собственность на HashSet<>
и модифицируя его. Если вы обеспокоены этим, подумайте return _referencedColumns.ToList()
в свойстве getter, которое создаст копию вашего базового набора.
В то время как это не только для чтения, Microsoft выпустила пакет nuget под названием System.Collections.Immutable
который содержит ImmutableHashSet<T>
который реализует IImmutableSet<T>
который расширяется IReadOnlyCollection<T>
Пример быстрого использования:
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();
}
Вы можете использовать следующий декоратор, чтобы обернуть хэш-набор и вернуть ICollection<T>
только для чтения (IsReadOnly
свойство возвращает true, а методы модификации выдают NotSupportedException
как указано в договоре 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; }
}
}
И вы можете использовать это так:
public class MyClass
{
private readonly HashSet<string> _referencedColumns;
public ICollection<string> ReferencedColumns {
get { return new MyReadOnlyCollection<string>(_referencedColumns); }
}
//...
Обратите внимание, что это решение не будет делать снимок HashSet, вместо этого оно будет содержать ссылку на HashSet. Это означает, что возвращаемая коллекция будет содержать живую версию HashSet, т. Е. Если HashSet изменен, потребитель, получивший коллекцию только для чтения до изменения, сможет увидеть изменение.
Это довольно просто, я не знаю, почему Microsoft не предоставила это, но я опубликую свою реализацию на основе IReadOnlyCollection<T>
, вместе с методом расширения для полноты.
public class MyClass {
private readonly HashSet<string> _referencedColumns;
public IReadonlyHashSet<string> ReferencedColumns => _referencedColumns.AsReadOnly();
}
/// <summary>Represents hash set which don't allow for items addition.</summary>
/// <typeparam name="T">Type of items int he set.</typeparam>
public interface IReadonlyHashSet<T> : IReadOnlyCollection<T> {
/// <summary>Returns true if the set contains given item.</summary>
public bool Contains(T i);
}
/// <summary>Wrapper for a <see cref="HashSet{T}"/> which allows only for lookup.</summary>
/// <typeparam name="T">Type of items in the set.</typeparam>
public class ReadonlyHashSet<T> : IReadonlyHashSet<T> {
/// <inheritdoc/>
public int Count => set.Count;
private HashSet<T> set;
/// <summary>Creates new wrapper instance for given hash set.</summary>
public ReadonlyHashSet(HashSet<T> set) => this.set = set;
/// <inheritdoc/>
public bool Contains(T i) => set.Contains(i);
/// <inheritdoc/>
public IEnumerator<T> GetEnumerator() => set.GetEnumerator();
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => set.GetEnumerator();
}
/// <summary>Extension methods for the <see cref="HashSet{T}"/> class.</summary>
public static class HasSetExtensions {
/// <summary>Returns read-only wrapper for the set.</summary>
public static ReadonlyHashSet<T> AsReadOnly<T>(this HashSet<T> s)
=> new ReadonlyHashSet<T>(s);
}