O C# tem como me fornecer um dicionário imutável?
-
09-06-2019 - |
Pergunta
Existe algo incorporado nas principais bibliotecas C# que possa me fornecer um dicionário imutável?
Algo na linha de Java:
Collections.unmodifiableMap(myMap);
E só para esclarecer, não pretendo impedir que as próprias chaves/valores sejam alteradas, apenas a estrutura do Dicionário.Eu quero algo que falhe rápido e alto se algum dos métodos mutadores do IDictionary for chamado (Add, Remove, Clear
).
Solução
Não, mas um wrapper é bastante trivial:
public class ReadOnlyDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
IDictionary<TKey, TValue> _dict;
public ReadOnlyDictionary(IDictionary<TKey, TValue> backingDict)
{
_dict = backingDict;
}
public void Add(TKey key, TValue value)
{
throw new InvalidOperationException();
}
public bool ContainsKey(TKey key)
{
return _dict.ContainsKey(key);
}
public ICollection<TKey> Keys
{
get { return _dict.Keys; }
}
public bool Remove(TKey key)
{
throw new InvalidOperationException();
}
public bool TryGetValue(TKey key, out TValue value)
{
return _dict.TryGetValue(key, out value);
}
public ICollection<TValue> Values
{
get { return _dict.Values; }
}
public TValue this[TKey key]
{
get { return _dict[key]; }
set { throw new InvalidOperationException(); }
}
public void Add(KeyValuePair<TKey, TValue> item)
{
throw new InvalidOperationException();
}
public void Clear()
{
throw new InvalidOperationException();
}
public bool Contains(KeyValuePair<TKey, TValue> item)
{
return _dict.Contains(item);
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
_dict.CopyTo(array, arrayIndex);
}
public int Count
{
get { return _dict.Count; }
}
public bool IsReadOnly
{
get { return true; }
}
public bool Remove(KeyValuePair<TKey, TValue> item)
{
throw new InvalidOperationException();
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return _dict.GetEnumerator();
}
System.Collections.IEnumerator
System.Collections.IEnumerable.GetEnumerator()
{
return ((System.Collections.IEnumerable)_dict).GetEnumerator();
}
}
Obviamente, você pode alterar o setter this[] acima se quiser permitir a modificação de valores.
Outras dicas
Pelo que eu sei, não existe.Mas pode ser que você possa copiar algum código (e aprender muito) destes artigos:
Imutabilidade em C# Parte Um:Tipos de imutabilidade
Imutabilidade em C# Parte Dois:Uma pilha imutável simples
Imutabilidade em C# Parte Três:Uma pilha imutável covariante
Imutabilidade em C# Parte Quatro:Uma fila imutável
Imutabilidade em C# Parte Seis:Uma árvore binária simples
Imutabilidade em C# Parte Sete:Mais sobre árvores binárias
Imutabilidade em C# Parte Oito:Ainda mais sobre árvores binárias
Imutabilidade em C# Parte Nove:Acadêmico?Além da minha implementação de árvore AVL
Imutabilidade em C# Parte 10:Uma fila dupla
Imutabilidade em C# Parte Onze:Uma fila dupla funcional
Com o lançamento do .NET 4.5, há um novo Dicionário somente leitura aula.Você simplesmente passa um IDictionary
ao construtor para criar o dicionário imutável.
Aqui é um método de extensão útil que pode ser usado para simplificar a criação do dicionário somente leitura.
Eu não acho.Existe uma maneira de criar uma lista somente leitura e uma coleção somente leitura, mas não acho que exista um dicionário somente leitura integrado.System.ServiceModel possui uma implementação ReadOnlyDictinoary, mas é interna.Provavelmente não seria muito difícil copiá-lo, usando o Reflector, ou simplesmente criar o seu próprio do zero.Basicamente, envolve um dicionário e é lançado quando um modificador é chamado.
Adicionando em resposta do DBKK, eu queria poder usar um inicializador de objeto ao criar meu ReadOnlyDictionary pela primeira vez.Fiz as seguintes modificações:
private readonly int _finalCount;
/// <summary>
/// Takes a count of how many key-value pairs should be allowed.
/// Dictionary can be modified to add up to that many pairs, but no
/// pair can be modified or removed after it is added. Intended to be
/// used with an object initializer.
/// </summary>
/// <param name="count"></param>
public ReadOnlyDictionary(int count)
{
_dict = new SortedDictionary<TKey, TValue>();
_finalCount = count;
}
/// <summary>
/// To allow object initializers, this will allow the dictionary to be
/// added onto up to a certain number, specifically the count set in
/// one of the constructors.
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
public void Add(TKey key, TValue value)
{
if (_dict.Keys.Count < _finalCount)
{
_dict.Add(key, value);
}
else
{
throw new InvalidOperationException(
"Cannot add pair <" + key + ", " + value + "> because " +
"maximum final count " + _finalCount + " has been reached"
);
}
}
Agora posso usar a classe assim:
ReadOnlyDictionary<string, string> Fields =
new ReadOnlyDictionary<string, string>(2)
{
{"hey", "now"},
{"you", "there"}
};
O código aberto PowerCollections biblioteca inclui um wrapper de dicionário somente leitura (bem como wrappers somente leitura para praticamente todo o resto), acessível por meio de um arquivo estático ReadOnly()
método no Algorithms
aula.
Uma solução alternativa pode ser lançar uma nova lista de KeyValuePair do Dicionário para manter o original inalterado.
var dict = new Dictionary<string, string>();
dict.Add("Hello", "World");
dict.Add("The", "Quick");
dict.Add("Brown", "Fox");
var dictCopy = dict.Select(
item => new KeyValuePair<string, string>(item.Key, item.Value));
// returns dictCopy;
Desta forma, o dicionário original não será modificado.
"Fora da caixa" não há como fazer isso.Você pode criar um derivando sua própria classe Dictionary e implementando as restrições necessárias.
Encontrei uma implementação de uma implementação Inmutável (não READONLY) de um AVLTree para C# aqui.
Uma árvore AVL tem custo logarítmico (não constante) em cada operação, mas ainda é rápida.
Desde o Linq, existe uma interface genérica Eu olho para cima.Leia mais em MSDN.
Portanto, para simplesmente obter um dicionário imutável, você pode chamar:
using System.Linq;
// (...)
var dictionary = new Dictionary<string, object>();
// (...)
var read_only = dictionary.ToLookup(kv => kv.Key, kv => kv.Value);
Você poderia tentar algo assim:
private readonly Dictionary<string, string> _someDictionary;
public IEnumerable<KeyValuePair<string, string>> SomeDictionary
{
get { return _someDictionary; }
}
Isso removeria o problema de mutabilidade em favor de seu chamador ter que convertê-lo em seu próprio dicionário:
foo.SomeDictionary.ToDictionary(kvp => kvp.Key);
...ou use uma operação de comparação na chave em vez de uma pesquisa de índice, por exemplo:
foo.SomeDictionary.First(kvp => kvp.Key == "SomeKey");
Em geral, é uma idéia muito melhor não distribuir nenhum dicionário em primeiro lugar (se você não PRECISA).
Em vez disso - crie um objeto de domínio com uma interface que não ofereça nenhum método modificando o dicionário (que embrulha).Em vez disso, oferece o método LookUp necessário que recupera o elemento do dicionário por chave (o bônus é que também o torna mais fácil de usar do que um dicionário).
public interface IMyDomainObjectDictionary
{
IMyDomainObject GetMyDomainObject(string key);
}
internal class MyDomainObjectDictionary : IMyDomainObjectDictionary
{
public IDictionary<string, IMyDomainObject> _myDictionary { get; set; }
public IMyDomainObject GetMyDomainObject(string key) {.._myDictionary .TryGetValue..etc...};
}
Há também outra alternativa, como descrevi em:
http://www.softwarerockstar.com/2010/10/readonlydictionary-tkey-tvalue/
Essencialmente, é uma subclasse de ReadOnlyCollection>, que realiza o trabalho de maneira mais elegante.Elegante no sentido de que possui suporte em tempo de compilação para tornar o Dicionário somente leitura, em vez de lançar exceções de métodos que modificam os itens dentro dele.