Я пытаюсь собрать все ситуации, в которых происходит бокс на C #:
Преобразование типа значения в
System.Object
тип:struct S { } object box = new S();
Преобразование типа значения в
System.ValueType
тип:struct S { } System.ValueType box = new S();
Преобразование значения типа перечисления в
System.Enum
тип:enum E { A } System.Enum box = E.A;
Преобразование типа значения в ссылку на интерфейс:
interface I { } struct S : I { } I box = new S();
Использование типов значений в конкатенации строк C #:
char c = F(); string s1 = "char value will box" + c;
примечание: константы
char
типа объединяются во время компиляциипримечание: начиная с версии 6.0 C # компилятор оптимизирует конкатенация с участием
bool
,char
,IntPtr
,UIntPtr
типыСоздание делегата из метода экземпляра типа значения:
struct S { public void M() {} } Action box = new S().M;
Вызов непереопределенных виртуальных методов для типов значений:
enum E { A } E.A.GetHashCode();
Использование шаблонов констант C # 7.0 в
is
выражении:int x = …; if (x is 42) { … } // boxes both 'x' and '42'!
Бокс в преобразованиях типов кортежей C #:
(int, byte) _tuple; public (object, object) M() { return _tuple; // 2x boxing }
Необязательные параметры
object
типа со значениями по умолчанию для типа значения:void M([Optional, DefaultParameterValue(42)] object o); M(); // boxing at call-site
Проверка значения неограниченного универсального типа для
null
:bool M<T>(T t) => t != null; string M<T>(T t) => t?.ToString(); // ?. checks for null M(42);
примечание: это может быть оптимизировано JIT в некоторых средах выполнения .NET
Значение
struct
типового тестирования неограниченного или общего типа с операторамиis
/as
:bool M<T>(T t) => t is int; int? M<T>(T t) => t as int?; IEquatable<T> M<T>(T t) => t as IEquatable<T>; M(42);
примечание: это может быть оптимизировано JIT в некоторых средах выполнения .NET
Есть ли еще какие-нибудь боксерские ситуации, может быть, скрытые, о которых вы знаете?
источник
private int? nullableInteger
Ответы:
Отличный вопрос!
Упаковка происходит ровно по одной причине: когда нам нужна ссылка на тип значения . Все, что вы перечислили, подпадает под это правило.
Например, поскольку объект является ссылочным типом, приведение типа значения к объекту требует ссылки на тип значения, что вызывает упаковку.
Если вы хотите перечислить все возможные сценарии, вы также должны включить производные, такие как возврат типа значения из метода, который возвращает объект или тип интерфейса, потому что это автоматически приводит тип значения к объекту / интерфейсу.
Кстати, случай конкатенации строк, который вы проницательно определили, также происходит от приведения к объекту. Оператор + переводится компилятором в вызов метода Concat строки, который принимает объект для передаваемого вами типа значения, поэтому происходит приведение к объекту и, следовательно, упаковка.
На протяжении многих лет я всегда советовал разработчикам помнить единственную причину бокса (я указал выше) вместо того, чтобы запоминать каждый отдельный случай, потому что список длинный и трудный для запоминания. Это также способствует пониманию того, какой код IL генерирует компилятор для нашего кода C # (например, + в строке вызывает вызов String.Concat). Когда вы сомневаетесь в том, что генерирует компилятор, и если происходит бокс, вы можете использовать дизассемблер IL (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
Надеюсь, это поможет!
Мотти
источник
ToString()
вызывается для определенного типа значения, который не переопределяет его, будет ли тип значения помещен в коробку на сайте вызова или метод будет отправлен (без упаковки) на автоматически сгенерированное переопределение, которое ничего не делает, кроме цепочки (с бокс) к базовому методу?base
тип значения, вызовет бокс. Это включает виртуальные методы, которые не переопределяются структурой, иObject
методы, которые вообще не являются виртуальными (например,GetType()
). См. Этот вопрос .ToString
методpublic override void ToString() { return base.ToString(); }
и ...ToString()
получить доступ к методу структуры через Reflection, как и любой другой, и использовать его для создания статического делегата, который принимает тип структуры в качествеref
параметра [такая вещь работает с ненаследуемыми методами структуры], но я просто попытался создать такого делегата, и это не сработало. Можно ли создать статический делегат дляToString()
метода структуры , и если да, то каким должен быть тип параметра?Вызов не виртуального метода GetType () для типа значения:
struct S { }; S s = new S(); s.GetType();
источник
GetType
требует бокса не только потому, что он не виртуальный, но и потому, что места хранения значений типа, в отличие от объектов кучи, не имеют «скрытого» поля, котороеGetType()
можно использовать для определения их типа.Enum
наличии каких-либо собственных невиртуальных методов, хотяToString()
метод для anEnum
должен иметь доступ к информации о типе. Интересно, есть лиObject
,ValueType
илиEnum
какие-либо невиртуальные методы, которые могли бы выполнять свою работу без информации о типе.Упоминается в ответе Мотти, просто иллюстрируя образцы кода:
Участвующие параметры
public void Bla(object obj) { } Bla(valueType) public void Bla(IBla i) //where IBla is interface { } Bla(valueType)
Но это безопасно:
public void Bla<T>(T obj) where T : IBla { } Bla(valueType)
Тип возврата
public object Bla() { return 1; } public IBla Bla() //IBla is an interface that 1 inherits { return 1; }
Проверка неограниченного T на нуль
public void Bla<T>(T obj) { if (obj == null) //boxes. }
Использование динамических
dynamic x = 42; (boxes)
Еще один
enumValue.HasFlag
источник
System.Collections
таких какArrayList
илиHashTable
.Конечно, это конкретные примеры вашего первого случая, но они могут быть скрыты. Удивительно, сколько кода я до сих пор встречаю, в котором они используются вместо
List<T>
иDictionary<TKey,TValue>
.источник
Добавление любого значения типа значения в ArrayList вызывает бокс:
ArrayList items = ... numbers.Add(1); // boxing to object
источник