Pregunta

¿Hay algo integrado en las bibliotecas principales de C# que pueda proporcionarme un diccionario inmutable?

Algo parecido a Java:

Collections.unmodifiableMap(myMap);

Y solo para aclarar, no estoy buscando evitar que se cambien las claves/valores en sí, solo la estructura del Diccionario.Quiero algo que falle rápido y ruidosamente si se llama a alguno de los métodos mutadores de IDictionary (Add, Remove, Clear).

¿Fue útil?

Solución

No, pero un contenedor es 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, puede cambiar el configurador this[] anterior si desea permitir la modificación de valores.

Otros consejos

Con el lanzamiento de .NET 4.5, hay una nueva Diccionario de sólo lectura clase.Simplemente pasas un IDictionary al constructor para crear el diccionario inmutable.

Aquí es un método de extensión útil que se puede utilizar para simplificar la creación del diccionario de solo lectura.

No me parece.Hay una manera de crear una Lista de solo lectura y una Colección de solo lectura, pero no creo que haya un Diccionario de solo lectura integrado.System.ServiceModel tiene una implementación ReadOnlyDictinoary, pero es interna.Probablemente no sería demasiado difícil copiarlo usando Reflector o simplemente crear uno propio desde cero.Básicamente envuelve un diccionario y lo lanza cuando se llama a un mutador.

Añadiendo a La respuesta de dbkk, quería poder utilizar un inicializador de objetos al crear mi ReadOnlyDictionary por primera vez.Hice las siguientes modificaciones:

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

Ahora puedo usar la clase así:

ReadOnlyDictionary<string, string> Fields =
    new ReadOnlyDictionary<string, string>(2)
        {
            {"hey", "now"},
            {"you", "there"}
        };

El código abierto Colecciones de poder La biblioteca incluye un contenedor de diccionario de solo lectura (así como contenedores de solo lectura para casi todo lo demás), accesible a través de un archivo estático. ReadOnly() método en el Algorithms clase.

Una solución alternativa podría ser generar una nueva lista de KeyValuePair del Diccionario para mantener el original sin modificar.

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 esta forma el diccionario original no se modificará.

"Fuera de la caja" no hay una manera de hacer esto.Puede crear uno derivando su propia clase de Diccionario e implementando las restricciones que necesite.

Encontré una implementación de una implementación inmutable (no READONLY) de un AVLTree para C# aquí.

Un árbol AVL tiene un costo logarítmico (no constante) en cada operación, pero sigue siendo rápido.

http://csharpfeeds.com/post/7512/Immutability_in_Csharp_Part_Nine_Academic_Plus_my_AVL_tree_implementation.aspx

Desde Linq, existe una interfaz genérica. Miro hacia arriba.Leer más en MSDN.

Por lo tanto, para obtener simplemente un diccionario inmutable, puedes llamar:

using System.Linq;
// (...)
var dictionary = new Dictionary<string, object>();
// (...)
var read_only = dictionary.ToLookup(kv => kv.Key, kv => kv.Value);

Podrías probar algo como esto:

private readonly Dictionary<string, string> _someDictionary;

public IEnumerable<KeyValuePair<string, string>> SomeDictionary
{
    get { return _someDictionary; }
}

Esto eliminaría el problema de mutabilidad a favor de que la persona que llama tenga que convertirlo a su propio diccionario:

foo.SomeDictionary.ToDictionary(kvp => kvp.Key);

...o utilice una operación de comparación en la clave en lugar de una búsqueda de índice, por ejemplo:

foo.SomeDictionary.First(kvp => kvp.Key == "SomeKey");

En general, es una idea mucho mejor no pasar ningún diccionario en primer lugar (si NO ES NECESARIO).

En su lugar, cree un objeto de dominio con una interfaz que no ofrezca ningún método. modificando el diccionario (que envuelve).En lugar de ofrecer el método de búsqueda requerido que recupera elementos del diccionario por clave (la ventaja es que también lo hace más fácil de usar que un diccionario).

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...};
}

También hay otra alternativa como la que he descrito en:

http://www.softwarerockstar.com/2010/10/readonlydictionary-tkey-tvalue/

Esencialmente es una subclase de ReadOnlyCollection>, que realiza el trabajo de una manera más elegante.Elegante en el sentido de que tiene soporte en tiempo de compilación para hacer que el Diccionario sea de solo lectura en lugar de generar excepciones de métodos que modifican los elementos que contiene.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top