Бокс Происшествие в С#

Я пытаюсь собрать все ситуации, в которых бокс происходит в С#:

  • Преобразование типа значения в System.Object type:

    struct S { }
    object box = new S();
    
  • Преобразование типа значения в System.ValueType type:

    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();
    
  • Использование типов значений в конкатенации строк С#:

    char c = F();
    string s1 = "char value will box" + c;
    

    примечание: константы типа char объединяются во время компиляции

    примечание: с версии 6.0 С# компилятор оптимизирует конкатенацию с использованием bool, char, IntPtr, UIntPtr типы

  • Создание делегата из метода экземпляра типа значения:

    struct S { public void M() {} }
    Action box = new S().M;
    
  • Вызов непереопределенных виртуальных методов для типов значений:

    enum E { A }
    E.A.GetHashCode();
    
  • Использование констант С# 7.0 в выражении is:

    int x = …;
    if (x is 42) { … } // boxes both 'x' and '42'!
    
  • Бокс в преобразованиях типов кортежей С#:

    (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-режимах

Есть ли еще какие-нибудь ситуации в боксе, возможно, скрытые, о которых вы знаете?

Ответы

Ответ 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

Надеюсь, это поможет!

Моти

Ответ 2

Вызов не виртуального метода GetType() для типа значения:

struct S { };
S s = new S();
s.GetType();

Ответ 3

Упоминается в ответе Мотти, просто иллюстрируя примеры кода:

Применяемые параметры

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

Ответ 4

  • Использование не общих наборов в System.Collections, таких как ArrayList или HashTable.

Конечно, это конкретные примеры вашего первого случая, но они могут быть скрыты. Это удивительно, количество кода, которое я до сих пор встречаю сегодня, которые используют их вместо List<T> и Dictionary<TKey,TValue>.

Ответ 5

Добавление значения значения типа в ArrayList вызывает бокс:

ArrayList items = ...
numbers.Add(1); // boxing to object