manage - ¿Hay un diccionario genérico de solo lectura disponible en.NET?
manage dictionary c# (14)
.NET 4.5
.NET Framework 4.5 BCL presenta ReadOnlyDictionary<TKey, TValue>
( source ).
Como .NET Framework 4.5 BCL no incluye AsReadOnly
para diccionarios, tendrá que escribir el suyo (si lo desea). Sería algo como lo siguiente, cuya simplicidad tal vez destaca por qué no era una prioridad para .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 y abajo
Antes de .NET 4.5, no existe una clase .NET framework que envuelva un Dictionary<TKey, TValue>
como ReadOnlyCollection cierra una List . Sin embargo, no es difícil crear uno.
Aquí hay un ejemplo : hay muchos otros si busca Google ReadOnlyDictionary .
Estoy devolviendo una referencia a un diccionario en mi propiedad de solo lectura. ¿Cómo evito que los consumidores cambien mis datos? Si esto fuera un IList
, simplemente podría devolverlo AsReadOnly
. ¿Hay algo similar que pueda hacer con un diccionario?
Private _mydictionary As Dictionary(Of String, String)
Public ReadOnly Property MyDictionary() As Dictionary(Of String, String)
Get
Return _mydictionary
End Get
End Property
+1 Buen trabajo, Thomas. Llevé ReadOnlyDictionary un paso más allá.
Al igual que la solución de Dale, quería eliminar Add()
, Clear()
, Remove()
, etc. de IntelliSense. Pero quería que mis objetos derivados implementaran IDictionary<TKey, TValue>
.
Además, me gustaría que se rompa el siguiente código: (De nuevo, la solución de Dale también lo hace)
ReadOnlyDictionary<int, int> test = new ReadOnlyDictionary<int,int>(new Dictionary<int, int> { { 1, 1} });
test.Add(2, 1); //CS1061
La línea Add () genera:
error CS1061: ''System.Collections.Generic.ReadOnlyDictionary<int,int>'' does not contain a definition for ''Add'' and no extension method ''Add'' accepting a first argument
La persona que llama aún puede IDictionary<TKey, TValue>
a IDictionary<TKey, TValue>
, pero la NotSupportedException
generará si intenta utilizar los miembros que no son de solo lectura (de la solución de Thomas).
De todos modos, aquí está mi solución para cualquiera que también quiera esto:
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);
}
}
}
Ahora, hay colecciones inmutables de Microsoft ( System.Collections.Immutable
). Consíguelos a través de NuGet .
Aquí hay una implementación simple que envuelve un diccionario:
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");
}
}
Esta es una mala solución, mira en la parte inferior.
Para aquellos que todavía usan .NET 4.0 o anterior, tengo una clase que funciona igual que la de la respuesta aceptada, pero es mucho más corta. Extiende el objeto de diccionario existente, sobrescribiendo (en realidad, ocultando) a ciertos miembros para que emitan una excepción cuando se los llama.
Si el llamante intenta llamar a Agregar, Eliminar o alguna otra operación de mutación que tenga el diccionario incorporado, el compilador emitirá un error. Uso los atributos obsoletos para generar estos errores de compilación. De esta forma, puede reemplazar un diccionario con este ReadOnlyDictionary e inmediatamente ver dónde podría haber problemas sin tener que ejecutar su aplicación y esperar excepciones en tiempo de ejecución.
Echar un vistazo:
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(); }
}
Esta solución tiene un problema señalado por @supercat ilustrado aquí:
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);
}
En lugar de dar un error en tiempo de compilación como esperaba, o una excepción en tiempo de ejecución como esperaba, este código se ejecuta sin error. Imprime cuatro números. Eso hace que ReadOnlyDictionary sea ReadWriteDictionary.
IsReadOnly en IDictionary<TKey,TValue>
se hereda de ICollection<T>
( IDictionary<TKey,TValue>
amplía ICollection<T>
como ICollection<KeyValuePair<TKey,TValue>>
). No se usa ni se implementa de ninguna manera (y de hecho está "oculto" mediante el uso de implementar explícitamente los miembros ICollection<T>
asociados).
Hay al menos 3 formas en que puedo ver para resolver el problema:
- Implemente un
IDictionary<TKey, TValue>
personalizado de soloIDictionary<TKey, TValue>
y wrap / delegate en un diccionario interno como se ha sugerido - Devuelve un
ICollection<KeyValuePair<TKey, TValue>>
configurado como de solo lectura o unIEnumerable<KeyValuePair<TKey, TValue>>
según el uso del valor - Clone el diccionario utilizando el constructor de copia
.ctor(IDictionary<TKey, TValue>)
y devuelva una copia; de este modo, el usuario puede hacer lo que quiera y no afecta el estado del objeto que aloja el diccionario de origen. . Tenga en cuenta que si el diccionario que está clonando contiene tipos de referencia (no cadenas como se muestra en el ejemplo), tendrá que hacer la copia "manualmente" y clonar también los tipos de referencia.
Como un aparte; al exponer colecciones, pretenden exponer la interfaz más pequeña posible; en el caso de ejemplo, debería ser IDictionary ya que esto permite variar la implementación subyacente sin romper el contrato público que expone el tipo.
Ninguno disponible en el BCL. Sin embargo, publiqué un ReadOnlyDictionary (llamado ImmutableMap) en mi BCL Extras Project
Además de ser un diccionario totalmente inmutable, es compatible con la producción de un objeto proxy que implemente IDictionary y se pueda utilizar en cualquier lugar donde se tome IDictionary. Lanzará una excepción cada vez que se llame a una de las API mutantes
void Example() {
var map = ImmutableMap.Create<int,string>();
map = map.Add(42,"foobar");
IDictionary<int,string> dictionary = CollectionUtility.ToIDictionary(map);
}
No creo que haya una manera fácil de hacerlo ... si tu diccionario es parte de una clase personalizada, puedes lograrlo con un indexador:
public class MyClass
{
private Dictionary<string, string> _myDictionary;
public string this[string index]
{
get { return _myDictionary[index]; }
}
}
No, pero sería fácil hacer el suyo. IDictionary define una propiedad IsReadOnly. Simplemente ajuste un diccionario y genere una NotSupportedException de los métodos apropiados.
Puede crear una clase que solo implemente una implementación parcial del diccionario y oculte todas las funciones de agregar / eliminar / establecer.
Use un diccionario interno al que la clase externa pasa todas las solicitudes.
Sin embargo, dado que es probable que su diccionario tenga tipos de referencia, no hay manera de evitar que el usuario establezca valores en las clases que contiene el diccionario (a menos que esas clases sean solo de lectura).
Se anunció en la reciente conferencia BUILD que desde .NET 4.5, se incluye la interfaz System.Collections.Generic.IReadOnlyDictionary<TKey,TValue>
. La prueba está here (Mono) y here (Microsoft);)
No estoy seguro si también se incluye ReadOnlyDictionary
, pero al menos con la interfaz no debería ser difícil crear ahora una implementación que expone una interfaz genérica .NET oficial :)
Siéntase libre de usar mi envoltorio simple. NO implementa IDictionary, por lo que no tiene que arrojar excepciones en tiempo de ejecución para los métodos de diccionario que cambiarían el diccionario. Los métodos de cambio simplemente no están ahí. Hice mi propia interfaz llamada 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(); }
}
Un diccionario de solo lectura puede ser sustituido hasta cierto punto por Func<TKey, TValue>
- Usualmente lo uso en una API si solo quiero que las personas realicen búsquedas; es simple, y en particular, es simple reemplazar el backend si alguna vez lo desea. Sin embargo, no proporciona la lista de claves; si eso importa depende de lo que estás haciendo.
public IEnumerable<KeyValuePair<string, string>> MyDictionary()
{
foreach(KeyValuePair<string, string> item in _mydictionary)
yield return item;
}