Existe um dictionary genérico somente leitura disponível no .NET?

Estou retornando uma referência a um dictionary em minha propriedade somente leitura. Como evito que os consumidores alterem meus dados? Se este fosse um IList eu poderia simplesmente devolvê-lo como AsReadOnly . Existe algo semelhante que eu possa fazer com um dictionary?

 Private _mydictionary As Dictionary(Of String, String) Public ReadOnly Property MyDictionary() As Dictionary(Of String, String) Get Return _mydictionary End Get End Property 

Aqui está uma implementação simples que envolve um dictionary:

 public class ReadOnlyDictionary : IDictionary { private readonly IDictionary _dictionary; public ReadOnlyDictionary() { _dictionary = new Dictionary(); } public ReadOnlyDictionary(IDictionary dictionary) { _dictionary = dictionary; } #region IDictionary Members void IDictionary.Add(TKey key, TValue value) { throw ReadOnlyException(); } public bool ContainsKey(TKey key) { return _dictionary.ContainsKey(key); } public ICollection Keys { get { return _dictionary.Keys; } } bool IDictionary.Remove(TKey key) { throw ReadOnlyException(); } public bool TryGetValue(TKey key, out TValue value) { return _dictionary.TryGetValue(key, out value); } public ICollection Values { get { return _dictionary.Values; } } public TValue this[TKey key] { get { return _dictionary[key]; } } TValue IDictionary.this[TKey key] { get { return this[key]; } set { throw ReadOnlyException(); } } #endregion #region ICollection> Members void ICollection>.Add(KeyValuePair item) { throw ReadOnlyException(); } void ICollection>.Clear() { throw ReadOnlyException(); } public bool Contains(KeyValuePair item) { return _dictionary.Contains(item); } public void CopyTo(KeyValuePair[] array, int arrayIndex) { _dictionary.CopyTo(array, arrayIndex); } public int Count { get { return _dictionary.Count; } } public bool IsReadOnly { get { return true; } } bool ICollection>.Remove(KeyValuePair item) { throw ReadOnlyException(); } #endregion #region IEnumerable> Members public IEnumerator> 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

O .NET Framework 4.5 BCL apresenta ReadOnlyDictionary ( origem ).

Como o .NET Framework 4.5 BCL não inclui um AsReadOnly para dictionarys, você precisará escrever o seu próprio (se desejar). Seria algo como o seguinte, cuja simplicidade talvez destaca por que não era uma prioridade para o .NET 4.5.

 public static ReadOnlyDictionary AsReadOnly( this IDictionary dictionary) { return new ReadOnlyDictionary(dictionary); } 

.NET 4.0 e abaixo

Antes do .NET 4.5, não há nenhuma class de estrutura .NET que envolva um Dictionary como o ReadOnlyCollection encapsula uma List . No entanto, não é difícil criar um.

Aqui está um exemplo – há muitos outros se você usar o Google para ReadOnlyDictionary .

Foi anunciado na recente conferência BUILD que, desde o .NET 4.5, a interface System.Collections.Generic.IReadOnlyDictionary está incluída. A prova está aqui (Mono) e aqui (Microsoft);)

Não tenho certeza se ReadOnlyDictionary está incluído também, mas pelo menos com a interface não deve ser difícil criar agora uma implementação que expõe uma interface genérica .NET oficial 🙂

Sinta-se à vontade para usar meu invólucro simples. Ele não implementa IDictionary, portanto, não precisa lançar exceções em tempo de execução para methods de dictionary que alterem o dictionary. Os methods de mudança simplesmente não estão lá. Eu fiz minha própria interface para isso chamado IReadOnlyDictionary.

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

IsReadOnly on IDictionary é herdado de ICollection ( IDictionary estende ICollection como ICollection> ). Ele não é usado ou implementado de qualquer maneira (e é, de fato, “oculto” por meio do uso de implementação explícita dos membros ICollection associados).

Existem pelo menos 3 maneiras que posso ver para resolver o problema:

  1. Implemente um IDictionary somente de leitura personalizada e envolva / delegate em um dictionary interno, como foi sugerido
  2. Retornar um ICollection> definido como somente leitura ou um IEnumerable> dependendo do uso do valor
  3. Clone o dictionary usando o construtor de cópias .ctor(IDictionary) e retorne uma cópia – assim o usuário fica livre para fazer o que quiser e não afeta o estado do object que hospeda o dictionary de origem . Observe que, se o dictionary que você está clonando contiver tipos de referência (não sequências de caracteres, como mostrado no exemplo), será necessário fazer a cópia “manualmente” e clonar também os tipos de referência.

Como um aparte; Ao expor collections, visam expor a menor interface possível – no exemplo, deve ser IDictionary, pois isso permite que você varie a implementação subjacente sem quebrar o contrato público que o tipo expõe.

Um dictionary somente leitura pode, até certo ponto, ser substituído por Func – normalmente, uso isso em uma API se eu quiser apenas que as pessoas realizem pesquisas; é simples e, em particular, é simples replace o backend, caso você deseje. Não fornece a lista de chaves, no entanto; se isso é importante depende do que você está fazendo.

Não, mas seria fácil fazer o seu próprio. IDictionary define uma propriedade IsReadOnly. Basta encapsular um dictionary e lançar uma NotSupportedException dos methods apropriados.

