Когда происходит с типами значений, когда они удаляются из коллекции? - PullRequest
4 голосов
/ 05 октября 2009

Предположим, у меня есть несколько простых struct таких:

public struct WeightedInt {
    public int value;
    public double weight;
}

Тогда, скажем, у меня есть коллекция экземпляров этой структуры:

List<WeightedInt> weightedInts = new List<WeightedInt>();

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

void AddWeightedIntToList(int value, double weight) {
    WeightedInt wint = new WeightedInt();
    wint.value = value;
    wint.weight = weight;

    weightedInts.Add(wint);
}

a копия локальной переменной wint добавляется к weightedInts, тогда как локальная переменная сама удаляется из памяти после завершения AddWeightedIntToList.

Прежде всего: это правильно?

Во-вторых, где хранится эта копия wint? Он не может быть в стеке, с тех пор он исчезнет после завершения функции (верно?). Означает ли это, что копия хранится в куче вместе с weightedInts? И собирается ли мусор после удаления, как если бы это был экземпляр ссылочного типа?

Вполне возможно, что на этот вопрос есть ответ где-нибудь в статье, и в этом случае ссылка на эту статью будет вполне приемлемым ответом. Мне просто не повезло найти его.

Ответы [ 2 ]

6 голосов
/ 05 октября 2009

Прежде всего: это правильно?

Да. Оригинал исчезает, как только заканчивается область действия.

Во-вторых, где хранится эта копия wint? Он не может быть в стеке, с тех пор он исчезнет после завершения функции (верно?). Означает ли это, что копия хранится в куче вместе с weightedInts? И собирается ли мусор после удаления, как если бы это был экземпляр ссылочного типа?

Ваш экземпляр List<WeightedInt> создает массив в куче. Вы назначаете части этого массива копию вашего типа значения, когда вы «добавляете» его в список. Значение сохраняется в куче как часть массива (внутренняя часть класса List).

Когда ваш член weightedInts выходит из области видимости, он становится необжитым и будет иметь право на сбор мусора. В какой-то момент после этого GC запустится и освободит память, связанную с его внутренним массивом, освободив тем самым память, связанную с вашей копией wint.


Edit:

Также при звонке:

weightedInts.Remove(wint);

Происходит несколько вещей (с List<T>).

Во-первых, список находит индекс FIRST-экземпляра вашего типа значения, равный wint. Затем он вызывает RemoteAt (индекс).

Метод RemoveAt (index) в основном отмечает, что внутренний размер на один меньше, а затем проверяет индекс, который вы удаляете. Если он находится в середине списка, он фактически копирует ВСЕ экземпляры типа значения в один элемент, используя Array.Copy, чтобы «сжать» список. Затем он обнуляет память в конце массива.

Сам массив не сжимается, поэтому при удалении элементов не освобождается память. Если вы хотите восстановить эту память (или даже сделать ее доступной для GC), вам нужно позвонить List<T>. TrimExcess ().

1 голос
/ 05 октября 2009

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

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

...