在.NET中是否有只读的通用字典?
我在我的只读属性中返回对字典的引用。 如何防止消费者更改我的数据? 如果这是一个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
这是一个包装字典的简单实现:
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"); } }
.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 。 但是,创build一个并不困难。
这里有一个例子 – 如果你是Google for ReadOnlyDictionary,还有很多其他的。
在最近的BUILD会议上宣布,自.NET 4.5开始,包含了接口System.Collections.Generic.IReadOnlyDictionary<TKey,TValue>
。 这里的certificate(Mono)和这里 (Microsoft);)
不确定是否也包含了ReadOnlyDictionary
,但至less在界面上,现在应该不难创build一个暴露官方.NET通用接口的实现:)
随意使用我的简单包装。 它不会实现IDictionary,所以它不必在运行时为会改变字典的字典方法抛出exception。 改变方法根本就不存在。 我为它创build了自己的接口,叫做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(); } }
IsReadOnly on IDictionary<TKey,TValue>
inheritance自ICollection<T>
( IDictionary<TKey,TValue>
将ICollection<T>
扩展为ICollection<KeyValuePair<TKey,TValue>>
)。 它不以任何方式使用或实现(实际上通过使用显式实现相关的ICollection<T>
成员来“隐藏”)。
至less有三种方法可以解决这个问题:
- 实现一个自定义的只读
IDictionary<TKey, TValue>
并按照build议将其封装/委托给内部字典 - 根据值的使用将
ICollection<KeyValuePair<TKey, TValue>>
设置为只读或IEnumerable<KeyValuePair<TKey, TValue>>
- 使用复制构造函数
.ctor(IDictionary<TKey, TValue>)
克隆字典并返回一个副本 – 这样用户就可以随意使用它,并且不会影响托pipe源字典的对象的状态。 请注意,如果您正在克隆的字典包含引用types(不像示例中所示的string),则需要执行“手动”复制并克隆引用types。
旁白 当展示集合时,目的是公开最小的可能的接口 – 在示例中它应该是IDictionary,因为这允许您改变底层实现,而不破坏types公开的公共契约。
只读字典在某种程度上可以被Func<TKey, TValue>
替代 – 我通常在API中使用它,如果我只希望人们执行查找, 这很简单,尤其是,如果你愿意的话,更换后端很简单。 它不提供密钥列表,但是; 无论这个问题取决于你在做什么。
不,但是很容易推出自己的产品。 IDictionary确实定义了一个IsReadOnly属性。 只需包装一个Dictionary,并从适当的方法中引发一个NotSupportedException。
BCL中没有提供。 不过,我在BCL Extras项目中发布了ReadOnlyDictionary(名为ImmutableMap)
除了作为一个完全不可变的字典外,它还支持生成一个实现了IDictionary的代理对象,并可以在任何需要使用IDictionary的地方使用。 只要调用一个可变的API,它就会抛出一个exception
void Example() { var map = ImmutableMap.Create<int,string>(); map = map.Add(42,"foobar"); IDictionary<int,string> dictionary = CollectionUtility.ToIDictionary(map); }
我不认为有这样一个简单的方法…如果你的字典是自定义类的一部分,你可以用索引器来实现它:
public class MyClass { private Dictionary<string, string> _myDictionary; public string this[string index] { get { return _myDictionary[index]; } } }
+1伟大的工作,托马斯。 我把ReadOnlyDictionary更进了一步。
就像Dale的解决scheme一样,我想从IntelliSense中删除Add()
, Clear()
, Remove()
等。 但是我想让我的派生对象实现IDictionary<TKey, TValue>
。
此外,我想要破解下面的代码:(同样,Dale的解决scheme也是这样做的)
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的解决scheme),则会引发NotSupportedException
。
无论如何,这是我的解决scheme,任何人也想这个:
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); } } }
现在,有Microsoft不变集合 ( System.Collections.Immutable
)。 通过NuGet获取他们。
你可以创build一个只实现字典部分实现的类,并隐藏所有的add / remove / set函数。
内部使用一个字典,外部类传递所有的请求。
但是,由于您的字典可能存在引用types,因此您无法阻止用户设置字典所持有的类的值(除非这些类本身是只读的)
public IEnumerable<KeyValuePair<string, string>> MyDictionary() { foreach(KeyValuePair<string, string> item in _mydictionary) yield return item; }
这是一个糟糕的解决scheme,请参阅底部。
对于那些仍然使用.NET 4.0或更早版本的人,我有一个类似于被接受的答案的类,但是它要短得多。 它扩展了现有的Dictionary对象,覆盖(实际上是隐藏)某些成员,使它们在被调用时抛出一个exception。
如果调用者试图调用Add,Remove或其他内置Dictionary所具有的变异操作,编译器将会抛出一个错误。 我使用Obsolete属性来提高这些编译器错误。 这样,你可以用这个ReadOnlyDictionaryreplace一个Dictionary,并立即看到任何问题,而不必运行你的应用程序,并等待运行时exception。
看一看:
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(); } }
这个解决scheme有一个问题在这里说明@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); }
这个代码不会像我期望的那样给出编译时错误,或者像我希望的那样运行时exception,而是运行时没有错误。 它打印四个数字。 这使我的ReadOnlyDictionary一个ReadWriteDictionary。