Замечание О Коллекциях В C# И Структурах. Вопрос Памяти

Эта информация будет полезна тем, кто только осваивает C#, пытается разобраться в теории на практике, не дочитав предварительно книги до конца.

Скорее всего, это заметка на полях, а не подробное описание и анализ темы.

Итак, всем, кому интересно, как ведут себя структуры в памяти при работе с коллекциями в C#, добро пожаловать.

Читая по вечерам Рихтера, я решил разобраться в каждой детали подробнее, имея огромное желание глубже разобраться в идеологии .

Net. Листая страницы книги, я пришел к выводу, что многое из того, что проектируется и пишется каждый день, постоянно нарушает некоторые основы .

Net. Давайте рассмотрим на примере структур и коллекций.

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

Пример 1: Использование старого ArrayList.

   
  
 
 IL_0002: stloc.0 // myInt
  IL_0003: newobj System.Collections.ArrayList.ctor
  IL_0008: stloc.1 // al
  IL_0009: ldloc.1 // al
  IL_000A: ldloc.0 // myInt
  IL_000B: box System.Int32
  IL_0010: callvirt System.Collections.ArrayList.Add
 
Ожидаемый результат: в стеке будет два разных элемента типа System.Int32. Причём один из них уже будет ссылочным типом.

Если мы откроем IL-представление этого кода, то увидим честное подтверждение:

 
 IL_0002: stloc.0 // myInt
  IL_0003: newobj System.Collections.Generic.List.ctor
  IL_0008: stloc.1 // l
  IL_0009: ldloc.1 // l
  IL_000A: ldloc.0 // myInt
 IL_000B: callvirt System.Collections.Generic.List.Add
 
Операция box(boxing) создает в куче объект типа Int32. При добавлении элемента типа структура каждый раз его аналог будет создаваться в куче, а ссылка попадет в коллекцию.

Со временем на помощь в этой акции пришли родовые коллекции.

Давайте посмотрим на пример и его IL-представление: Пример 2. Использование дженериков для коллекций:

 
 IList l = new List<T>();
 
Этот код преобразуется в:
 
 System.Int32 myInt = 7;
  IList l = new List<int>();
  l.Add(myInt);
 
Этот тип коллекции является более продвинутым и может работать с типами значений более разумно, без использования операции box\unbox. Это значительно снижает затраты памяти, повышает производительность и уменьшает количество сборок мусора.

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

 
  IL_0002: stloc.0 // myInt
  IL_0003: newobj System.Collections.Generic.List.ctor
  IL_0008: stloc.1 // l
  IL_0009: ldloc.1 // l
  IL_000A: ldloc.0 // myInt
  IL_000B: box System.Int32
  IL_0010: callvirt System.Collections.IList.Add
 
Код прост, но его результаты для структур оказались неоднозначными.

Если мы посмотрим на IL-представление этого кода:

System.Int32 myInt = 7; ArrayList al = new ArrayList(); al.Add(myInt);

Тогда мы получим следующее:

System.Int32 myInt = 7; List<int> l = new List<int>(); l.Add(myInt);

Мы снова оказываемся на упаковочном блоке.

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

Это был первый вывод, с помощью которого я попытался объяснить происходящее.

В итоге чуть позже было найдено объяснение от самого автора.

Любая интерфейсная переменная всегда должна помещаться в кучу, поэтому и появляется дополнительная операция упаковки.

Вывод прост. Сначала теория, а потом практика, хотя обычно все происходит иначе.

Теги: #C++ #.

NET #.

NET #C++

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.