Варианты использования для упаковки типа значения в С#?

Бывают случаи, когда экземпляр типа значения необходимо рассматривать как экземпляр ссылочного типа. В подобных ситуациях экземпляр типа значения можно преобразовать в экземпляр ссылочного типа с помощью процесса, называемого заниматься боксом. Когда экземпляр типа значения упакован, хранилище выделяется в куче, и значение экземпляра копируется в это пространство. Ссылка на это хранилище помещается в стек. Упакованное значение — это объект, ссылочный тип, который содержит содержимое экземпляра типа значения.

Понимание системы общих типов .NET

В Википедии есть пример для Java. Но в C#, в каких случаях нужно упаковать тип значения? Или лучше/похожий вопрос: зачем хранить тип значения в куче (в штучной упаковке), а не в стеке?


person felideon    schedule 22.06.2009    source источник
comment
Также см. boxing-occurrence-in-c-sharp   -  person nawfal    schedule 03.06.2013


Ответы (9)


Как правило, вам следует избегать упаковки типов значений.

Однако бывают редкие случаи, когда это полезно. Например, если вам нужно ориентироваться на платформу 1.1, у вас не будет доступа к универсальным коллекциям. Любое использование коллекций в .NET 1.1 потребует обработки вашего типа значения как System.Object, что вызывает упаковку/распаковку.

Есть еще случаи, когда это может быть полезно в .NET 2.0+. Каждый раз, когда вы хотите воспользоваться тем фактом, что все типы, включая типы значений, могут рассматриваться как объекты напрямую, вам может понадобиться использовать упаковку/распаковку. Иногда это может быть удобно, так как позволяет сохранять любой тип в коллекции (используя object вместо T в универсальной коллекции), но в целом лучше этого избегать, так как вы теряете безопасность типов. Однако один случай, когда упаковывание часто происходит, - это когда вы используете Reflection - многие вызовы в отражении потребуют упаковки/распаковки при работе с типами значений, поскольку тип заранее не известен.

person Reed Copsey    schedule 22.06.2009

Почти никогда не бывает веской причины преднамеренно упаковывать тип значения. Почти всегда причиной упаковки типа значения является сохранение его в некоторой коллекции, которая не поддерживает тип. Старый ArrayList, например, является коллекцией объектов, которые являются ссылочными типами. Единственный способ собрать, скажем, целые числа — упаковать их как объекты и передать в ArrayList.

В настоящее время у нас есть общие коллекции, так что это не проблема.

person Randolpho    schedule 22.06.2009
comment
Очень верно. Тем не менее, рефлексия — это то, чем вы хотите ограничиться. Я абсолютно не могу сделать это в других случаях, поэтому я думаю, что мое первое предложение правильно. - person Randolpho; 22.06.2009

Бокс обычно происходит автоматически в .NET, когда это необходимо; часто, когда вы передаете тип значения чему-то, что ожидает ссылочный тип. Типичным примером является string.Format(). Когда вы передаете примитивные типы значений этому методу, они упаковываются как часть вызова. Так:

int x = 10;
string s = string.Format( "The value of x is {0}", x ); // x is boxed here

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

Интересно отметить, что когда вы используете дженерики в .NET, типы значений не упаковываются при использовании в качестве параметров или членов типа. Это делает дженерики более эффективными, чем старый код C# (например, ArrayList), который обрабатывает все как {object}, чтобы не зависеть от типа. Это добавляет еще одну причину для использования универсальных коллекций, таких как List<T> или Dictionary<T,K> вместо ArrayList или Hashtable.

person LBushkin    schedule 22.06.2009

Я бы порекомендовал вам 2 хорошие статьи Эрика Липперта.

http://blogs.msdn.com/ericlippert/archive/2009/04/27/the-stack-is-an-implementation-detail.aspx

http://blogs.msdn.com/ericlippert/archive/2009/05/04/the-stack-is-an-implementation-detail-part-two.aspx

Вот цитата, с которой я согласен на 100%

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

