Изменение значения элемента в списке структур

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

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

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

MyList[1].Name = "bob"

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

Невозможно изменить возвращаемое значение System.Collections.Generic.List.this [int] ', потому что это не переменная

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

Я предполагаю, что ответ связан с тем, что structs является типом значения.

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

Ответ 1

MyList[1] = new MyStruct("bob");
Структуры

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

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

Ответ 2

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

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

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

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

Ответ 3

Не так много, чтобы структуры были "неизменными".

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

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

Ответ 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 до того, как код выполнит или что они могут ожидать делать с ним после.

Ответ 5

В дополнение к другим ответам я подумал, что было бы полезно объяснить, почему компилятор жалуется.

Когда вы вызываете MyList[1].Name, в отличие от массива, MyList[1] фактически вызывает метод индексатора за кулисами.

Каждый раз, когда метод возвращает экземпляр структуры, вы получаете копию этой структуры (если вы не используете ref/out).

Итак, вы получаете копию и устанавливаете свойство Name для копии, которая собирается быть отброшена, поскольку копия нигде не сохранялась в переменной.

Этот учебник описывает, что происходит более подробно (включая сгенерированный код CIL).