2010-01-14 27 views
6

Un diccionario no se puede cambiar si lo estamos recorriendo en .Net. Pero ¿por qué el valor en un diccionario es solo de lectura? ¿Alguien tiene alguna idea? Por qué el equipo de .Net decidió no cambiar el valor al pasar por encima del diccionario. Puedo entender si la clave no se puede cambiar, pero ¿por qué valor? Además, si usa LINQ y recupera las claves en forma de Ienumerable, ¿se puede cambiar el valor? ¿la carga lenta tiene un rol que jugar?Valor del diccionario C#

+0

No estoy seguro de si quiere decir cambiar el valor devuelto por el enumerador (el objeto que le permite 'foreach' sobre las colecciones) o agregar/eliminar elementos mientras itera? – Skurmedel

Respuesta

10

¿Por qué el equipo BCL decidir sobre no permitir cambios en el valor cuando bucle sobre diccionario. Entiendo que la clave no se puede cambiar, pero ¿por qué no permitir cambios en el valor?

No puedo hablar definitivamente para el equipo que construyó el diccionario, pero puedo hacer algunas conjeturas educadas .

En primer lugar, a menudo se dice que los buenos programas son estrictos con respecto a la corrección de sus resultados, pero son indulgentes con lo que aceptan. No creo que este sea un buen principio de diseño. Este es un mal principio diseño ya que permite a las personas que llaman con errores a tomar dependencias en indefinido y sin apoyo comportamiento. Por lo tanto, crea una carga de compatibilidad hacia atrás. (Ciertamente vemos esto en el mundo de los navegadores web, donde cada proveedor de navegadores es presionado para que sea compatible con la aceptación de HTML por parte de los demás navegadores).

Los componentes definen los contratos: dicen qué información aceptan como entrada, qué operaciones ellos apoyan, y qué resultados producen. Cuando acepta entradas que no están en su contrato, o admite operaciones que no están en su contrato, lo que está haciendo es básicamente hacer un contrato nuevo, un contrato que no está documentado, no es compatible, y podría estar roto una versión futura. Básicamente estás construyendo una bomba de tiempo y cuando suena, un cliente (que probablemente ni siquiera sabía que estaban haciendo algo sin soporte) se rompe, o el proveedor de componentes termina teniendo que mantener para siempre un contrato que no hicieron. de hecho, registrarse.

Por lo tanto, es un buen principio de diseño para hacer cumplir estrictamente su contrato. El contrato de IEnumerable en una colección es "es ilegal modificar la colección mientras se itera". Un implementador de este contrato puede elegir decir "bueno, sé que ciertas modificaciones son seguras, así que las permitiré", y bueno, de repente ya no está implementando el contrato. Está implementando un contrato diferente e indocumentado en el que las personas confiarán.

Es mejor simplemente hacer cumplir el contrato, incluso si eso no es necesario. De esta forma, en el futuro, tendrá la libertad de confiar en su contrato documentado sin preocuparse de que una persona que llama haya roto el contrato y se haya salido con la suya, y espera poder seguir haciéndolo para siempre.

Sería fácil diseñar un diccionario que permitiera la mutación de valores durante la iteración. Si lo hace, impedirá que los proveedores de componentes puedan apagar esa función, y no están obligados a proporcionarla, por lo que es mejor dar un error cuando alguien intenta que permitir que una persona que llama viole el contrato.

Segundo intento: el tipo de diccionario no está sellado y, por lo tanto, se puede ampliar. Los terceros podrían extender el diccionario de tal manera que sus invariantes se violarían si se cambiara un valor durante una enumeración. Supongamos, por ejemplo, que alguien extiende un diccionario de tal manera que se puede enumerar ordenado por el valor.

Cuando escribe un método que toma un diccionario y le hace algo, supone que la operación funcionará en todos los diccionarios, incluso en extensiones de terceros. Los diseñadores de un componente destinado a la extensión deben ser aún más cuidadosos de lo habitual para garantizar que el objeto cumpla su contrato, ya que terceros desconocidos pueden confiar en la ejecución de ese contrato. Dado que podría ser un diccionario que no admite admitir el cambio de un valor durante la iteración, la clase base tampoco debería soportarlo; hacer lo contrario es violar la capacidad de sustitución de las clases derivadas para las clases base.

