2008-09-09 34 views
53

Tôi có một danh sách các cấu trúc và tôi muốn thay đổi một phần tử. Ví dụ:Thay đổi giá trị của một phần tử trong danh sách các cấu trúc

MyList.Add(new MyStruct("john"); 
MyList.Add(new MyStruct("peter"); 

Bây giờ tôi muốn thay đổi một phần tử:

MyList[1].Name = "bob" 

Tuy nhiên, bất cứ khi nào tôi cố gắng và làm được điều này tôi nhận được lỗi sau:

Cannot modify the return value of System.Collections.Generic.List.this[int]‘ because it is not a variable

Nếu tôi sử dụng một danh sách các lớp, vấn đề không xảy ra.

Tôi đoán câu trả lời phải làm với cấu trúc là loại giá trị.

Vì vậy, nếu tôi có danh sách các cấu trúc, tôi có nên coi chúng là chỉ đọc không? Nếu tôi cần phải thay đổi các yếu tố trong một danh sách sau đó tôi nên sử dụng các lớp học và không cấu trúc?

Trả lời

28
MyList[1] = new MyStruct("bob"); 

cấu trúc trong C# hầu như luôn luôn được thiết kế là không thay đổi (nghĩa là không có cách nào thay đổi trạng thái nội bộ khi chúng được tạo).

Trong trường hợp của bạn, những gì bạn muốn làm là thay thế toàn bộ cấu trúc trong chỉ mục mảng được chỉ định, không cố gắng thay đổi chỉ một thuộc tính hoặc trường duy nhất.

+2

Đây không phải là câu trả lời đầy đủ, câu trả lời của Gishu hoàn chỉnh hơn nhiều. – Motti

+0

Những gì Jolson nói - Nó không quá nhiều cấu trúc là "bất biến". đúng. -1 cos Thật sai khi nói rằng cấu trúc là không thay đổi. – GuruC

+2

Để công bằng với Andrew - tôi không giải thích rằng ông nói rằng cấu trúc là "bất biến", ông nói rằng họ nên được sử dụng như * nếu * họ là bất biến; và chắc chắn bạn có thể biến chúng thành bất biến nếu tất cả các trường đều chỉ đọc. – Montdidier

38

Không hoàn toàn. Thiết kế một loại như lớp hoặc cấu trúc không được thúc đẩy bởi nhu cầu của bạn để lưu trữ nó trong bộ sưu tập :) Bạn nên xem 'ngữ nghĩa' cần thiết

Sự cố bạn thấy là do ngữ nghĩa loại giá trị. Mỗi biến giá trị/tham chiếu là một cá thể mới. Khi bạn nói

Struct obItem = MyList[1]; 

điều gì xảy ra là trường hợp mới của cấu trúc được tạo và tất cả thành viên được sao chép từng cái một. Vì vậy, bạn có một bản sao của MyList [1] tức là 2 trường hợp. Bây giờ nếu bạn sửa đổi obItem, nó không ảnh hưởng đến bản gốc.

obItem.Name = "Gishu"; // MyList[1].Name still remains "peter" 

Bây giờ chịu với tôi trong 2 phút ở đây (Điều này mất một thời gian để nuốt gọn .. nó đã làm cho tôi :) Nếu bạn thực sự cần cấu trúc được lưu trữ trong một bộ sưu tập và sửa đổi như bạn đã nêu trong của bạn câu hỏi, bạn sẽ phải làm cho cấu trúc của bạn lộ một giao diện (Tuy nhiên điều này sẽ dẫn đến boxing). Sau đó, bạn có thể sửa đổi cấu trúc thực tế thông qua tham chiếu giao diện, tham chiếu đến đối tượng được đóng hộp.

Đoạn mã sau minh họa những gì tôi vừa nói ở trên

public interface IMyStructModifier 
{  String Name { set;  }  } 
public struct MyStruct : IMyStructModifier ... 

List<Object> obList = new List<object>(); 
obList.Add(new MyStruct("ABC")); 
obList.Add(new MyStruct("DEF")); 

MyStruct temp = (MyStruct)obList[1]; 
temp.Name = "Gishu"; 
foreach (MyStruct s in obList) // => "ABC", "DEF" 
{  Console.WriteLine(s.Name);   } 

IMyStructModifier temp2 = obList[1] as IMyStructModifier; 
temp2.Name = "Now Gishu"; 
foreach (MyStruct s in obList) // => "ABC", "Now Gishu" 
{  Console.WriteLine(s.Name);  } 

HTH. Câu hỏi hay.
Cập nhật: @Hath - bạn đã cho tôi chạy để kiểm tra xem tôi có bỏ qua điều gì đó đơn giản không. (Nó sẽ không phù hợp nếu các thuộc tính setter không và các phương thức đã làm - vũ trụ .Net vẫn được cân bằng :)
Phương thức Setter không hoạt động obList2 [1] trả về một bản sao có trạng thái sẽ được sửa đổi. Cấu trúc ban đầu trong danh sách vẫn chưa được sửa đổi. Vì vậy, Set-via-Interface có vẻ là cách duy nhất để làm điều đó.

List<MyStruct> obList2 = new List<MyStruct>(); 
obList2.Add(new MyStruct("ABC")); 
obList2.Add(new MyStruct("DEF")); 
obList2[1].SetName("WTH"); 
foreach (MyStruct s in obList2) // => "ABC", "DEF" 
{ 
    Console.WriteLine(s.Name); 
} 
+0

Vẫn không tốt. Danh sách sẽ phải được khai báo là loại giao diện, trong trường hợp đó tất cả các mục trong đó sẽ được đóng hộp. Đối với mọi loại giá trị, có một giá trị tương đương đóng hộp * có ngữ nghĩa loại lớp *. Nếu bạn muốn sử dụng một lớp học, sử dụng một lớp, nhưng sau đó nhận thức được những điều khó chịu khác của chúng. – supercat

+0

@Supercat - Giống như tôi đã đề cập ở trên ... nó sẽ gây ra Boxing. Tôi không đề xuất sửa đổi thông qua tham chiếu giao diện - chỉ cần nói rằng nó sẽ hoạt động nếu bạn phải có một bộ sưu tập structs + muốn sửa đổi chúng tại chỗ.Đó là một hack .. về cơ bản bạn đang làm cho wrappers loại ref cho các loại giá trị. – Gishu

+1

Thay vì sử dụng 'Danh sách ' hoặc có cấu trúc triển khai giao diện setter (ngữ nghĩa whoswe là khủng khiếp, như đã nói ở trên), cách khác là định nghĩa 'class SimpleHolder {public T Value; } 'và sau đó sử dụng một' Danh sách > '. Nếu 'Giá trị' là một trường thay vì cấu trúc, thì một giá trị sẽ là một câu lệnh như' obList2 [1] .Value.Name = "George"; 'sẽ hoạt động tốt. – supercat

11

Không quá nhiều cấu trúc là "không thay đổi".

Vấn đề cơ bản thực sự là cấu trúc là loại Giá trị, không phải là loại Tham chiếu. Vì vậy, khi bạn kéo ra một "tham chiếu" để cấu trúc từ danh sách, nó tạo ra một bản sao mới của toàn bộ cấu trúc. Vì vậy, bất kỳ thay đổi nào bạn thực hiện trên đó sẽ thay đổi bản sao, chứ không phải phiên bản gốc trong danh sách.

Giống như bang Andrew, bạn phải thay thế toàn bộ cấu trúc. Mặc dù tôi nghĩ bạn phải tự hỏi tại sao bạn lại sử dụng cấu trúc ở vị trí đầu tiên (thay vì một lớp). Đảm bảo bạn không làm điều đó xung quanh các mối quan tâm tối ưu hóa sớm.

4

Không có gì sai với các cấu trúc có trường tiếp xúc hoặc cho phép đột biến thông qua trình định vị thuộc tính. Tuy nhiên, các Structs tự biến đổi để phản hồi các phương thức hoặc các getters thuộc tính, là nguy hiểm vì hệ thống sẽ cho phép các phương thức hoặc các getters thuộc tính được gọi trên các cá thể struct tạm thời; nếu các phương pháp hoặc getters thay đổi cấu trúc, những thay đổi đó sẽ bị loại bỏ.

Thật không may, khi bạn lưu ý, các bộ sưu tập được tích hợp vào .net thực sự yếu khi phơi bày các đối tượng kiểu giá trị chứa trong đó. Đặt cược tốt nhất của bạn thường là để làm điều gì đó như:

 
    MyStruct temp = myList[1]; 
    temp.Name = "Albert"; 
    myList[1] = temp; 

Hơi khó chịu và không an toàn. Tuy nhiên sự cải thiện so với một danh sách của một kiểu lớp, nơi làm được điều tương tự có thể yêu cầu:

 
    myList[1].Name = "Albert"; 

nhưng nó cũng có thể yêu cầu:

 
    myList[1] = myList[1].Withname("Albert"); 

hoặc có lẽ

 
    myClass temp = (myClass)myList[1].Clone(); 
    temp.Name = "Albert"; 
    myList[1] = temp; 

hoặc có lẽ một số biến thể khác. Một thực sự sẽ không thể biết trừ khi một kiểm tra myClass cũng như mã khác mà đưa những thứ trong danh sách. Hoàn toàn có thể là người ta có thể không biết được liệu hình thức đầu tiên có an toàn hay không mà không kiểm tra mã trong các hội đồng mà người đó không có quyền truy cập. Ngược lại, nếu Name là một trường tiếp theo của MyStruct, phương thức mà tôi đưa ra để cập nhật nó sẽ hoạt động, bất kể MyStruct có chứa gì khác, hoặc bất kể những thứ khác có thể đã làm với myList trước khi mã thực hiện hoặc những gì họ có thể mong đợi làm gì với nó sau.

+0

Bạn nói, "... nếu' Tên' là một trường tiếp xúc của 'MyStruct', phương pháp tôi đưa ra để cập nhật nó sẽ hoạt động ..." Không chính xác. Vì bạn đã đưa ra bóng ma của an toàn luồng cho trường hợp tham chiếu 'lớp', nó chỉ công bằng để đánh giá mã' ValueType' trên cùng một cơ sở, và các vị trí chỉ mục trong danh sách có thể thay đổi trong quá trình hoạt động của bạn sao cho 'myList [1] ] 'không còn tương ứng với cá thể' struct' mà bạn đã tìm nạp. Để khắc phục điều này, bạn cần một số loại khóa hoặc đồng bộ hóa nhận thức được toàn bộ cá thể bộ sưu tập. Và phiên bản 'class' vẫn chịu đựng những vấn đề tương tự. –

+0

@GlennSlayden: Một bộ sưu tập hiển thị các mục như byrefs để chỉnh sửa tại chỗ có thể dễ dàng cho phép chỉnh sửa các mục trong thời trang an toàn nếu tất cả các bổ sung và xóa sẽ được thực hiện trước khi bất kỳ phần tiếp theo nào được hiển thị. Nếu cần thiết, một bộ sưu tập có thể được xây dựng để cho phép các mục được thêm vào cuối mà không ảnh hưởng đến bất kỳ byref nào, nhưng điều đó sẽ yêu cầu bất kỳ việc mở rộng nào được thực hiện chỉ bằng cách thêm các đối tượng hoặc mảng mới - chắc chắn có thể. – supercat

+0

Tôi đã không nói điều đó là không thể và tôi chắc chắn biết nhiều cách để sửa chữa nó, tôi chỉ muốn chỉ ra điều kiện chủng tộc trong ví dụ của bạn khi nó đứng. Có lẽ đó là bởi vì chuyên môn vi mô tò mò của tôi trong khu vực tối nghĩa này khiến cho các chủng tộc dính vào như những thiên thạch trên sông băng Nam Cực. Đối với "bộ sưu tập hiển thị các mục như byrefs cho chỉnh sửa tại chỗ:" Có, chính xác; Tôi không thể tự nói tốt hơn. Đã ở đó, làm điều đó, công trình tuyệt vời. Ồ, và tôi gần như quên mất, đồng thời không khóa. –

Các vấn đề liên quan