在.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
72524 次浏览

您可以创建一个类,该类只实现字典的部分实现,并隐藏所有的 add/delete/set 函数。

在内部使用外部类向其传递所有请求的字典。

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

没有,但是你自己卷起来很容易。IDictionary 确实定义了 IsReadOnly 属性。只需包装一个 Dictionary 并从适当的方法抛出 NotSupportedException 即可。

.NET 4.5

NET Framework 4.5 BCL 引入了 ReadOnlyDictionary<TKey, TValue>(来源)。

作为。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 框架类,它像 ReadOnlyCollection包装 名单一样包装 Dictionary<TKey, TValue>。然而,创造一个并不困难。

这里有一个例子 -如果你是 Google for ReadOnlyDictionary 谷歌只读字典,还有很多其他的例子。

我不认为有一个简单的方法可以做到这一点... 如果你的字典是一个定制类的一部分,你可以实现它与索引器:

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


public string this[string index]
{
get { return _myDictionary[index]; }
}
}
public IEnumerable<KeyValuePair<string, string>> MyDictionary()
{
foreach(KeyValuePair<string, string> item in _mydictionary)
yield return item;
}

但是我在我的 BCL 额外项目中发布了一个 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);
}

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 > > set as read only or an IEnumable < KeyValuePair < TKey, TValue > > 取决于使用 价值
  3. 使用副本克隆字典 构造函数 < code > . ctor (IDictionary < TKey, TValue >) 并返回一个拷贝-that 用户可以自由使用它的方式 如他们所愿,但事实并非如此 对物体状态的影响 托管源字典。注意 如果你是字典 克隆包含引用类型( 而不是示例中显示的字符串 ) “手动”并克隆引用 类型。

顺便说一句,在公开集合时,目标是公开尽可能小的接口——在本例中,它应该是 IDictionary,因为这允许您在不破坏类型公开的公共契约的情况下更改底层实现。

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

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

只读字典在某种程度上可以被 Func<TKey, TValue>替换——如果我只想让人们执行查找,我通常会在 API 中使用它; 它很简单,特别是,如果你想要替换后端,它很简单。但是,它不提供键列表; 这是否重要取决于您正在执行的操作。

请随意使用我的简单包装纸。它没有实现 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(); }
}

在最近的 建造会议中宣布,自从。NET 4.5,包含 System.Collections.Generic.IReadOnlyDictionary<TKey,TValue>接口。证明是 给你(Mono)和 给你(Microsoft) ;)

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

干得好,托马斯。我把 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);
}
}
}

现在,有 Microsoft 不可变集合(System.Collections.Immutable)。让他们 通过 NuGet

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

对于那些仍在使用。NET 4.0或更早的版本,我有一个类,它的工作原理和公认的答案中的类一样,但是它要短得多。它扩展了现有的 Dictionary 对象,重写(实际上隐藏)某些成员,使它们在调用时抛出异常。

如果调用方试图调用添加、删除或内置 Dictionary 具有的其他变异操作,编译器将引发错误。我使用过时属性引发这些编译器错误。通过这种方式,您可以使用 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。

对于任何使用.NET Core 来回答这个问题的人,请查看 系统,收集,不变

特别是对于不可变的字典,您可以使用 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();
}