С# изменение структур в 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?)