Además, si utiliza LINQ y recupera las claves en forma de IEnumerable, ¿se puede cambiar el valor?

La regla es que un diccionario no puede cambiarse al iterar sobre él. Si usa LINQ para hacer la iteración o no es irrelevante; la iteración es iteración.

hace lazy loading ¿Tiene un rol que jugar?

Sure. Recuerde, definir una consulta LINQ no itera sobre nada; el resultado de una expresión de consulta es un objeto de consulta. Solo cuando itera sobre ese objeto, la iteración real ocurre sobre la colección. Cuando dice:

var bobs = from item in items where item.Name == "Bob" select item; 

no hay iteración aquí. No es hasta que diga

foreach(var bob in bobs) ... 

que la iteración sobre los artículos sucede.

+0

Gracias Eric por explicar de manera excelente. Tengo mi respuesta. – Prashant

4

Un KeyValuePair es una struct y las estructuras mutables son malas, por lo que se ha hecho de solo lectura.

Para cambiar algunos valores, puede recorrer los KeyValuePairs y almacenar todas las actualizaciones que desee realizar. Cuando haya terminado de iterar, puede recorrer su lista de actualizaciones y aplicarlas. Aquí está un ejemplo de cómo podría hacerlo (sin utilizar LINQ):

Dictionary<string, string> dict = new Dictionary<string,string>(); 
    dict["foo"] = "bar"; 
    dict["baz"] = "qux"; 

    List<KeyValuePair<string, string>> updates = new List<KeyValuePair<string,string>>(); 
    foreach (KeyValuePair<string, string> kvp in dict) 
    { 
     if (kvp.Key.Contains("o")) 
      updates.Add(new KeyValuePair<string, string>(kvp.Key, kvp.Value + "!")); 
    } 

    foreach (KeyValuePair<string, string> kvp in updates) 
    { 
     dict[kvp.Key] = kvp.Value; 
    } 
1

No estoy seguro de por qué, pero se podría evitar esta limitación mediante una referencia indirecta valores. Por ejemplo, se podría crear una clase que simplemente hace referencia a otra de esta manera:

class StrongRef<ObjectType> 
{ 
    public StrongRef(ObjectType actualObject) 
    { 
     this.actualObject = actualObject; 
    } 

    public ObjectType ActualObject 
    { 
     get { return this.actualObject; } 
     set { this.actualObject = value; } 
    } 

    private ObjectType actualObject; 
} 

Entonces, en vez alterando los valores de la dicitonary que podría alterar la referencia indirecta. (He oído de buena fuente que la siguiente rutina es usada por Peter azul para sustituir a sus perros muertos con animales vivos que buscan idénticos.)

public void ReplaceDeadDogs() 
{ 
    foreach (KeyValuePair<string, StrongRef<Dog>> namedDog in dogsByName) 
    { 
     string name = namedDog.Key; 
     StrongRef dogRef = namedDog.Value; 

     // update the indirect reference 
     dogRef.ActualObject = PurchaseNewDog(); 
    } 
} 

Otras alternativas serían para registrar los cambios necesarios en un segundo diccionario al iterar y luego aplicar estos cambios después (al iterar el segundo dicitonario), o construir un diccionario de reemplazo completo mientras itera y usa esto en lugar del original después de completar el ciclo.

0

Desde el this Microsoft support reply (basado en la Hashtable pero lo mismo se aplica)

La clave aquí es que los encuestadores son diseñado para proporcionar una instantánea de toda la colección , pero por razones de rendimiento no lo hacen copie toda la colección a otra matriz temporal o algo por el estilo. En su lugar, usan la colección en vivo y lanzan una excepción si detectan que alguien ha cambiado la colección. Sí, hace este tipo de código más difícil de escribir ...

Cuestiones relacionadas