2010-02-14 23 views
56
Dictionary<string,double> myDict = new Dictionary(); 
//... 
foreach (KeyValuePair<string,double> kvp in myDict) 
{ 
    kvp.Value = Math.Round(kvp.Value, 3); 
}

Me aparece un error: "propiedad o indizador System.Collections.Generic.KeyValuePair.Value 'no se puede asignar a - que es de sólo lectura."
¿Cómo puedo iterar a través del myDict y cambiar los valores?¿Cómo iterar a través de Diccionario y cambiar valores?

+1

Si la adición de una gran cantidad de objetos de referencia es aceptable desde una perspectiva de rendimiento (direccionamiento indirecto y más presión de gc) _y_ usted tiene control sobre el diccionario y luego crear un cuadro para sus valores resolvería el problema, por ejemplo: 'class Box {public T en caja; } 'y luego use' Dictionary > 'que luego puede" modificar "en el foreach algo así como:' kvp.Value.boxed = 123.456; '. No dice que es el "mejor" enfoque, pero tiene su parte de usos. – AnorZaken

Respuesta

88

Según MSDN:

The foreach statement is a wrapper around the enumerator, which allows only reading from the collection, not writing to it.

Utilice esta:

var dictionary = new Dictionary<string, double>(); 
// TODO Populate your dictionary here 
var keys = new List<string>(dictionary.Keys); 
foreach (string key in keys) 
{ 
    dictionary[key] = Math.Round(dictionary[key], 3); 
} 
+3

He probado su ejemplo en .NET 2 y 3.5 y arroja 'La colección fue excepción modificada'. Ver: http://stackoverflow.com/questions/1562729/why-cant-we-change-values-of-a-dictionary-while-enumerating-its-keys ¿Ha cambiado esto en .NET 4 o no ha probado su ¿ejemplo? – Ash

+0

No funciona en .NET 4 – foson

+2

Qué vergonzoso: dejé fuera la parte donde se llena la lista. Corregido ahora. La idea aquí es que puede cambiar el valor de la entrada del diccionario, simplemente no es su referencia. –

8

No debe cambiar el diccionario mientras lo itera, de lo contrario obtendrá una excepción.

Así que primero copiar los pares clave-valor a una lista temporal y luego iterar a través de esta lista temperatura y luego cambiar su diccionario:

Dictionary<string, double> myDict = new Dictionary<string, double>(); 

// a few values to play with 
myDict["a"] = 2.200001; 
myDict["b"] = 77777.3333; 
myDict["c"] = 2.3459999999; 

// prepare the temp list 
List<KeyValuePair<string, double>> list = new List<KeyValuePair<string, double>>(myDict); 

// iterate through the list and then change the dictionary object 
foreach (KeyValuePair<string, double> kvp in list) 
{ 
    myDict[kvp.Key] = Math.Round(kvp.Value, 3); 
} 


// print the output 
foreach (var pair in myDict) 
{ 
    Console.WriteLine(pair.Key + " = " + pair.Value); 
} 

// uncomment if needed 
// Console.ReadLine(); 

salida (en mi máquina):

a = 2.2
b = 77777.333
c = 2.346

Nota: en términos de rendimiento, esta solución es un poco mejor que las soluciones publicadas actualmente, ya que el valor ya está asignado con la clave, y no hay necesidad de volver a buscarlo desde el objeto del diccionario.

+1

Ese es probablemente el enfoque que seguiré, pero me encantaría saber la sobrecarga de copiar el diccionario completo. – Alberto

-2

Pasa por las teclas en el diccionario, no en los KeyValuePairs.

Dictionary<string, double> myDict = new Dictionary<string, double>(); 
//... 
foreach (string key in myDict.Keys) 
{ 
    myDict[key] = Math.Round(myDict[key], 3); 
} 
+8

Sorprendentemente arroja una excepción "* Colección modificada ... *". – Slauma

