С# изменение структур в List <T>
Короткий вопрос: как изменить отдельные элементы в List
? (точнее, члены a struct
, хранящиеся в List
?)
Полное объяснение:
Во-первых, определения struct
, используемые ниже:
public struct itemInfo
{
...(Strings, Chars, boring)...
public String nameStr;
...(you get the idea, nothing fancy)...
public String subNum; //BTW this is the element I'm trying to sort on
}
public struct slotInfo
{
public Char catID;
public String sortName;
public Bitmap mainIcon;
public IList<itemInfo> subItems;
}
public struct catInfo
{
public Char catID;
public String catDesc;
public IList<slotInfo> items;
public int numItems;
}
catInfo[] gAllCats = new catInfo[31];
gAllCats
заполняется при загрузке и т.д. вниз по линии по мере запуска программы.
Проблема возникает, когда я хочу сортировать объекты itemInfo
в массиве subItems
.
Я использую LINQ для этого (потому что, похоже, не существует другого разумного способа сортировки списков нестрогового типа).
Итак, вот что у меня есть:
foreach (slotInfo sInf in gAllCats[c].items)
{
var sortedSubItems =
from itemInfo iInf in sInf.subItems
orderby iInf.subNum ascending
select iInf;
IList<itemInfo> sortedSubTemp = new List<itemInfo();
foreach (itemInfo iInf in sortedSubItems)
{
sortedSubTemp.Add(iInf);
}
sInf.subItems.Clear();
sInf.subItems = sortedSubTemp; // ERROR: see below
}
Ошибка: "Невозможно изменить членов" sInf ", потому что это" переменная итерации foreach ".
a, это ограничение не имеет смысла; не является ли первичное использование конструкции foreach?
b (также из злости), что делает Clear(), если не изменить список? (BTW, List удаляется, согласно отладчику, если я удаляю последнюю строку и запускаю ее.)
Итак, я попытался использовать другой подход и посмотреть, работает ли он с использованием регулярного цикла. (По-видимому, это допустимо только потому, что gAllCats[c].items
на самом деле является IList
, я не думаю, что это позволит вам индексировать обычный List
таким образом.)
for (int s = 0; s < gAllCats[c].items.Count; s++)
{
var sortedSubItems =
from itemInfo iInf in gAllCats[c].items[s].subItems
orderby iInf.subNum ascending
select iInf;
IList<itemInfo> sortedSubTemp = new List<itemInfo>();
foreach (itemInfo iInf in sortedSubItems)
{
sortedSubTemp.Add(iInf);
}
//NOTE: the following two lines were incorrect in the original post
gAllCats[c].items[s].subItems.Clear();
gAllCats[c].items[s].subItems = sortedSubTemp; // ERROR: see below
}
На этот раз ошибка: "Невозможно изменить возвращаемое значение" System.Collections.Generic.IList.this [int] ", потому что это не переменная". Тьфу! Что это, если не переменная? и когда он стал "возвращаемым значением"?
Я знаю, что должен быть "правильный" способ сделать это; Я прихожу к этому с фона C, и я знаю, что могу сделать это на C (хотя и с небольшим количеством ручного управления памятью.)
Я искал вокруг, и кажется, что ArrayList
вышел из моды в пользу общих типов (я использую 3.0), и я не могу использовать массив, так как размер должен быть динамическим.
Ответы
Ответ 1
Рассматривая подход for-loop, причина (и решение) для этого указана в документации для ошибки компиляции:
Была сделана попытка изменить значение тип, который создается в результате промежуточное выражение, но не хранится в переменной. Эта ошибка может возникают, когда вы пытаетесь напрямую изменить структуру в общем коллекция.
Чтобы изменить структуру, сначала назначьте ее к локальной переменной, измените переменную, затем назначьте переменную вернуться к элементу в коллекции.
Итак, в вашем for-loop измените следующие строки:
catSlots[s].subItems.Clear();
catSlots[s].subItems = sortedSubTemp; // ERROR: see below
... в:
slotInfo tempSlot = gAllCats[0].items[s];
tempSlot.subItems = sortedSubTemp;
gAllCats[0].items[s] = tempSlot;
Я удалил вызов методу Clear
, так как я не думаю, что он добавляет ничего.
Ответ 2
Проблема, с которой вы сталкиваетесь в foreach
, состоит в том, что структуры являются типами значений, и в результате переменная цикла итерации на самом деле не является ссылкой на структуру в списке, а скорее копией структуры.
Я предполагаю, что компилятор запретит вам изменять его, потому что он, скорее всего, не будет делать то, что вы ожидаете от него.
subItems.Clear()
не является проблемой, потому что, поскольку поле может быть копией элемента в списке, это также ссылка на список (мелкая копия).
Простейшим решением, вероятно, было бы изменение от struct
до a class
для этого. Или используйте совершенно другой подход с for (int ix = 0; ix < ...; ix++)
и т.д.
Ответ 3
Цикл foreach не работает, потому что sInf
является копией структуры внутри элементов. Изменение sInf
не изменит "фактическую" структуру в списке.
Очистка работает, потому что вы не меняете sInf, вы меняете список внутри sInf, а Ilist<T>
всегда будет ссылочным типом.
То же самое происходит, когда вы используете оператор индексирования на Ilist<T>
- он возвращает копию вместо фактической структуры. Если компилятор разрешил catSlots[s].subItems = sortedSubTemp;
, вы будете изменять подтипы копии, а не фактическую структуру. Теперь вы видите, почему компилятор говорит, что возвращаемое значение не является переменной - копия не может быть снова указана.
Существует довольно простое исправление - работайте над копией, а затем перезапишите исходную структуру своей копией.
for (int s = 0; s < gAllCats[c].items.Count; s++)
{
var sortedSubItems =
from itemInfo iInf in gAllCats[c].items[s].subItems
orderby iInf.subNum ascending
select iInf;
IList<itemInfo> sortedSubTemp = new List<itemInfo>();
foreach (itemInfo iInf in sortedSubItems)
{
sortedSubTemp.Add(iInf);
}
var temp = catSlots[s];
temp.subItems = sortedSubTemp;
catSlots[s] = temp;
}
Да, это приводит к двум операциям копирования, но к той цене, которую вы платите за семантику значения.
Ответ 4
Две указанные вами ошибки связаны с тем фактом, что вы используете структуры, которые в С# являются типами значений, а не ссылочными типами.
Вы можете использовать ссылочные типы в циклах foreach. Если вы измените свои структуры на классы, вы можете просто сделать это:
foreach(var item in gAllCats[c].items)
{
item.subItems = item.subItems.OrderBy(x => x.subNum).ToList();
}
С помощью структур это должно измениться на:
for(int i=0; i< gAllCats[c].items.Count; i++)
{
var newitem = gAllCats[c].items[i];
newitem.subItems = newitem.subItems.OrderBy(x => x.subNum).ToList();
gAllCats[c].items[i] = newitem;
}
В других ответах есть более подробная информация о том, почему структуры работают иначе, чем классы, но я думал, что смогу помочь с сортировкой.
Ответ 5
Если subItems
было изменено на конкретный список вместо интерфейса IList
, вы сможете использовать метод Sort
.
public List<itemInfo> subItems;
Итак, вся ваша петля становится:
foreach (slotInfo sInf in gAllCats[c].items)
sInf.subItems.Sort();
Это не потребует изменения содержимого struct
вообще (как правило, это хорошо). Члены struct
будут по-прежнему указывать на одни и те же объекты.
Кроме того, очень мало оснований для использования struct
в С#. GC очень, очень хорошо, и вам будет лучше с class
, пока вы не продемонстрируете узкое место в распределении памяти в профилировщике.
Еще более лаконично, если items
в gAllCats[c].items
также является List
, вы можете написать:
gAllCats[c].items.ForEach(i => i.subItems.Sort());
Изменить: вы слишком легко сдаетесь!:)
Sort
очень легко настроить. Например:
var simpsons = new[]
{
new {Name = "Homer", Age = 37},
new {Name = "Bart", Age = 10},
new {Name = "Marge", Age = 36},
new {Name = "Grandpa", Age = int.MaxValue},
new {Name = "Lisa", Age = 8}
}
.ToList();
simpsons.Sort((a, b) => a.Age - b.Age);
Это от младшего до самого старого. (Не подходит ли тип вывода в С# 3?)