.NET中是否有只读通用字典?

Rob*_*ers 185 .net dictionary readonly

我在我的只读属性中返回对字典的引用.如何防止消费者更改我的数据?如果这是一个IList我可以简单地返回它AsReadOnly.我能用字典做些什么吗?

Private _mydictionary As Dictionary(Of String, String)
Public ReadOnly Property MyDictionary() As Dictionary(Of String, String)
    Get
        Return _mydictionary
    End Get
End Property
Run Code Online (Sandbox Code Playgroud)

Jef*_*tes 230

.NET 4.5

.NET Framework 4.5 BCL介绍ReadOnlyDictionary<TKey, TValue>(源代码).

由于.NET Framework 4.5 BCL不包含AsReadOnlyfor词典,因此您需要编写自己的(如果需要).它将类似于以下内容,其简单性可能突出了它为什么不是.NET 4.5的优先级.

public static ReadOnlyDictionary<TKey, TValue> AsReadOnly<TKey, TValue>(
    this IDictionary<TKey, TValue> dictionary)
{
    return new ReadOnlyDictionary<TKey, TValue>(dictionary);
}
Run Code Online (Sandbox Code Playgroud)

.NET 4.0及更低版本

在.NET 4.5之前,没有.NET框架类包装Dictionary<TKey, TValue>类似于ReadOnlyCollection包装List.但是,创建一个并不困难.

这是一个例子 - 如果你是谷歌的ReadOnlyDictionary,还有很多其他的.

  • 它们看起来并不像在通常的`Dictionary <,>`上创建`AsReadOnly()`方法,所以我想知道有多少人会发现他们的新类型.不过,这个Stack Overflow线程会有所帮助. (7认同)

Tho*_*que 156

这是一个包装字典的简单实现:

