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

时间:2009-03-24 17:21:31

标签: .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

15 个答案:

答案 0 :(得分:230)

.NET 4.5

.NET Framework 4.5 BCL引入了ReadOnlyDictionary<TKey, TValue>source)。

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

public static ReadOnlyDictionary<TKey, TValue> AsReadOnly<TKey, TValue>(
    this IDictionary<TKey, TValue> dictionary)
{
    return new ReadOnlyDictionary<TKey, TValue>(dictionary);
}

.NET 4.0及以下

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

Here is an example - 如果您Google for ReadOnlyDictionary,还有很多其他人。

答案 1 :(得分: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");
    }
}

答案 2 :(得分:19)

最近在BUILD conference宣布自.NET 4.5以来,包含了System.Collections.Generic.IReadOnlyDictionary<TKey,TValue>接口。证明是here(单声道)和here(微软);)

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

答案 3 :(得分: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(); }
}

答案 4 :(得分:11)

IDictionary<TKey,TValue>上的IsReadOnly继承自ICollection<T>IDictionary<TKey,TValue>扩展ICollection<T>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>)并返回一个副本 - 那个 用户可以自由使用它的方式 因为他们喜欢而事实并非如此 对对象状态的影响 托管源字典。注意 如果你是字典 克隆包含引用类型( 不是字符串,如示例中所示 )你需要进行复制 “手动”并克隆参考 类型也是如此。
  4. 撇开;暴露集合时,旨在暴露最小的可能接口 - 在示例中它应该是IDictionary,因为这允许您在不破坏类型公开的公共合同的情况下改变底层实现。

答案 5 :(得分:7)

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

答案 6 :(得分:4)

不,但滚动自己会很容易。 IDictionary确实定义了一个IsReadOnly属性。只需包装一个Dictionary并从适当的方法中抛出NotSupportedException。

答案 7 :(得分:3)

BCL中没有。但是我在BCL Extras Project

中发布了一个ReadOnlyDictionary(名为ImmutableMap)

除了是一个完全不可变的字典之外,它还支持生成一个实现IDictionary的代理对象,并且可以在任何采用IDictionary的地方使用。只要调用其中一个变异API,就会抛出异常

void Example() { 
  var map = ImmutableMap.Create<int,string>();
  map = map.Add(42,"foobar");
  IDictionary<int,string> dictionary = CollectionUtility.ToIDictionary(map);
}

答案 8 :(得分:1)

您可以创建一个只实现字典部分实现的类,并隐藏所有添加/删除/设置功能。

在内部使用字典,外部类将所有请求传递给。

但是,由于您的字典可能包含引用类型,因此您无法阻止用户在字典所持有的类上设置值(除非这些类本身是只读的)

答案 9 :(得分:1)

我认为没有一种简单的方法可以做到这一点......如果你的字典是自定义类的一部分,你可以用索引器实现它:

public class MyClass
{
  private Dictionary<string, string> _myDictionary;

  public string this[string index]
  {
    get { return _myDictionary[index]; }
  }
}

答案 10 :(得分:1)

+1干得好,托马斯。我进一步采用了ReadOnlyDictionary。

与Dale的解决方案非常相似,我想从IntelliSense中删除Add()Clear()Remove()等。但我希望我的派生对象能够实现IDictionary<TKey, TValue>

此外,我希望以下代码能够打破:(同样,Dale的解决方案也是如此)

ReadOnlyDictionary<int, int> test = new ReadOnlyDictionary<int,int>(new Dictionary<int, int> { { 1, 1} });
test.Add(2, 1);  //CS1061

Add()行产生:

error CS1061: 'System.Collections.Generic.ReadOnlyDictionary<int,int>' does not contain a definition for 'Add' and no extension method 'Add' accepting a first argument 

调用者仍然可以将其强制转换为IDictionary<TKey, TValue>,但如果您尝试使用非只读成员(来自Thomas的解决方案),则会引发NotSupportedException

无论如何,对于任何想要这个的人来说,这是我的解决方案:

namespace System.Collections.Generic
{
    public class ReadOnlyDictionary<TKey, TValue> : IDictionary<TKey, TValue>
    {
        const string READ_ONLY_ERROR_MESSAGE = "This dictionary is read-only";

