Ответ 1
Отличный вопрос!
Бокс происходит по одной причине: когда нам нужна ссылка на тип значения. Все, что вы указали, попадает в это правило.
Например, поскольку объект является ссылочным типом, для задания типа значения для объекта требуется ссылка на тип значения, который вызывает бокс.
Если вы хотите перечислить все возможные сценарии, вы должны также включить производные инструменты, такие как возврат типа значения из метода, который возвращает объект или тип интерфейса, поскольку это автоматически присваивает тип значения объекту/интерфейсу.
Кстати, случай конкатенации строк, который вы точно идентифицировали, также происходит от каста к объекту. Оператор + преобразуется компилятором в вызов метода Concat строки, который принимает объект для типа значения, который вы передаете, поэтому бросание на объект и, следовательно, бокс происходит.
На протяжении многих лет Ive всегда советовала разработчикам помнить единственную причину бокса (я указал выше) вместо того, чтобы запоминать каждый отдельный случай, потому что список длинный и трудно запоминающийся. Это также способствует пониманию того, какой код IL генерирует компилятор для нашего кода С# (например, + на строке дает вызов String.Concat). Когда вы сомневаетесь в том, что генерирует компилятор, и если происходит бокс, вы можете использовать IL Disassembler (ILDASM.exe). Как правило, вы должны искать код операции box (есть только один случай, когда может произойти бокс, даже если 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
Надеюсь, это поможет!
Моти