2008-09-09 3 views
53

У меня есть список структур, и я хочу изменить один элемент. Например:Изменение значения элемента в списке структур

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

Теперь я хочу изменить один элемент:

MyList[1].Name = "bob" 

Однако всякий раз, когда я пытаюсь сделать это, я получаю следующее сообщение об ошибке:

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

Если я использую список классов, проблема не возникает.

Я думаю, что ответ имеет отношение к structs, являющемуся типом значения.

Итак, если у меня есть список структур, я должен рассматривать их как только для чтения? Если мне нужно изменить элементы в списке, тогда я должен использовать классы, а не структуры?

ответ

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

в C Структура # почти всегда должно быть разработана, чтобы быть неизменны (то есть, нет никакого способа изменить свое внутреннее состояние, когда они были созданы).

В вашем случае то, что вы хотите сделать, это заменить всю структуру в указанном индексе массива, а не пытаться изменить только одно свойство или поле.

+2

Это не полный ответ, ответ Гишу гораздо более полный. – Motti 2008-10-06 19:50:50

+0

То, что сказал Джолсон, - это не столько, что структуры «неизменяемы». верно. -1 cos Действительно неправильно сказать, что структуры неизменяемы. – GuruC 2011-03-17 09:44:16

+2

Чтобы быть справедливым к Эндрю - я не понимаю, что он говорит, что структуры являются «неизменными», он говорит, что они должны использоваться как *, если * они неизменяемы; и, конечно же, вы можете сделать их неизменными, если все поля прочитаны только. – Montdidier 2012-05-03 01:52:44

38

Не совсем. Проектирование типа класса или структуры не должно зависеть от необходимости хранить его в коллекциях :). Вы должны посмотреть на «семантику», необходимую

Проблема, которую вы видите, связана с семантикой типа значения. Каждая переменная типа/reference типа значения представляет собой новый экземпляр. Когда вы говорите

Struct obItem = MyList[1]; 

Что происходит, так это то, Итак, у вас есть клон MyList [1], т. Е. 2 ​​экземпляра. Теперь, если вы изменяете obItem, это не влияет на оригинал.

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

Теперь терпите меня в течение 2 минут здесь (Это займет некоторое время, чтобы давиться .. это сделал для меня :) Если вам действительно нужно, чтобы быть Структуры хранятся в коллекции и модифицирована, как вы указали в вашем вопрос, вам придется сделать вашу структуру разоблачением интерфейса (Однако это приведет к боксу). Затем вы можете изменить фактическую структуру через ссылку на интерфейс, которая ссылается на объект в штучной упаковке.

Следующий фрагмент кода показывает, что я только что сказал выше

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. Хороший вопрос.
Обновление: @Hath - вы заставили меня бежать, чтобы проверить, не пропустил ли я что-то такое простое. (Было бы непоследовательно, если бы свойства setter и методы не выполнялись. Вселенная все еще сбалансирована :)
Метод сеттера не работает
obList2 [1] возвращает копию, состояние которой будет изменено. Исходная структура в списке остается неизменной. Так что Set-through-Interface, кажется, только способ сделать это.

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); 
} 
11

Не так много структур, которые являются «неизменными».

Реальная основная проблема заключается в том, что structs представляют собой тип Value, а не тип Reference. Поэтому, когда вы вытаскиваете «ссылку» на структуру из списка, она создает новую копию всей структуры. Таким образом, любые изменения, внесенные вами, изменяют копию, а не оригинальную версию в списке.

Как и Эндрю, вы должны заменить всю структуру. В этой точке, хотя я думаю, вы должны спросить себя, почему вы используете структуру в первую очередь (вместо класса). Удостоверьтесь, что вы не делаете этого в условиях преждевременной оптимизации.

4

Нет ничего плохого в структурах, которые имеют открытые поля или которые допускают мутацию через средства определения свойств. Однако структуры, которые мутируют себя в ответ на методы или получатели свойств, опасны, потому что система позволит вызвать методы или свойства getters для временных экземпляров структуры; если методы или геттеры вносят изменения в структуру, эти изменения в конечном итоге будут отброшены.

К сожалению, как вы помните, коллекции, встроенные в .net, действительно слабы, при экспонировании объектов типа значений, содержащихся в них. Лучше всего сделать что-то вроде:

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

Немного досадно, а вовсе не потокобезопасно. Тем не менее улучшение по сравнению с Перечня типа класса, где делают то же самое, возможно, потребуется:

 
    myList[1].Name = "Albert"; 

но это может также потребовать:

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

или, может быть,

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

или, может быть, некоторые другие варианты. Один действительно не смог бы знать, если бы никто не рассмотрел myClass, а также другой код, который помещал вещи в список. Вполне возможно, что не может быть известно, безопасна ли первая форма без изучения кода в сборках, к которым у кого нет доступа. В отличие от этого, если Name является открытым полем MyStruct, метод, который я дал для его обновления, будет работать независимо от того, что содержит MyStruct, или независимо от того, что другие вещи могли сделать с myList до того, как код выполнит или что они могут ожидать сделайте с ним после.