Nenhuma disponível na BCL. No entanto eu publiquei um ReadOnlyDictionary (chamado ImmutableMap) no meu projeto BCL Extras

Além de ser um dictionary totalmente imutável, ele suporta a produção de um object proxy que implementa o IDictionary e pode ser usado em qualquer lugar onde o IDictionary seja usado. Ele lançará uma exceção sempre que uma das APIs mutantes for chamada

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

Você poderia criar uma class que implementasse apenas uma implementação parcial do dictionary e ocultasse todas as funções de adicionar / remover / definir.

Use um dictionary internamente para o qual a class externa passa todas as solicitações.

No entanto, como seu dictionary provavelmente contém tipos de referência, não é possível impedir que o usuário defina valores nas classs mantidas pelo dictionary (a menos que essas classs sejam somente leitura)

Eu não acho que haja uma maneira fácil de fazer isso … se o seu dictionary é parte de uma class personalizada, você poderia alcançá-lo com um indexador:

 public class MyClass { private Dictionary _myDictionary; public string this[string index] { get { return _myDictionary[index]; } } } 

+1 excelente trabalho, Thomas. Eu levei ReadOnlyDictionary um passo adiante.

Assim como a solução de Dale, eu queria remover Add() , Clear() , Remove() , etc do IntelliSense. Mas eu queria que meus objects derivados implementassem IDictionary .

Além disso, gostaria que o seguinte código fosse quebrado: (Novamente, a solução de Dale faz isso também)

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

A linha Add () resulta em:

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

O chamador ainda pode convertê-lo para IDictionary , mas o NotSupportedException será gerado se você tentar usar os membros que não são somente leitura (da solução de Thomas).

Enfim, aqui está a minha solução para qualquer um que também quisesse isso:

 namespace System.Collections.Generic { public class ReadOnlyDictionary : IDictionary { const string READ_ONLY_ERROR_MESSAGE = "This dictionary is read-only"; protected IDictionary _Dictionary; public ReadOnlyDictionary() { _Dictionary = new Dictionary(); } public ReadOnlyDictionary(IDictionary dictionary) { _Dictionary = dictionary; } public bool ContainsKey(TKey key) { return _Dictionary.ContainsKey(key); } public ICollection Keys { get { return _Dictionary.Keys; } } public bool TryGetValue(TKey key, out TValue value) { return _Dictionary.TryGetValue(key, out value); } public ICollection 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 item) { return _Dictionary.Contains(item); } public void CopyTo(KeyValuePair[] array, int arrayIndex) { _Dictionary.CopyTo(array, arrayIndex); } public int Count { get { return _Dictionary.Count; } } public bool IsReadOnly { get { return true; } } public IEnumerator> GetEnumerator() { return _Dictionary.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return (_Dictionary as IEnumerable).GetEnumerator(); } void IDictionary.Add(TKey key, TValue value) { throw new NotSupportedException(READ_ONLY_ERROR_MESSAGE); } bool IDictionary.Remove(TKey key) { throw new NotSupportedException(READ_ONLY_ERROR_MESSAGE); } void ICollection>.Add(KeyValuePair item) { throw new NotSupportedException(READ_ONLY_ERROR_MESSAGE); } void ICollection>.Clear() { throw new NotSupportedException(READ_ONLY_ERROR_MESSAGE); } bool ICollection>.Remove(KeyValuePair item) { throw new NotSupportedException(READ_ONLY_ERROR_MESSAGE); } } } 

Agora, há collections imutáveis ​​da Microsoft ( System.Collections.Immutable ). Obtê-los via NuGet .

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

Esta é uma solução ruim, veja abaixo.

Para aqueles que ainda usam o .NET 4.0 ou anterior, eu tenho uma class que funciona como a da resposta aceita, mas é muito mais curta. Ele estende o object Dictionary existente, substituindo (na verdade, ocultando) certos membros para que eles lançem uma exceção quando chamados.

Se o chamador tentar chamar Add, Remove ou alguma outra operação de mutação que o dictionary interno possui, o compilador lançará um erro. Eu uso os atributos obsoletos para aumentar esses erros de compilador. Dessa forma, você pode replace um dictionary por este ReadOnlyDictionary e ver imediatamente onde os problemas podem estar sem precisar executar o aplicativo e aguardar exceções em tempo de execução.

Dê uma olhada:

 public class ReadOnlyException : Exception { } public class ReadOnlyDictionary : Dictionary { public ReadOnlyDictionary(IDictionary dictionary) : base(dictionary) { } public ReadOnlyDictionary(IDictionary dictionary, IEqualityComparer 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 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 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(); } } 

Esta solução tem um problema apontado por @supercat ilustrado aqui:

 var dict = new Dictionary { { 1, "one" }, { 2, "two" }, { 3, "three" }, }; var rodict = new ReadOnlyDictionary(dict); var rwdict = rodict as Dictionary; rwdict.Add(4, "four"); foreach (var item in rodict) { Console.WriteLine("{0}, {1}", item.Key, item.Value); } 

Em vez de dar um erro de tempo de compilation como eu esperava, ou uma exceção de tempo de execução como eu esperava, esse código é executado sem erros. Imprime quatro números. Isso faz com que meu ReadOnlyDictionary um ReadWriteDictionary.