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

Tôi gặp lỗi: "Thuộc tính hoặc chỉ mục" System.Collections.Generic.KeyValuePair.Value 'không thể được gán cho - chỉ đọc. "
Làm cách nào để tôi có thể lặp qua myDict và thay đổi giá trị?Làm cách nào để lặp qua từ điển và thay đổi giá trị?

+1

Nếu thêm rất nhiều đối tượng tham khảo được chấp nhận từ góc nhìn hiệu suất (indirection và gc-pressure) _and_ bạn có quyền kiểm soát từ điển sau đó tạo một hộp cho giá trị của bạn sẽ giải quyết được vấn đề, ví dụ: 'class Box {công khai T đóng hộp; } 'và sau đó sử dụng một chuỗi' Dictionary <, Box > 'mà sau đó bạn có thể" sửa đổi "trong một thứ gì đó như:' kvp.Value.boxed = 123.456; '. Không nói đó là cách tiếp cận "tốt nhất" nhưng nó có phần sử dụng của nó. – AnorZaken

Trả lời

88

Theo MSDN:

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

Sử dụng này:

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

Tôi đã thử nghiệm ví dụ của bạn trong .NET 2 và 3.5 và nó ném 'Bộ sưu tập đã được sửa đổi ngoại lệ'. Xem: http://stackoverflow.com/questions/1562729/why-cant-we-change-values-of-a-dictionary-while-enumerating-its-keys Điều này có thay đổi trong .NET 4 hay bạn không kiểm tra thí dụ? – Ash

+0

Không hoạt động trong .NET 4 hoặc – foson

+2

Làm thế nào xấu hổ - Tôi đã bỏ qua phần mà danh sách được điền. Đã sửa lỗi. Ý tưởng ở đây là bạn có thể thay đổi giá trị của mục nhập từ điển, không phải là tham chiếu của nó. –

8

Bạn không nên thay đổi từ điển trong khi lặp lại nó, nếu không bạn sẽ nhận được một ngoại lệ.

Vì vậy, đầu tiên sao chép các cặp khóa-giá trị vào danh sách tạm thời và sau đó lặp qua danh sách tạm thời này và sau đó thay đổi từ điển của bạn:

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(); 

đầu ra (trên máy tính của tôi):

a = 2.2
b = 77777.333
c = 2.346

Lưu ý: về mặt hiệu suất, giải pháp này tốt hơn một chút so với các giải pháp được đăng hiện tại vì giá trị đã được gán với khóa và không cần tìm nạp lại từ đối tượng từ điển.

+1

Đó có lẽ là cách tiếp cận tôi sẽ làm theo, nhưng rất thích biết chi phí của việc sao chép toàn bộ từ điển. – Alberto

-2

Lặp qua các phím trong từ điển, chứ không phải KeyValuePairs.

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

Đáng ngạc nhiên là nó ném một ngoại lệ "* Bộ sưu tập đã sửa đổi ... *". – Slauma

+0

@Slauma Thật vậy * đáng ngạc nhiên *, vì sự thay đổi này không phải là một sự thay đổi về cấu trúc, do đó việc ném số đếm vào thực tế là không cần thiết. –

0

Trong khi iterating trên các từ điển trực tiếp là không thể bởi vì bạn sẽ có được một ngoại lệ (như Ron đã nói), bạn không cần sử dụng danh sách tạm thời để giải quyết vấn đề.

Thay vì sử dụng không phải là foreach, nhưng một vòng lặp for để lặp qua các từ điển và thay đổi các giá trị với truy cập được lập chỉ mục:

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

Mặc dù điều này sẽ làm việc, 'Enumerable.ElementAt()' - là phương thức mở rộng bạn đang sử dụng - là một hoạt động ['O (n)' cho non-'IList <> 's] (http: // referencesource. microsoft.com/#System.Core/System/Linq/Enumerable.cs,7db56d44563d8761,references). –

+0

Sẽ nói rõ ràng những gì Eugene Beresovsky đang nói. Điều này kết thúc lên ít nhất là O (n^2). Vì vậy, đây là một giải pháp rất xấu. –

1

Một giải pháp sẽ được đặt các phím trong một danh sách (hoặc bộ sưu tập khác) trước và lặp qua chúng khi thay đổi từ điển:

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

Đối với các lập trình viên lười biếng:

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

Khi tôi đang mã hóa ngay bây giờ với .NET 4.5, phương thức 'ToList()' không có sẵn, nhưng thành viên 'Keys' có thể lặp lại được vì vậy' .ToList() 'là không cần thiết. –

+4

@MikeC xem nhận xét trên http://stackoverflow.com/a/2260462/1037948 - bạn không thể trực tiếp liệt kê các phím, '.ToList()' là một "hack" để có được xung quanh rằng – drzaus

+3

ToList () là một phương pháp mở rộng LINQ. Nó sẽ có sẵn nếu bạn thêm "using System.Linq;" để sử dụng báo cáo của bạn. – bcwhims

1

tôi nhận thấy rằng cách nhanh nhất (tại thời điểm này) lặp trên từ điển với modify là:

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

Đầu tiên mất khoảng 2.2s và thứ hai một 4.5s (thử nghiệm điển kích thước 1000 và lặp lại thời gian 10k, thay đổi kích thước từ điển thành 10 không thay đổi tỷ lệ). Ngoài ra không có một thỏa thuận lớn với việc nhận được danh sách khóa, giá trị từ điển [key] nhận được chỉ là chậm VS được xây dựng trong vòng lặp. Ngoài ra, nếu bạn muốn sử dụng tốc độ nhanh hơn, hãy sử dụng loại mã hóa cứng (C test), với điều đó tôi nhận được khoảng 1.85s (với mã hóa cứng để "đối tượng").

EDIT:

Anna đã đăng cùng một giải pháp trước: https://stackoverflow.com/a/6515474/766304

0

qua một thời gian, nhưng có lẽ ai đó đang quan tâm đến nó:

yourDict = yourDict.ToDictionary(kv => k.Key, kv => Math.Round(kv.Value, 3)) 
Các vấn đề liên quan