Когда происходит упаковка в C#

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

• преобразование типа значения в объектную ссылку;
• преобразование типа значения в ссылку на System.ValueType;
• преобразование типа значения в ссылку на интерфейс, реализованный этим типом значения;
• преобразование типа enum в ссылку на System.Еnum.

В каждом случае преобразование обычно принимает форму выражения присваивания. Первые два случая довольно очевидны, поскольку CLR здесь заполняет зазор между двумя категориями типов, превращая экземпляр типа значения в ссылочный тип. Третий случай может показаться несколько неожиданным.

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface IPrint {
void Print ();
}
public struct MyValue : IPrint {
public int x;
public void Print () {
System.Console.WriteLine( "{0}", x );
}
public class EntryPoint {
static void Main() {
MyValue myval = new MyValue();
myval.x = 123;
// нет упаковки myval.Print () ;
// нужно упаковать значение IPrint printer = myval;
printer.Print ();

Первый вызов Print выполняется через ссылку на значение, что не требует упаковки. Однако второй вызов Print осуществляется через интерфейс. В точке получения интерфейса происходит упаковка. Первым делом это все выглядит так, будто без упаковки можно было бы обойтись, если применять явную ссылку на тип интерфейса.

В данном случае это верно, поскольку Print также является частью общедоступного контракта MyValue. Однако если реализовать метод Print как явный интерфейс, то единственным способом вызова метода будет применение ссылки на интерфейсный тип.

Поэтому важно отметить, что всякий раз, когда вы реализуете интерфейс на типе значений явно, то тем самым вынуждаете клиентов типа значений упаковывать его перед вызовом через этот интерфейс.

Сказанное демонстрируется в следующем примере:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public interface IPrint {
void Print ();
}
public struct MyValue : IPrint {
public int x;
void IPrint.Print ()
{
System.Console.WriteLine ( "{0}", x );
}
}
public class EntryPoint {
static void Main() {
MyValue myval = new MyValue () ;
myval.x = 123;
// нужно упаковать значение
IPrint printer = myval;
printer.Print();
}

В качестве другого примера рассмотрим поддержку типом System.Int32 интерфейса IConvertable. Большинство методов интерфейса IConvertable реализовано явно. Поэтому, даже если вы хотите вызвать метод IConvertable, такой как Iconvertible. ToBoolean, на простом int, то сначала должны упаковать его.

На заметку! Обычно для выполнения преобразований, подобных упомянутым ранее, вы будете полагаться на внешний класс System.Convert. Вызов через IConvertable описан только в качестве примера.

Эффективность и путаница

Как и можно было ожидать, упаковка и распаковка — не самые эффективные операции в мире. Еще хуже то, что компилятор С# молча выполняет эти операции. Вам действительно нужно знать о том, когда происходит упаковка.

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

Например, рассмотрим контейнерный тип, подобный System.Collection.ArrayList. Все значения он хранит в виде ссылок на тип object. Если требуется вставить в него множество типов значений, то все они будут упакованы! К счастью, обобщения, которые появились в С# 2.0 и .NET 2.0, помогут преодолеть эту неэффективность. Однако всегда необходимо помнить о том, что упаковка — неэффективная операция, которой следует избегать, где только возможно.

К сожалению, поскольку упаковка — неявная операция в С#, ее обнаружение требует острого глаза. Наилучший инструмент, который можно для этого применить, если возникают сомнения в ее наличии или отсутствии — это ILDASM. С помощью ILDASM можно просмотреть код IL, сгенерированный для методов, и легко обнаружить там операции упаковки. Утилита ILDASM.exe находится в папке \bin комплекта .NET SDK.

Как упоминалось ранее, распаковка — обычно явная операция, происходящая во время приведения от ссылки на объект упаковки к типу упакованного значения. Тем не менее, в одном случае распаковка все-таки бывает неявной. Вспомните, что говорилось об отличии в поведении ссылки this внутри методов классов и внутри методов структур.

Главное отличие в том, что для типов значений ссылка this действует как параметр ref или out — в зависимости от ситуации.

Так что при вызове метода на типе значения скрытый параметр this внутри метода должен быть управляемым указателем, а не ссылкой.

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

Операции распаковки в CLR неэффективны сами по себе. Эта неэффективность происходит из того факта, что С# обычно комбинирует операцию распаковки с операцией копирования значения.

Вы можете следить за любыми ответами на эту запись через RSS 2.0 ленту. Вы можете оставить ответ, или trackback с вашего собственного сайта.

Оставьте отзыв

XHTML: Вы можете использовать следующие теги: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

 
Rambler's Top100