        protected IDictionary<TKey, TValue> _Dictionary;

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

        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]; }
            set { throw new NotSupportedException(READ_ONLY_ERROR_MESSAGE); }
        }

        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; }
        }

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

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

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

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

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

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

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

答案 11 :(得分:1)

现在,有Microsoft Immutable CollectionsSystem.Collections.Immutable)。得到他们via NuGet

答案 12 :(得分:0)

public IEnumerable<KeyValuePair<string, string>> MyDictionary()
{
    foreach(KeyValuePair<string, string> item in _mydictionary)
        yield return item;
}

答案 13 :(得分:0)

这是一个糟糕的解决方案,请参阅底部。

对于那些仍在使用.NET 4.0或更早版本的人,我有一个类似于接受的答案中的类,但它更短。它扩展了现有的Dictionary对象,覆盖(实际隐藏)某些成员,让它们在被调用时抛出异常。

如果调用者尝试调用内置字典所具有的Add,Remove或其他一些变异操作,编译器将抛出错误。我使用Obsolete属性来引发这些编译器错误。这样,您可以使用此ReadOnlyDictionary替换Dictionary,并立即查看可能存在的问题,而无需运行应用程序并等待运行时异常。

看看:

public class ReadOnlyException : Exception
{
}

public class ReadOnlyDictionary<TKey, TValue> : Dictionary<TKey, TValue>
{
    public ReadOnlyDictionary(IDictionary<TKey, TValue> dictionary)
        : base(dictionary) { }

    public ReadOnlyDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer)
        : base(dictionary, comparer) { }

    //The following four constructors don't make sense for a read-only dictionary

    [Obsolete("Not Supported for ReadOnlyDictionaries", true)]
    public ReadOnlyDictionary() { throw new ReadOnlyException(); }

    [Obsolete("Not Supported for ReadOnlyDictionaries", true)]
    public ReadOnlyDictionary(IEqualityComparer<TKey> comparer) { throw new ReadOnlyException(); }

    [Obsolete("Not Supported for ReadOnlyDictionaries", true)]
    public ReadOnlyDictionary(int capacity) { throw new ReadOnlyException(); }

    [Obsolete("Not Supported for ReadOnlyDictionaries", true)]
    public ReadOnlyDictionary(int capacity, IEqualityComparer<TKey> comparer) { throw new ReadOnlyException(); }


    //Use hiding to override the behavior of the following four members
    public new TValue this[TKey key]
    {
        get { return base[key]; }
        //The lack of a set accessor hides the Dictionary.this[] setter
    }

    [Obsolete("Not Supported for ReadOnlyDictionaries", true)]
    public new void Add(TKey key, TValue value) { throw new ReadOnlyException(); }

    [Obsolete("Not Supported for ReadOnlyDictionaries", true)]
    public new void Clear() { throw new ReadOnlyException(); }

    [Obsolete("Not Supported for ReadOnlyDictionaries", true)]
    public new bool Remove(TKey key) { throw new ReadOnlyException(); }
}

此解决方案存在@supercat指出的问题:

var dict = new Dictionary<int, string>
{
    { 1, "one" },
    { 2, "two" },
    { 3, "three" },
};

var rodict = new ReadOnlyDictionary<int, string>(dict);
var rwdict = rodict as Dictionary<int, string>;
rwdict.Add(4, "four");

foreach (var item in rodict)
{
    Console.WriteLine("{0}, {1}", item.Key, item.Value);
}

而不是像我期望的那样给出编译时错误,或者像我希望的那样给出运行时异常,这段代码运行时没有错误。它打印四个数字。这使我的ReadOnlyDictionary成为ReadWriteDictionary。

答案 14 :(得分:0)

对于使用.NET Core遇到此问题的任何人,请查看System.Collections.Immutable

对于不可变的字典,可以使用ImmutableDictionary<TKey, TValue>.Builder

public static ImmutableDictionary<string, string> ToReadOnly(this IDictionary<String, string> d)
{
    var builder = new ImmutableDictionary<string, string>.Builder();
    builder.AddRange(d);
    return builder.ToImmutable();
}