我在我的只读属性中返回对字典的引用。如何防止消费者更改我的数据?如果这是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
答案 0 :(得分:230)
.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.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种方法可以解决问题:
IDictionary<TKey, TValue>
和
包裹/委托给内心
字典已被建议ICollection<KeyValuePair<TKey,
TValue>>
设置为只读或
IEnumerable<KeyValuePair<TKey,
TValue>>
取决于使用
价值.ctor(IDictionary<TKey,
TValue>)
并返回一个副本 - 那个
用户可以自由使用它的方式
因为他们喜欢而事实并非如此
对对象状态的影响
托管源字典。注意
如果你是字典
克隆包含引用类型(
不是字符串,如示例中所示
)你需要进行复制
“手动”并克隆参考
类型也是如此。撇开;暴露集合时,旨在暴露最小的可能接口 - 在示例中它应该是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 Collections(System.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();
}