如何在不复制元素的情况下从 HashSet 创建 ReadOnlyCollection?

How to make a ReadOnlyCollection from a HashSet without copying the elements?

我有一个私有 HashSet<string>,它是只读 属性 的支持字段,它应该 return 一个只读集合,以便调用者无法修改该集合。所以我尝试:

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

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

这不会编译,因为 ReadOnlyCollection 接受一个 IList<T>,它不是由 HashSet<T> 实现的。我可以使用另一个包装器来避免复制项目吗?就我的目的而言,仅 return 实现 ICollection<T>(而不是 IList<T>)的东西就足够了,它由 HashSet<T>.

实现

考虑将 属性 公开为 IReadOnlyCollection<> 类型,这将提供 HashSet<> 的只读视图。这是一种有效的实现方式,因为 属性 getter 不需要基础集合的副本。

这不会阻止某人将 属性 转换为 HashSet<> 并对其进行修改。如果您担心这一点,请考虑 属性 getter 中的 return _referencedColumns.ToList(),这将创建您的基础集的副本。

您可以使用以下装饰器来包装哈希集和 return 一个只读的 ICollection<T>IsReadOnly 属性 return s 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 的引用。这意味着 returned 集合将包含 HashSet 的实时版本,即,如果 HashSet 发生更改,则在更改之前获得只读集合​​的​​消费者将能够看到更改。

虽然 它不是只读的,但微软发布了一个名为 System.Collections.Immutable 的 nuget 包,其中包含一个 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();
}

这很简单,我不知道为什么微软没有提供这个,但我会 post 我的实现基于 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);
}