В 99% приложений разработчиков не должно волновать, почему типы Value находятся в стеке, а не в куче, и какой прирост производительности здесь может быть. Ютс имеет в виду очень простые правила:

  1. Избегайте упаковки/распаковки, когда в этом нет необходимости, используйте коллекции дженериков. Большинство проблем возникает не тогда, когда вы определяете свои собственные типы, а когда вы неправильно используете существующие типы (определенные Microsoft или вашими коллегами).
  2. Сделайте ваши типы значений простыми. Если вам нужна структура с 10-20 полями, я полагаю, вам лучше создать класс. Представьте себе, все эти поля будут копироваться каждый раз, когда вы изредка будете передавать ей функцию по значению...
  3. Я не думаю, что очень полезно иметь типы значений с полями ссылочного типа внутри. Подобно структуре со строковыми и объектными полями.
  4. Определите, какой тип вам нужен, в зависимости от требуемой функциональности, а не от того, где он должен храниться. Структуры имеют ограниченную функциональность по сравнению с классами, поэтому, если структура не может предоставить требуемую функциональность, например конструктор по умолчанию, определите класс.
  5. Если что-то может выполнять какие-либо действия с данными других типов, оно обычно определяется как класс. Для структур операции с разными типами следует определять только в том случае, если вы можете привести один тип к другому. Скажем, вы можете добавить int к double, потому что вы можете привести in к double.
  6. Если что-то должно быть без состояния, это класс.
  7. Когда вы колеблетесь, используйте ссылочные типы. :-)

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

p.s. Я встречал некоторых разработчиков ASP.NET с 2-3-летним опытом, которые не знают разницы между стеком и кучей. :-( Я бы не нанял такого человека, если бы я был интервьюером, но не потому, что упаковка/распаковка может быть узким местом на любом из сайтов ASP.NET, которые я когда-либо видел.

person Bogdan_Ch    schedule 22.06.2009
comment
Хороший ответ. +1 согласен; если разработчики не знают разницы между стеком и кучей, это свидетельствует о реальном непонимании основных принципов хранения их данных и указания на них. Это может привести к появлению серьезных ошибок (при условии, что изменение объекта в одной коллекции не меняет его, например, в другой). - person StriplingWarrior; 22.06.2009
comment
Спасибо за связанные статьи! - person felideon; 23.06.2009
comment
Что вы имеете в виду в 3? почему это имеет значение? и не могли бы вы определить лиц без гражданства в 6 лет? - person MasterMastic; 12.12.2013

Я думаю, что хороший пример упаковки в С# встречается в необобщенных коллекциях, таких как ArrayList.

person bruno conde    schedule 22.06.2009

Одним из примеров может быть, когда метод принимает параметр объекта и должен быть передан тип значения.

person kemiller2002    schedule 22.06.2009

Ниже приведены некоторые примеры упаковки/распаковки.

ArrayList ints = new ArrayList();
myInts.Add(1); // boxing
myInts.Add(2); // boxing

int myInt = (int)ints [0]; // unboxing

Console.Write("Value is {0}", myInt); // boxing
person BengtBe    schedule 22.06.2009

Одна из ситуаций, когда это происходит, например, если у вас есть метод, который ожидает параметр типа объекта, и вы передаете один из примитивных типов, например, int. Или, если вы определяете параметр как «ref» типа int.

person epitka    schedule 22.06.2009
comment
-1. В C# нет упаковки при передаче по ссылке. См. примечание здесь: msdn.microsoft.com/en-us/library/14akc2c7. .aspx - person Randolpho; 22.06.2009

Код

int x = 42;
Console.Writeline("The value of x is {0}", x );

на самом деле упаковывает и распаковывает, потому что Writeline делает int приведение внутри. Чтобы избежать этого, вы можете сделать

int x = 42;
Console.Writeline("The value of x is {0}", x.ToString());

Остерегайтесь незаметных ошибок!

Вы можете объявить свои собственные типы значений, объявив свой собственный тип как struct. Представьте, что вы объявляете struct с множеством свойств, а затем помещаете несколько экземпляров внутрь ArrayList. Это коробит их, конечно. Теперь сошлитесь на один через оператор [], приведите его к типу и установите свойство. Вы просто устанавливаете свойство в копии. Тот, что в ArrayList, все еще не изменен.

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

person Hans Malherbe    schedule 22.06.2009
comment
Это то, что можно было бы назвать микрооптимизацией, и я бы рекомендовал избегать способа кодирования, подобного тому, который показан во втором блоке кода. На самом деле здесь вместо упаковки вы создаете новую строку, а затем эта строка копируется, потому что строки неизменяемы, и когда вы передаете их как параметр ByVal, они должны быть скопированы. Итак, вы действительно думаете, что этот код будет более оптимальным? Сомневаюсь. Если Ms создала базовую функцию Console.Writeline(string format, int i); который принимает целочисленный параметр, вы должны использовать его. - person Bogdan_Ch; 23.06.2009
comment
Это вообще не оптимизация, это просто иллюстрирует принцип. Что вы имеете в виду под словом "копировать"? - person Hans Malherbe; 23.06.2009