public class ReadOnlyDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
    private readonly IDictionary<TKey, TValue> _dictionary;

    public ReadOnlyDictionary()
    {
        _dictionary = new Dictionary<TKey, TValue>();
    }

    public ReadOnlyDictionary(IDictionary<TKey, TValue> dictionary)
    {
        _dictionary = dictionary;
    }

    #region IDictionary<TKey,TValue> Members

    void IDictionary<TKey, TValue>.Add(TKey key, TValue value)
    {
        throw ReadOnlyException();
    }

    public bool ContainsKey(TKey key)
    {
        return _dictionary.ContainsKey(key);
    }

    public ICollection<TKey> Keys
    {
        get { return _dictionary.Keys; }
    }

    bool IDictionary<TKey, TValue>.Remove(TKey key)
    {
        throw ReadOnlyException();
    }

    public bool TryGetValue(TKey key, out TValue value)
    {
        return _dictionary.TryGetValue(key, out value);
    }

    public ICollection<TValue> Values
    {
        get { return _dictionary.Values; }
    }

    public TValue this[TKey key]
    {
        get
        {
            return _dictionary[key];
        }
    }

    TValue IDictionary<TKey, TValue>.this[TKey key]
    {
        get
        {
            return this[key];
        }
        set
        {
            throw ReadOnlyException();
        }
    }

    #endregion

    #region ICollection<KeyValuePair<TKey,TValue>> Members

    void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
    {
        throw ReadOnlyException();
    }

    void ICollection<KeyValuePair<TKey, TValue>>.Clear()
    {
        throw ReadOnlyException();
    }

    public bool Contains(KeyValuePair<TKey, TValue> item)
    {
        return _dictionary.Contains(item);
    }

    public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
    {
        _dictionary.CopyTo(array, arrayIndex);
    }

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

    public bool IsReadOnly
    {
        get { return true; }
    }

    bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
    {
        throw ReadOnlyException();
    }

    #endregion

    #region IEnumerable<KeyValuePair<TKey,TValue>> Members

    public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
    {
        return _dictionary.GetEnumerator();
    }

    #endregion

    #region IEnumerable Members

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

    #endregion

    private static Exception ReadOnlyException()
    {
        return new NotSupportedException("This dictionary is read-only");
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @askheaves:很好的观察,但在Read Only类型中使用原始引用实际上非常有用 - 在私有变量中保留原始变量并为外部使用者修改它.例如,查看内置的ReadOnlyObservableCollection或ReadOnlyCollection对象:Thomas提供的内容与.Net框架固有的内容完全相同.谢谢托马斯!+1 (24认同)
  • @Samuel:用于创建一个空的只读字典. (22认同)
  • 注意那个构造函数.如果您执行传入的字典的引用副本,则外部代码可以修改"只读"字典.您的构造函数应该对参数执行完整,深入的副本. (20认同)
  • @ user420667:它的实现方式,它是"非只读字典的只读视图".其他一些代码可能会更改原始字典的内容,这些更改将反映在只读字典中.它可能是理想的行为,取决于你想要实现的目标...... (12认同)
  • +1用于发布完整代码而不仅仅是链接,但我很好奇,ReadOnlyDictionary中空构造函数的重点是什么?:-) (11认同)
  • @Thomas:这与.NET BCL中的ReadOnlyCollection相同.它是可能可变集合的只读视图.ReadOnly并不意味着不可预期的不可变性和不变性. (5认同)
  • 我建议将所有NotSupported方法实现为显式接口实现,以便它们更难调用. (5认同)
  • 我建议在你的Add,Remove,Clear和this [] setter实现之前放置`[Obsolete("ReadOnlyDictionaries"不支持,真实)]`attributues.这样,调用者在尝试使用这些方法时会收到编译时错误.这与您将按原样获得的运行时错误相反. (3认同)
  • @ user2023861,这些方法已经被声明为显式接口实现,因此不能直接从类型为ReadOnlyDictionary &lt;TKey,TValue&gt;的变量中调用它们。只能从IDictionary &lt;TKey,TValue&gt;`类型的变量中调用它们,显然我不能在接口上添加这些属性... (2认同)
  • @askheaves [`ImmutableDictionary&lt;TKey, TValue&gt;`](http://msdn.microsoft.com/en-us/library/dn467181(v=vs.111).aspx) (2认同)

kno*_*cte 19

在最近的BUILD会议上宣布,自.NET 4.5以来,System.Collections.Generic.IReadOnlyDictionary<TKey,TValue>包含了界面.证明在这里(单声道)和这里(微软);)

不确定是否ReadOnlyDictionary也包括在内,但至少在界面上创建一个暴露官方.NET通用接口的实现应该不难.

  • `ReadOnlyDictionary&lt;TKey, TValue&gt;` (.Net 4.5) -- http://msdn.microsoft.com/en-us/library/gg712875.aspx (5认同)

Dal*_*ard 18

随意使用我的简单包装.它不实现IDictionary,因此它不必在运行时为可能更改字典的字典方法抛出异常.改变方法根本不存在.我为它创建了自己的界面,名为IReadOnlyDictionary.

public interface IReadOnlyDictionary<TKey, TValue> : IEnumerable
{
    bool ContainsKey(TKey key);
    ICollection<TKey> Keys { get; }
    ICollection<TValue> Values { get; }
    int Count { get; }
    bool TryGetValue(TKey key, out TValue value);
    TValue this[TKey key] { get; }
    bool Contains(KeyValuePair<TKey, TValue> item);
    void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex);
    IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator();
}

public class ReadOnlyDictionary<TKey, TValue> : IReadOnlyDictionary<TKey, TValue>
{
    readonly IDictionary<TKey, TValue> _dictionary;
    public ReadOnlyDictionary(IDictionary<TKey, TValue> dictionary)
    {
        _dictionary = dictionary;
    }
    public bool ContainsKey(TKey key) { return _dictionary.ContainsKey(key); }
    public ICollection<TKey> Keys { get { return _dictionary.Keys; } }
    public bool TryGetValue(TKey key, out TValue value) { return _dictionary.TryGetValue(key, out value); }
    public ICollection<TValue> Values { get { return _dictionary.Values; } }
    public TValue this[TKey key] { get { return _dictionary[key]; } }
    public bool Contains(KeyValuePair<TKey, TValue> item) { return _dictionary.Contains(item); }
    public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) { _dictionary.CopyTo(array, arrayIndex); }
    public int Count { get { return _dictionary.Count; } }
    public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() { return _dictionary.GetEnumerator(); }
    IEnumerator IEnumerable.GetEnumerator() { return _dictionary.GetEnumerator(); }
}
Run Code Online (Sandbox Code Playgroud)

  • +1不违反`IDictionary`合同.我认为从OOP的角度来看,对于`IDictionary`继承自`IReadOnlyDictionary`更为正确. (4认同)

Nea*_*eal 11

IsReadOnly on IDictionary<TKey,TValue>继承自ICollection<T>(IDictionary<TKey,TValue>extends ICollection<T>as ICollection<KeyValuePair<TKey,TValue>>).它不以任何方式使用或实现(实际上通过使用显式实现关联ICollection<T>成员来"隐藏" ).

我可以通过至少3种方式来解决问题:

  1. 按照建议实现自定义只读IDictionary<TKey, TValue>和包装/委托给内部字典
  2. ICollection<KeyValuePair<TKey, TValue>>集合返回 为只读或 IEnumerable<KeyValuePair<TKey, TValue>>取决于值的使用
  3. 使用复制构造函数克隆字典.ctor(IDictionary<TKey, TValue>)并返回副本 - 这样用户可以随意使用它,并且它不会影响托管源字典的对象的状态.请注意,如果要克隆的字典包含引用类型(不是示例中所示的字符串),则需要"手动"复制并克隆引用类型.

作为旁白; 在暴露集合时,旨在暴露最小的可能接口 - 在示例中它应该是IDictionary,因为这允许您在不破坏类型公开的公共合同的情况下改变底层实现.


Eam*_*nne 8

只读字典可以在某种程度上替换为Func<TKey, TValue>- 如果我只希望人们执行查找,我通常在API中使用它.它很简单,特别是,如果您愿意,更换后端很简单.但是,它没有提供密钥列表; 这是否重要取决于你在做什么.