Est-ce que C# a un moyen de me donner un dictionnaire immuable ?
-
09-06-2019 - |
Question
Existe-t-il quelque chose d'intégré aux bibliothèques principales de C# qui puisse me donner un dictionnaire immuable ?
Quelque chose dans le sens de Java:
Collections.unmodifiableMap(myMap);
Et juste pour clarifier, je ne cherche pas à empêcher la modification des clés/valeurs elles-mêmes, mais simplement de la structure du dictionnaire.Je veux quelque chose qui échoue rapidement et fort si l'une des méthodes de mutation d'IDictionary est appelée (Add, Remove, Clear
).
La solution
Non, mais un wrapper, c'est plutôt 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();
}
}
Évidemment, vous pouvez modifier le paramètre this[] ci-dessus si vous souhaitez autoriser la modification des valeurs.
Autres conseils
Pour autant que je sache, il n'y en a pas.Mais peut-être pouvez-vous copier du code (et en apprendre beaucoup) à partir de ces articles :
Immuabilité en C# Première partie :Types d'immuabilité
Immuabilité en C# Deuxième partie :Une simple pile immuable
Immuabilité en C# Troisième partie :Une pile immuable covariante
Immuabilité en C#, quatrième partie :Une file d'attente immuable
Immuabilité en C#, sixième partie :Un arbre binaire simple
Immuabilité en C# Partie Sept :En savoir plus sur les arbres binaires
Immuabilité en C# Partie Huit :Encore plus sur les arbres binaires
Immuabilité en C# Partie Neuf :Académique?Plus mon implémentation de l'arborescence AVL
Immuabilité en C# Partie 10 :Une file d'attente à double extrémité
Immuabilité en C# Partie Onze :Une file d'attente à double extrémité fonctionnelle
Avec la sortie de .NET 4.5, il existe un nouveau Dictionnaire en lecture seule classe.Vous passez simplement un IDictionary
au constructeur pour créer le dictionnaire immuable.
Ici est une méthode d'extension utile qui peut être utilisée pour simplifier la création du dictionnaire en lecture seule.
Je ne pense pas.Il existe un moyen de créer une liste en lecture seule et une collection en lecture seule, mais je ne pense pas qu'il existe un dictionnaire intégré en lecture seule.System.ServiceModel a une implémentation ReadOnlyDictinoary, mais elle est interne.Il ne serait probablement pas trop difficile de le copier, en utilisant Reflector, ou simplement de créer le vôtre à partir de zéro.Il enveloppe essentiellement un dictionnaire et le lance lorsqu'un mutateur est appelé.
Ajout sur La réponse de dbkk, je voulais pouvoir utiliser un initialiseur d'objet lors de la première création de mon ReadOnlyDictionary.J'ai apporté les modifications suivantes :
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"
);
}
}
Maintenant, je peux utiliser la classe comme ceci :
ReadOnlyDictionary<string, string> Fields =
new ReadOnlyDictionary<string, string>(2)
{
{"hey", "now"},
{"you", "there"}
};
L'open source PowerCollections La bibliothèque comprend un wrapper de dictionnaire en lecture seule (ainsi que des wrappers en lecture seule pour à peu près tout le reste), accessible via un fichier statique ReadOnly()
méthode sur le Algorithms
classe.
Une solution de contournement pourrait consister à lancer une nouvelle liste de KeyValuePair à partir du dictionnaire pour conserver l'original non modifié.
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;
De cette façon, le dictionnaire original ne sera pas modifié.
"Hors de la boîte", il n'y a aucun moyen de le faire.Vous pouvez en créer une en dérivant votre propre classe Dictionary et en implémentant les restrictions dont vous avez besoin.
J'ai trouvé ici une implémentation d'une implémentation inmuable (pas READONLY) d'un AVLTree pour C#.
Un arbre AVL a un coût logarithmique (non constant) pour chaque opération, mais reste rapide.
Depuis Linq, il existe une interface générique Je regarde.Lire la suite dans MSDN.
Par conséquent, pour obtenir simplement un dictionnaire immuable, vous pouvez appeler :
using System.Linq;
// (...)
var dictionary = new Dictionary<string, object>();
// (...)
var read_only = dictionary.ToLookup(kv => kv.Key, kv => kv.Value);
Vous pourriez essayer quelque chose comme ceci :
private readonly Dictionary<string, string> _someDictionary;
public IEnumerable<KeyValuePair<string, string>> SomeDictionary
{
get { return _someDictionary; }
}
Cela supprimerait le problème de mutabilité et obligerait votre appelant à le convertir dans son propre dictionnaire :
foo.SomeDictionary.ToDictionary(kvp => kvp.Key);
...ou utilisez une opération de comparaison sur la clé plutôt qu'une recherche d'index, par exemple :
foo.SomeDictionary.First(kvp => kvp.Key == "SomeKey");
En général, il est préférable de ne pas faire circuler de dictionnaire en premier lieu (si vous n'êtes pas OBLIGATOIRE de le faire).
Au lieu de cela, créez un objet de domaine avec une interface qui n'offre aucune méthode modification le dictionnaire (qu'il enveloppe).Au lieu de cela, nous proposons la méthode LookUp requise qui récupère l'élément du dictionnaire par clé (le bonus est qu'elle le rend également plus facile à utiliser qu'un dictionnaire).
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...};
}
Il existe également une autre alternative, comme je l'ai décrit ici :
http://www.softwarerockstar.com/2010/10/readonlydictionary-tkey-tvalue/
Il s'agit essentiellement d'une sous-classe de ReadOnlyCollection>, qui effectue le travail de manière plus élégante.Élégant dans le sens où il prend en charge au moment de la compilation la création du dictionnaire en lecture seule plutôt que de lancer des exceptions aux méthodes qui modifient les éléments qu'il contient.