【问题标题】:C# HashSet<T> read-only workaroundC# HashSet<T> 只读解决方法
【发布时间】:2016-08-17 08:07:04
【问题描述】:

这是示例代码:

static class Store
{
    private static List<String> strList = new List<string>();
    private static HashSet<String> strHashSet = new HashSet<string>();

    public static List<String> NormalList
    {
        get { return strList; }
    }

    public static HashSet<String> NormalHashSet
    {
        get { return strHashSet; }
    }

    public static IReadOnlyList<String> ReadonlyList
    {
        get { return (IReadOnlyList<String>)strList; }
    }

    public static IReadOnlyCollection<String> ReadonlyHashSet
    {
        get { return (IReadOnlyCollection<String>)strHashSet; }
    }

    public static IReadOnlyList<String> Real_ReadonlyList
    {
        get { return (IReadOnlyList<String>)strList.AsReadOnly(); }
    }

    public static IReadOnlyCollection<String> Real_ReadonlyHashSet
    {
        get
        {
            List<String> tmpList = new List<String>(strHashSet);
            return (IReadOnlyList<String>)(tmpList).AsReadOnly();
        }
    }
}

这是一个测试代码:

// normal behaviour
// you can modify the list and the hashset

Store.NormalList.Add("some string 1");

Store.NormalHashSet.Add("some string 1");

// tricky behaviour
// you can still modify the list and the hashset

((List<String>)Store.ReadonlyList).Add("some string 2");

((HashSet<String>)Store.ReadonlyHashSet).Add("some string 2");

// expected read-only behaviour
// you can NOT modify

// throws InvalidCastException
((List<String>)Store.Real_ReadonlyList).Add("some string 3");
// throws InvalidCastException
((HashSet<String>)Store.Real_ReadonlyHashSet).Add("some string 3");

我的问题是:

“Real_ReadonlyHashSet”属性是否有更好的解决方案?

微软有一天会为 HashSet 实现“AsReadOnly”方法吗?

【问题讨论】:

标签: c# list hashset generic-collections readonly-collection


【解决方案1】:

这里是the entirety of the code.AsReadOnly()

public ReadOnlyCollection<T> AsReadOnly() {
    Contract.Ensures(Contract.Result<ReadOnlyCollection<T>>() != null);
    return new ReadOnlyCollection<T>(this);
}

如果您不使用 CodeContracts,则甚至不需要第一行。但是ReadOnlyCollection&lt;T&gt; 只支持IList&lt;T&gt;HashSet&lt;T&gt; 不支持。

我要做的是创建自己的 ReadOnlySet&lt;T&gt; 类,该类接受 ISet&lt;T&gt; 并且仅通过读取操作 like ReadOnlyCollection&lt;T&gt; does internally

更新: 这是一个完全充实的ReadOnlySet&lt;T&gt;,我很快写了一个扩展方法,在任何实现ISet&lt;T&gt;的东西上添加.AsReadOnly()

public static class SetExtensionMethods
{
    public static ReadOnlySet<T> AsReadOnly<T>(this ISet<T> set)
    {
        return new ReadOnlySet<T>(set);
    }
}

public class ReadOnlySet<T> : IReadOnlyCollection<T>, ISet<T>
{
    private readonly ISet<T> _set;
    public ReadOnlySet(ISet<T> set)
    {
        _set = set;
    }

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

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

    void ICollection<T>.Add(T item)
    {
        throw new NotSupportedException("Set is a read only set.");
    }

    public void UnionWith(IEnumerable<T> other)
    {
        throw new NotSupportedException("Set is a read only set.");
    }

    public void IntersectWith(IEnumerable<T> other)
    {
        throw new NotSupportedException("Set is a read only set.");
    }

    public void ExceptWith(IEnumerable<T> other)
    {
        throw new NotSupportedException("Set is a read only set.");
    }

