Появление бокса в C#


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

  • преобразование типа значения!--12--> тип:

    struct S { }
    object box = new S();
    
  • преобразование типа значения!--13--> тип:

    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

  • тип тестового значения unconstrained или 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 78

5 ответов:

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

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

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

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

кстати, случай конкатенации строк, который вы проницательно определили, также происходит от приведения к объекту. Оператор + переводится компилятором в вызов метода Concat string, который принимает объект для передаваемого типа значения, поэтому происходит приведение к объекту и, следовательно, бокс.

на протяжении многих лет я всегда советовал разработчикам помню единой причины для бокса (я указал выше), вместо того, чтобы запоминать каждый отдельный случай, потому что список длинный и трудно запомнить. Это также способствует пониманию того, какой код IL компилятор генерирует для нашего кода C# (например, + on string дает вызов String.Функция concat). Когда вы сомневаетесь в том, что генерирует компилятор, и если происходит бокс, вы можете использовать Il Disassembler (ILDASM.исполняемый.) Обычно вы должны искать код операции box (есть только один случай, когда бокс может произойти, даже если IL не включает в себя поле код операции, более подробно ниже).

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

другой случай для менее очевидного бокса - это вызов метода базового класса из структуры. Пример:

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

здесь ToString переопределяется, поэтому вызов ToString на MyValType не будет генерировать бокс. Однако реализация вызывает базовую строку ToString, и это вызывает бокс (проверьте IL!).

кстати, эти два неочевидных сценария бокса также вытекают из одного правила выше. Когда метод вызывается в базовом классе типа значения, там должно быть что-то для этой ключевое слово для обозначения. Так как базовый класс типа значения является (всегда) ссылочным типом, то этой ключевое слово должно ссылаться на ссылочный тип, и поэтому нам нужна ссылка на тип значения, и поэтому бокс происходит из-за одного правила.

вот прямая ссылка на раздел моего онлайн-курса .NET, который подробно обсуждает бокс:http://motti.me/mq

Если вас интересует только больше расширенные сценарии бокса вот прямая ссылка там (хотя ссылка выше приведет вас туда же, как только она обсудит более основные вещи):http://motti.me/mu

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

Мотти

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

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

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

параметры

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 против null

public void Bla<T>(T obj)
{
    if (obj == null) //boxes.
}

использование динамических

dynamic x = 42; (boxes)

еще один

enumValue.HasFlag

  • использование неродовых коллекций в System.Collections таких как ArrayList или HashTable.

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

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

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