+0

@Slauma De hecho * sorprendentemente *, ya que este cambio no es un cambio estructural, por lo tanto, no sería necesario tener el proyecto de enumerador. –

0

Mientras que la iteración en el diccionario no es directamente posible debido a que obtiene una excepción (como Ron ya se dijo), se no necesita usar una lista temporal para resolver el problema.

utilizar en su lugar no la foreach, pero un bucle for para iterar a través del diccionario y cambiar los valores con acceso indexado:

Dictionary<string, double> myDict = new Dictionary<string,double>(); 
//...  
for(int i = 0; i < myDict.Count; i++) { 
    myDict[myDict.ElementAt(i).Key] = Math.Round(myDict.ElementAt(i).Value, 3); 
} 
+0

Aunque esto funcionará, 'Enumerable.ElementAt()' - que es el método de extensión que está utilizando - es una operación ['O (n)' para non-'IList <> 's] (http: // referencesource. microsoft.com/#System.Core/System/Linq/Enumerable.cs,7db56d44563d8761,references). –

+0

Dirán explícitamente lo que dice Eugene Beresovsky. Esto termina siendo al menos O (n^2). Entonces esta es una muy mala solución. –

1

Una solución sería poner las llaves en una lista (o otra colección) de antemano y iterar a través de ellos mientras se cambia el diccionario:

Dictionary<string, double> dictionary = new Dictionary<string, double>(); 

// Populate it 
List<string> keys = new List<string>(dictionary.Keys); 

foreach (string key in keys) 
{ 
    dictionary[key] = Math.Round(dictionary[key], 3); 
} 
26

Para los programadores perezosos:

Dictionary<string, double> dictionary = new Dictionary<string, double>(); 
foreach (var key in dictionary.Keys.ToList()) 
{ 
    dictionary[key] = Math.Round(dictionary[key], 3); 
} 
+2

Como estoy codificando ahora con .NET 4.5, el método 'ToList()' no está disponible, pero el miembro 'Keys' es iterable por lo que' .ToList() 'no es necesario. –

+4

@MikeC ver comentario en http://stackoverflow.com/a/2260462/1037948 - no se puede enumerar directamente en las teclas, '.ToList()' es un "truco" para evitar que – drzaus

+3

ToList () es un método de extensión LINQ. Debería estar disponible si agrega "using System.Linq"; a sus declaraciones de uso. – bcwhims

1

me di cuenta de que manera más rápida (en este momento) iterar sobre Dictionary con Modificar es:

//Just a dumb class 
class Test<T> 
{ 
    public T value; 

    public Test() { } 
    public Test(T v) { value = v; } 
} 

Dictionary<int, Test<object>> dic = new Dictionary<int, Test<object>>(); 
//Init dictionary 
foreach (KeyValuePair<int, Test> pair in dic) 
{ 
    pair.Value.value = TheObject;//Modify 
} 

VS

List<int> keys = new List<int>(dic.Keys); //This is fast operation 
foreach (int key in keys) 
{ 
    dic[key] = TheObject; 
} 

Primero uno tarda aproximadamente 2.2s 4.5s y segundo (diccionario probado tamaño de 1000 y repetido 10k veces, cambiar el tamaño del diccionario a 10 no cambió las proporciones). Además, no fue un gran problema obtener la lista de claves, el valor del diccionario [clave] es simplemente lento, la iteración integrada es VS. Además, si desea aún más velocidad, utilice la clase de tipo codificado a tonto ("Prueba"), con eso obtengo aproximadamente 1.85s (con el código fijo para "objeto").

EDIT:

Ana ha fijado la misma solución antes: https://stackoverflow.com/a/6515474/766304

0

pasado algún tiempo, pero tal vez alguien está interesado en él:

yourDict = yourDict.ToDictionary(kv => k.Key, kv => Math.Round(kv.Value, 3)) 
Cuestiones relacionadas