Возникновение бокса в C # - PullRequest
81 голосов
/ 03 ноября 2011

Я пытаюсь собрать все ситуации, в которых происходит бокс в 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 #sions:

    (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

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

Ответы [ 5 ]

41 голосов
/ 03 ноября 2011

Это отличный вопрос!

Бокс происходит ровно по одной причине: когда нам нужна ссылка на тип значения . Все, что вы перечислили, подпадает под это правило.

Например, поскольку объект является ссылочным типом, приведение типа значения к объекту требует ссылки на тип значения, что вызывает упаковку.

Если вы хотите перечислить все возможные сценарии, вы должны также включить производные, такие как возвращение типа значения из метода, который возвращает объект или тип интерфейса, потому что это автоматически приводит тип значения к объекту / интерфейсу.

Кстати, случай конкатенации строк, который вы проницательно определили, также происходит от приведения к объекту. Оператор + переводится компилятором в вызов метода строки 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

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

Моти

5 голосов
/ 03 ноября 2011

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

struct S { };
S s = new S();
s.GetType();
2 голосов
/ 16 апреля 2013

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

Параметры задействованы

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

0 голосов
/ 04 ноября 2011

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

ArrayList items = ...
numbers.Add(1); // boxing to object
0 голосов
/ 03 ноября 2011
  • Использование неуниверсальных коллекций в System.Collections, таких как ArrayList или HashTable.

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

...