Вопрос

Способ создания коллекции целых чисел в .NET 1.0 (например):

ArrayList list = new ArrayList();
list.Add(i);          /* boxing   */
int j = (int)list[0]; /* unboxing */

Наказанием за это является отсутствие безопасности типов и производительности из-за упаковки и распаковки.

Способ .NET 2.0 заключается в использовании дженериков:

List<int> list = new List<int>();
list.Add(i);
int j = list[0];

Цена упаковки (насколько я понимаю) — это необходимость создать объект в куче, скопировать целое число, выделенное в стеке, в новый объект и наоборот для распаковки.

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

Что происходит на самом деле?

Это было полезно?

Решение

Когда дело доходит до коллекций, дженерики позволяют избежать бокса / распаковки, используя фактические T[] массивы внутри. List<T> Например, использует T[] Массив для хранения его содержимого.

То множество, Конечно, является эталонным типом и поэтому (в текущей версии CLR, Yada Yada) хранится на куче. Но так как это T[] И не ан object[], Элементы массива могут быть сохранены «напрямую»: то есть они все еще на куче, но они на куче в массиве Вместо того, чтобы быть в штучной упаковке и наличие массива содержит ссылки на коробки.

Так для А. List<int>, Например, то, что у вас в массиве "выглядит", как это:

[ 1 2 3 ]

Сравнить это с ArrayList, который использует object[] И поэтому «смотреть» что-то вроде этого:

* A * B * C

...куда *a, и т. Д. И т. Д. Находятся ссылки на объекты (в штучных размерах):

* A -> 1 * B -> 2 * C -> 3

Извините эти неочищенные иллюстрации; Надеюсь, вы знаете, что я имею в виду.

Другие советы

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

  • Переменная - это место хранения, которое имеет тип.
  • Срок службы переменной может быть коротким или длительным. «Короче говоря, мы имеем в виду», пока текущая функция не возвращается или бросает «и« долго », мы имеем в виду« возможно, больше, чем это ».
  • Если тип переменной является эталонным типом, то содержимое переменной является ссылкой на долгоживущее местоположение хранения. Если тип переменной является тип значения, то содержимое переменной является значением.

В качестве деталей реализации, место хранения, которое гарантированно будет недолгино, может быть выделено в стеке. Расположение хранения, которое может быть долговреждено, выделяется на куче. Обратите внимание, что это ничего не говорит о «Типах ценностей всегда выделяется на стеке». Типы ценностей есть нет Всегда выделяется в стеке:

int[] x = new int[10];
x[1] = 123;

x[1] это место хранения. Это долгоживутся; Это может жить дольше, чем этот метод. Поэтому он должен быть на куче. Тот факт, что он содержит int, не имеет значения.

Вы правильно говорите, почему в штучной упаковке есть дорого:

Цена на бокс - это необходимость создания объекта на куче, скопируйте стек выделенного целого числа к новому объекту и вице-версии для Unboxing.

Куда вы ошибаетесь, это сказать «стек выделенного целого числа». Неважно, где было выделено целое число. Что дело заключалось в том, что его хранение содержал целое число, вместо того, чтобы содержаться Ссылка на расположение кучи. Отказ Цена - это необходимость создавать объект и сделать копию; Это единственная стоимость, которая актуальна.

Так почему же общая вариабельная вариация дорогая? Если у вас есть переменная типа T, и T создается, чтобы быть int, то у вас есть переменная типа INT, период. Переменная типа Int - это место хранения, и он содержит int. Будь то место хранения на стеке или куча полностью не имеет значения. Отказ Что актуально, это то, что место хранения содержит int., вместо того, чтобы содержаться Ссылка на что-то на куче. Отказ Поскольку место хранения содержит INT, вам не нужно принимать расходы на бокс и распаковку: выделение нового хранения на куче и копированием int на новое хранилище.

Это теперь ясно?

Generics позволяет набрать внутренний массив списка int[] вместо эффективного object[], что потребует бокса.

Вот что происходит без универсов:

  1. Ты звонишь Add(1).
  2. Целое число 1 в коробке входит в объект, который требует, чтобы новый объект был построен на куче.
  3. Этот объект передан ArrayList.Add().
  4. Объект в штучной упаковке набивается в object[].

Здесь есть три уровня косвествия: ArrayList -> object[] -> object -> int.

С рождами:

  1. Ты звонишь Add(1).
  2. INT 1 передается List<int>.Add().
  3. INT набивается в int[].

Так что есть только два уровня косвения: List<int> -> int[] -> int.

Несколько других различий:

  • Необслуживаемый метод потребуется сумма 8 или 12 байтов (один указатель, один int) для хранения значения, 4/8 в одном распределении и 4 в другом. И это, вероятно, будет больше из-за выравнивания и прокладки. Общий метод потребует всего 4 байта пространства в массиве.
  • Необслуживаемый метод требует выделения коробок; универсальный метод не делает. Это быстрее и уменьшает GC Churn.
  • Необщий метод требует отбрасывания для извлечения значений. Это не WarsAfe, и это немного медленнее.

ArrayList только обрабатывает тип object Так что использовать этот класс, требует литья до и из object. Отказ В случае типов ценностей это отливка включает в себя бокс и распаковывать.

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

Цена на бокс (к моему пониманию) - это необходимость создания объекта на куче, скопируйте стек выделенного целого числа к новому объекту и наоборот для Unboxing.

Я думаю, что вы предполагаете, что типы значений всегда создаются в стеке. Это не так - они могут быть созданы либо на куче, на стеке или в регистрах. Для получения дополнительной информации об этом см. Статью Эрика Липперта: Правда о типах ценностей.

В .NET 1, когда Add метод называется:

  1. Пространство выделяется в куче;появилась новая ссылка
  2. Содержание i переменная копируется в ссылку
  3. Копия ссылки помещается в конце списка.

В .NET 2:

  1. Копия переменной i передается в Add метод
  2. Копия этой копии помещается в конец списка.

Да, i переменная по-прежнему копируется (в конце концов, это тип значения, а типы значений всегда копируются, даже если они являются просто параметрами метода).Но в куче не создается избыточной копии.

Почему вы думаете с точки зрения WHERE Значения объекты хранятся? В типов значения C # можно хранить на стеке, а также куча в зависимости от того, что выбирает CLR.

Где дженерики имеют значение, это WHAT хранится в коллекции. В случае ArrayList Коллекция содержит ссылки на боксерские объекты, где в качестве List<int> Содержит ценности int сами.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top