Упаковка и распаковка значимых типов (boxing и unboxing)

Любую переменную значимого типа можно преобразовать к ссылочному типу – например экземпляру object. Это преобразование называется упаковкой (boxing) значимого типа. Обратное преобразование называется распаковка (unboxing).

Рассмотрим следующий пример кода:

using System;

class Program {
    static void Main() {
        int i = 0;
        Console.WriteLine( "i = " + i );
    }
}

В этой программе объявляется целочисленная переменная i, а затем ее значение выводится в консоль. Вот ее вывод:

Посмотрим какой IL-код компилятор генерирует для этой программы:

.class private auto ansi beforefieldinit
  Program extends [mscorlib]System.Object {
  .method private hidebysig static void
    Main() cil managed {
    .entrypoint
    .maxstack 2
    .locals init (
      [0] int32 i
    )

    IL_0000: ldc.i4.0
    IL_0001: stloc.0      // i

    IL_0002: ldstr        "i = "
    IL_0007: ldloc.0      // i
    IL_0008: box          [mscorlib]System.Int32  // Упаковка!
    IL_000d: call         string [mscorlib]System.String::Concat(object, object)
    IL_0012: call         void [mscorlib]System.Console::WriteLine(string)

    IL_0017: ret

  } // end of method Program::Main
} // end of class Program

Обратите внимание на инструкцию box в строке 16. Именно эта команда и выполняет упаковку значимых типов. В нашем случае была произведена упаковка целочисленной переменной i. Это произошло потому что нет перегруженных версий метода String.Concat, совпадающих с типом передаваемых в программе переменных (string и int32), поэтому был вызван метод String.Concat(object, object), для которого требуется преобразовать значимый тип int32 в ссылочный тип object. Это и привело к упаковке переменной.

Наличие большого числа операций упаковки и распаковки может заметно снизить производительность программы, т.к. требует дополнительных расходов процессорного времени и памяти на обработку таких операций. Следовательно, для повышения производительности программы, нужно по возможности избегать появления операций упаковки и распаковки.

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

using System;

class Program {
    static void Main() {
        int i = 0;
        Console.WriteLine( "i = " + i.ToString() );
    }
}

При наличии явного вызова метода i.ToString() упаковки не происходит.

Упаковка значимых типов предполагает выполнение средой CLR следующих операций:

  1. Выделение в управляемой куче дополнительной памяти, равной длине значимого типа, а также нескольких служебных полей – блока синхронизации объекта и указателя на тип объекта.
  2. Копирование значимого типа в выделенную область памяти в управляемой куче. При этой операции в памяти появляются два одинаковых объекта – один – значимый тип – в стеке, другой – ссылочный – в куче.
  3. В стек возвращается ссылка на вновь созданный объект.

Возможные ошибки при работе со структурами

Структуры в C# относятся к типам значений и в некоторых случаях могут быть упакованы. Например, приведение структуры к интерфейсу приводит к упаковке структуры. Рассмотрим следующий пример кода:

using System;

interface IInterface {
    void PrintValue();
    void SetValue( int i );
}

struct Test : IInterface {
    public int Field { get; set; }

    public void PrintValue() {
        Console.WriteLine( "Field = {0}", Field);
    }

    public void SetValue( int value ) {
        Field = value;
    }
}

class Program {
    static void Main() {
        Test test = new Test { Field = 10 };
        Console.WriteLine( "Field = {0}", test.Field );

        IInterface iTest = test; // здесь происходит упаковка структуры
        iTest.SetValue( 1 );
        iTest.PrintValue();

        Console.WriteLine( "Field = {0}", test.Field );

        Console.ReadKey();
    }
}

Вывод программы:

В 25 строчке кода происходит упаковка структуры test и, следовательно, создается ее копия. Поэтому в 26 строчке кода меняется значение не переменной test, а ее упакованной копии. В 29 строчке кода выводится значение поля Field переменной test. Можно убедиться, что работа с интерфейсным объектом никак не повлияла на нее.

Операция упаковки (boxing) в IL-коде представлена специальной командой – box. Можно убедиться, что предыдущий фрагмент кода вызывает операцию упаковки, если открыть скомпилированную программу в ildasm.exe:

.method private hidebysig static void Main( string[] args ) cil managed
  {  

    // Здесь удален начальный участок кода 

    IL_002c: ldloc.0      // test
    IL_002d: box          ConsoleApplication34.Test // упаковка
    IL_0032: stloc.1      // iTest

    // ... и конечный

  } 

Обратная операция – распаковка тоже представлена своей IL-командой – unbox.

avatar
5000
  Подписаться  
Уведомление о