    public void SymmetricExceptWith(IEnumerable<T> other)
    {
        throw new NotSupportedException("Set is a read only set.");
    }

    public bool IsSubsetOf(IEnumerable<T> other)
    {
        return _set.IsSubsetOf(other);
    }

    public bool IsSupersetOf(IEnumerable<T> other)
    {
        return _set.IsSupersetOf(other);
    }

    public bool IsProperSupersetOf(IEnumerable<T> other)
    {
        return _set.IsProperSupersetOf(other);
    }

    public bool IsProperSubsetOf(IEnumerable<T> other)
    {
        return _set.IsProperSubsetOf(other);
    }

    public bool Overlaps(IEnumerable<T> other)
    {
        return _set.Overlaps(other);
    }

    public bool SetEquals(IEnumerable<T> other)
    {
        return _set.SetEquals(other);
    }

    public bool Add(T item)
    {
        throw new NotSupportedException("Set is a read only set.");
    }

    public void Clear()
    {
        throw new NotSupportedException("Set is a read only set.");
    }

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

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

    public bool Remove(T item)
    {
        throw new NotSupportedException("Set is a read only set.");
    }

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

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

【讨论】:

  • 能否请您为 ImmutableHashSet 添加更新?问题下方的评论很容易被忽略。
  • @webbertee 不可变对象行与只读对象行不同。不可变用于并发......我不是这方面的专家,但是使用一个代替另一个可能导致各种问题,即使它们很微妙。
【解决方案2】:

您可以编写自己的 IReadOnlyCollection&lt;T&gt; 实现,其中包含 IEnumerable&lt;T&gt; 和计数:

public sealed class ReadOnlyCollectionFromEnumerable<T>: IReadOnlyCollection<T>
{
    readonly IEnumerable<T> _data;

    public ReadOnlyCollectionFromEnumerable(IEnumerable<T> data, int count)
    {
        _data = data;
        Count = count;
    }

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

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

    public int Count { get; }
}

然后你像这样声明你的ReadonlyHashSet 属性:

public static IReadOnlyCollection<String> ReadonlyHashSet
{
    get { return new ReadOnlyCollectionFromEnumerable<string>(strHashSet, strHashSet.Count); }
}

我认为这会解决问题。

【讨论】:

  • 我认为传递ICollection&lt;T&gt; 会更好,这样你就可以传递.Contains(,这是HashSet 中最强大的部分。
  • @ScottChamberlain 我保持返回类型与 OP 中的相同 - 当然,Contains() 不是IReadOnlyCollection&lt;T&gt; 的成员。我想 OP 想通过类型而不是属性(IsReadOnly)表明该值是只读的
  • 啊,我要去的是ReadOnlyCollection&lt;T&gt;,哪个does pass it along,而不是IReadOnlyCollection&lt;T&gt;
  • @ScottChamberlain 虽然很奇怪,不是吗?微软在这方面似乎不太一致……
【解决方案3】:

HashSet 实现 IReadOnlyCollection 接口启动 使用 .NET Framework 4.6;在 .NET 的早期版本中 框架,HashSet类没有实现这个接口。

Read in docs.microsoft.com

【讨论】:

  • 但是,IReadOnlyCollection&lt;T&gt; 没有公开 Boolean Contains(T item) 方法。
  • @Dai 你是对的,但是当前实现 Enumerable.Contains() 扩展方法试图将类型转换为 ICollection 并且如果成功调用包含在 ICollection 上(转换为 ICollection 成功,因为 HashSet 实现了 ICollection) - 和该方法的实现将包含在 HashSet 中。 referencesource.microsoft.com/#System.Core/System/Linq/…
【解决方案4】:

在 .NET Framework 4.6 版本中,HashSet 实现了 IReadOnlyCollection 接口以及 ISet 接口。 link... 似乎确实如此。

您也可以这样做,但可能会影响性能:

var foo = (IReadOnlyCollection<string>) mySet.toList(); 

【讨论】: