Поява боксу в C #


85

Я намагаюся зібрати всі ситуації, в яких бокс відбувається в 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

Чи є ще такі ситуації боксу, можливо, приховані, про які ви знаєте?


2
Я займався цим деякий час тому і виявив це досить цікавим: виявлення (не) боксу за допомогою FxCop
Джордж Дакетт

Дуже хороші способи конвертації, які ви показали. Infact, я не знав 2-го, або, можливо, ніколи не пробував :) Дякую
Zenwalker

12
це має бути запитання wiki спільноти
Хитрий

2
А як щодо типів, що дозволяють обнуляти? private int? nullableInteger
Аллансон

1
@allansson, нульові типи - це просто тип значень
controlflow

Відповіді:


42

Це чудове питання!

Бокс відбувається саме з однієї причини: коли нам потрібне посилання на тип цінності . Все, що ви перелічили, підпадає під це правило.

Наприклад, оскільки об’єкт є посилальним типом, для передачі типу значення об’єкту потрібне посилання на тип значення, що спричиняє поле.

Якщо ви хочете перерахувати всі можливі сценарії, вам слід також включити похідні, такі як повернення типу значення з методу, який повертає об'єкт або тип інтерфейсу, оскільки це автоматично передає тип значення об'єкту / інтерфейсу.

До речі, ви чітко визначений випадок об'єднання рядків також походить від приведення до об'єкта. Оператор + перекладається компілятором у виклик методу Concat рядка, який приймає об'єкт для типу значення, який ви передаєте, тому відбувається приведення до об'єкта, а отже, і бокс.

Протягом багатьох років я завжди радив розробникам пам’ятати єдину причину боксу (я вказав вище), а не запам’ятовувати кожен окремий випадок, оскільки цей список довгий і важко запам’ятати. Це також сприяє розумінню того, який код IL створює компілятор для нашого коду C # (наприклад, + на рядок дає виклик String.Concat). Коли ви сумніваєтесь, що створює компілятор, і якщо відбувається бокс, ви можете використовувати IL Disassembler (ILDASM.exe). Зазвичай вам слід шукати код операційної коробки (існує лише один випадок, коли може відбутися бокс, навіть якщо ІЛ не містить кодовий код дії, детальніше нижче).

Але я згоден, що деякі випадки боксу менш очевидні. Ви перерахували один із них: виклик неперевизначеного методу типу значення. Насправді це менш очевидно з іншої причини: коли ви перевіряєте код ІЛ, ви бачите не код дії коду, а код обмеження, тому навіть в ІЛ не очевидно, що бокс відбувається! Я не буду вдаватися в точні подробиці, чому не дати цій відповіді стати ще довшою ...

Інший випадок менш очевидного боксу - це виклик методу базового класу зі структури. Приклад:

struct MyValType
{
    public override string ToString()
    {
        return base.ToString();
    }
}

Тут ToString замінено, тому виклик ToString на MyValType не призведе до боксу. Однак реалізація викликає базовий ToString, що викликає бокс (перевірте IL!).

До речі, ці два неочевидні сценарії боксу також випливають із єдиного правила вище. Коли метод викликається в базовому класі типу значення, має бути щось, на що посилається це ключове слово. Оскільки базовим класом типу значення є (завжди) посилальний тип, ключове слово this повинно посилатися на тип посилання, і тому нам потрібне посилання на тип значення, і тому бокс відбувається завдяки одному правилу.

Ось пряме посилання на розділ мого онлайн-курсу .NET, де детально розглядається бокс: http://motti.me/mq

Якщо вас цікавлять лише більш просунуті сценарії боксу, тут є пряме посилання (однак посилання вище переведе вас туди, коли воно обговорить більш основні речі): http://motti.me/mu

Сподіваюся, це допоможе!

Мотті


1
Якщо a ToString()викликається для певного типу значення, який не замінює його, чи буде значення значення вставлено на сайт виклику або метод буде відправлений (без боксу) до автоматично згенерованого заміщення, яке не робить нічого, крім ланцюжка (з боксу) до базового методу?
supercat

@supercat Виклик будь-якого методу, який викликає baseтип значення, спричинить бокс. Це включає віртуальні методи, які не замінені структурою, і Objectметоди, які взагалі не є віртуальними (наприклад GetType()). Дивіться це питання .
faafak Gür

@ FaafakGür: Я знаю, що кінцевим результатом буде бокс. Мені було цікаво про точний механізм, за допомогою якого це відбувається. Оскільки компілятор, що генерує IL, може не знати, чи є тип типом значення або посиланням (це може бути загальним), він незалежно генерує виклик. JITter буде знати, чи тип є типом значення, і чи він замінює ToString, тому він може генерувати код сайту виклику для здійснення боксу; в якості альтернативи, він може автоматично генерувати для кожної структури, яка не перевизначає ToStringметод public override void ToString() { return base.ToString(); }і ...
supercat

... нехай бокс відбувається в рамках цього методу. Оскільки метод був би дуже коротким, тоді він міг би бути вбудованим. Виконання речей з останнім підходом дозволило ToString()б отримати доступ до методу struct через Reflection, як і будь-який інший, і використовувати його для створення статичного делегата, який приймає тип struct як refпараметр [така річ працює з не успадкованими методами struct], але я просто спробував створити такого делегата, і це не спрацювало. Чи можна створити статичний делегат для ToString()методу структури , і якщо так, то яким повинен бути тип параметра?
supercat

Посилання порушено.
OfirD

5

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

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

2
GetTypeвимагає боксу не просто тому, що він невіртуальний, а тому, що місця зберігання цінного типу, на відміну від об’єктів купи, не мають «прихованого» поля, яке GetType()можна використовувати для ідентифікації їх типу.
supercat

@supercat Хммм. 1. Бокс, доданий компілятором, і приховане поле, що використовується під час виконання. Можливо, компілятор додає бокс, оскільки він знає про час виконання ... 2. Коли ми називаємо невіртуальний ToString (рядок) за значенням переліку, він також вимагає боксу, і я не вірю, що компілятор додає це, оскільки він знає про деталі реалізації Enum.ToString (рядок) . Таким чином, я думаю, я можу сказати, що бокс завжди відбувався, коли викликався невіртуальний метод на "тип базового значення".
Вячеслав Іванов

Я не розглядав Enumнаявність власних невіртуальних методів, хоча ToString()метод для методу Enumповинен мати доступ до інформації про тип. Цікаво , якщо Object, ValueTypeабо Enumмає якісь - або не-віртуальні методи , які могли б виконувати свою роботу без інформації про типі.
supercat

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


0
  • Використання необщим колекцій в System.Collectionsтакому , як ArrayListабо HashTable.

Звичайно, це конкретні випадки вашої першої справи, але їх можна приховати. Вражає кількість коду, з яким я стикаюся і сьогодні і використовую їх замість List<T>та Dictionary<TKey,TValue>.


0

Додавання будь-якого значення типу значення до списку ArrayList викликає бокс:

ArrayList items = ...
numbers.Add(1); // boxing to object
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.