Это отличный вопрос!
Бокс происходит ровно по одной причине: когда нам нужна ссылка на тип значения . Все, что вы перечислили, подпадает под это правило.
Например, поскольку объект является ссылочным типом, приведение типа значения к объекту требует ссылки на тип значения, что вызывает упаковку.
Если вы хотите перечислить все возможные сценарии, вы должны также включить производные, такие как возвращение типа значения из метода, который возвращает объект или тип интерфейса, потому что это автоматически приводит тип значения к объекту / интерфейсу.
Кстати, случай конкатенации строк, который вы проницательно определили, также происходит от приведения к объекту. Оператор + переводится компилятором в вызов метода строки Concat, который принимает объект для передаваемого вами типа значения, поэтому происходит приведение к объекту и, следовательно, упаковка.
В течение многих лет я всегда советовал разработчикам запоминать одну единственную причину для бокса (я указывал выше) вместо того, чтобы запоминать каждый отдельный случай, потому что список длинный и его трудно запомнить. Это также способствует пониманию того, какой IL-код генерирует компилятор для нашего кода C # (например, + в строке приводит к вызову String.Concat). Когда вы сомневаетесь в том, что генерирует компилятор, и если происходит боксирование, вы можете использовать IL Disassembler (ILDASM.exe). Как правило, вы должны искать код операции с блоком (есть только один случай, когда может произойти бокс, даже если IL не включает код операции с блоком, более подробно ниже).
Но я согласен, что некоторые случаи бокса менее очевидны. Вы перечислили один из них: вызов не переопределенного метода типа значения. На самом деле, это менее очевидно по другой причине: когда вы проверяете код IL, вы видите не код операции, а код операции ограничения, поэтому даже в IL не очевидно, что происходит бокс! Я не буду вдаваться в подробности, почему этот ответ не станет длиннее ...
Другой случай для менее очевидного бокса - это вызов метода базового класса из структуры. Пример:
struct MyValType
{
public override string ToString()
{
return base.ToString();
}
}
Здесь ToString переопределяется, поэтому вызов ToString для MyValType не будет генерировать бокс. Однако реализация вызывает базовую ToString, и это вызывает бокс (проверьте IL!).
Кстати, эти два неочевидных сценария бокса также вытекают из единственного правила, приведенного выше. Когда метод вызывается из базового класса типа значения, для ключевого слова this должно быть что-то, на что можно сослаться. Поскольку базовый класс типа значения (всегда) является ссылочным типом, ключевое слово this должно ссылаться на ссылочный тип, и поэтому нам нужна ссылка на тип значения, поэтому бокс происходит из-за единственного править.
Вот прямая ссылка на раздел моего онлайн-курса .NET, в котором подробно обсуждается бокс: http://motti.me/mq
Если вас интересуют только более продвинутые сценарии бокса, здесь есть прямая ссылка (хотя ссылка, приведенная выше, перенесет вас туда же, когда будут обсуждаться более простые вещи): http://motti.me/mu
Надеюсь, это поможет!
Моти