Изменение значения элемента в списке структур - PullRequest
56 голосов
/ 09 сентября 2008

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

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

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

MyList[1].Name = "bob"

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

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

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

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

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

Ответы [ 4 ]

41 голосов
/ 09 сентября 2008

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

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

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);
}
33 голосов
/ 09 сентября 2008
MyList[1] = new MyStruct("bob");

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

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

11 голосов
/ 10 сентября 2008

Это не так уж много, что структуры "неизменны".

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

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

5 голосов
/ 19 декабря 2011

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

К сожалению, как вы заметили, коллекции, встроенные в .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 до выполнения кода или чего они могут ожидать делать с этим после.

...