Кратко съдържание


Опаковане (boxing) и разопаковане (unboxing) на стойностни типове



страница43/73
Дата21.07.2018
Размер9.03 Mb.
#76887
1   ...   39   40   41   42   43   44   45   46   ...   73

Опаковане (boxing) и разопаковане (unboxing) на стойностни типове


Вече обяснихме, че стойностните типове се съхраняват в стека на прило­жението и не могат да приемат стойност null, докато референтните типове съдържат указател (референция) към стойност в динамичната памет и могат да бъдат null.

Понякога се налага на референтен тип да се присвои обект от стойностен тип. Например може да се наложи в System.Object инстанция да се запише System.Int32 стойност. CLR позволява това благодарение на т. нар. "опаковане" на стойностните типове (boxing).

В .NET Framework стойностните типове могат да се използват без преобразуване навсякъде, където се изискват референтни типове. При нужда CLR опакова и разопакова стойностните типове автоматично. Това спестява дефинирането на обвиващи (wrapper) класове за примитивните типове, структурите и изброените типове, но разбира се, може да доведе и до някои проблеми, които ще дискутираме по-късно.

Опаковане (boxing) на стойностни типове


Опаковането (boxing) е действие, което преобразува стойностен тип в референция към опакована стойност. То се извършва, когато е необхо­димо да се преобразува стойностен тип към референтен тип, например при преобразуване на Int32 към Object:

int i = 5;

object obj = i; // i се опакова



Всяка инстанция на стойностен тип може да бъде опакована чрез просто преобразуване до System.Object. Ако един тип е вече опакован, той не може да бъде опакован втори път и при преобразуване към System.Object си остава опакован само веднъж.

CLR извършва опаковането по следния начин:



  1. Заделя динамична памет за създаване на копие на обекта от стойностния тип.

  2. Копира съдържанието на стойностната променливата от стека в заделената динамична памет.

  3. Връща референция към създадения обект в динамичната памет.

При опаковането в динамичната памет се записва информация, че рефе­ренцията съдържа опакован обект и се запазва името на оригиналния стойностен тип.

Разопаковане (unboxing) на опаковани типове


Разопаковането (unboxing) е процесът на извличане на опакована стойност от динамичната памет. Разопаковане се извършва при преобра­зуване на опакована стойност обратно към инстанция на стойностен тип, например при преобразуване на Object към Int32:

object obj = 5; // 5 се опакова

int value = (int) obj; // стойността на obj се разопакова



CLR извършва разопаковането по следния начин:

  1. Ако референцията е null се предизвиква NullReferenceException.

  2. Ако референцията не сочи към валидна опакована стойност от съот­ветния тип, се предизвиква изключение InvalidCastException.

  3. Ако референцията е валидна опакована стойност от правилния тип, стойността се извлича от динамичната памет и се записва в стека.

За разлика от опаковането, разопаковането невинаги е успешна операция (и това трябва да се съобразява, когато се работи с опаковани стойности).

Особености при опаковането и разопаковането


При използване на автоматично опаковане и разопаковане на стойности трябва да се имат предвид някои особености:

  • Опаковането и разопаковането намаляват производителността. За оптимална производителност трябва да се намали броят на опако­ваните и разопакованите обекти.

  • Опакованите типове са копия на оригиналните стойности, поради което, ако променяме оригиналния неопакован тип, опакованото копие не се променя.

Как работят опаковането и разопаковането?


Нека имаме следния код:

int i = 5;

object obj = i; // boxing


int i2;

i2 = (int) obj; // unboxing



На картинката по-долу схематично е показано как работят опаковането и разопа­коването на стойностни типове в .NET Framework:

При опаковане стойността от стека се копира в динамичната памет, а при разопаковане стойността от динамичната памет се копира в обратно в стека.

Опакованите стойности се държат като останалите референтни типове – разполагат се в динамичната памет, унищожават се от garbage collector, когато не са необхо­дими на програмата, и при подаване като параметър при извикване на метод се пренасят по адрес.

Пример за опаковане и разопаковане


В следващия пример се илюстрира опаковането и разопаковането на стой­ностни типове, като се обръща внимание на някои особености при тези операции:

using System;
class TestBoxingUnboxing

{

static void Main()



{

int value1 = 1;

object obj = value1; // извършва се опаковане
value1 = 12345; // променя се само стойността в стека
int value2 = (int)obj; // извършва се разопаковане

Console.WriteLine(value2); // отпечатва се 1


long value3 = (long) (int) obj; // разопаковане
long value4 = (long) obj; // InvalidCastException

}

}



От примера се вижда, че разопаковане на Int32 стойност не може да се извърши чрез директно преобразуване към Int64. Необходимо е първо да се извлече Int32 стойността от опакования обект и след това да се извър­ши преобразуване до Int64.

Аномалии при опаковане и разопаковане


При работа с опаковани обекти трябва да се внимава, защото ако не бъдат съобразени някои особености, може да се наблюдава странно пове­дение на програмата. Ето един такъв пример:

using System;
interface IMovable

{

void Move(int aX, int aY);



}
///

/// Много лоша практика! Структурите не бива

/// да съдържат логика, а само данни!

///

struct Point : IMovable

{

public int mX, mY;


public void Move(int aX, int aY)

{

mX += aX;



mY += aY;

}
public override string ToString()

{

return String.Format("({0},{1})", mX, mY);



}

}
class TestPoint

{

static void Main()



{

Point p1 = new Point();

Console.WriteLine("p1={0}", p1); // p1=(0,0)
IMovable p1mov = (IMovable) p1; // p1 се опакова

IMovable p2mov = // p1mov не се опакова втори

(IMovable) p1mov; // път, защото е вече опакован

Point p2 = (Point) p2mov; // p2mov се разопакова


p1.Move(-100,-100);

p2mov.Move(5,5);

p2.Move(100,100);
Console.WriteLine("p1={0}", p1); // p1=(-100,-100)

Console.WriteLine("p1mov={0}", p1mov); // p1mov=(5,5)

Console.WriteLine("p2mov={0}", p2mov); // p2mov=(5,5)

Console.WriteLine("p2={0}", p2); // p2=(100,100)

}

}


Резултатът от изпълнение на примера е следният:

Основната причина за този резултат е фактът, че при преобразуване към интерфейс структурите се опаковат и съответно се създава копие на данните, намиращи се в тях. Опаковането е съвсем в реда на нещата, като се има предвид, че структурите са стойностни типове, а интерфейсите са референтни типове.





Препоръчва се, когато се използват структури в C#, те да съдържат само данни. Лоша практика е в структура да се дефинират методи с логика, както и структура да импле­менти­ра интерфейс.

Как работи примерът?


Да разгледаме как работи примерът. Ако съобразим разположението на стойностните и референтните променливи в паметта, можем да си обясним какво се случва:

Променливите p1 и p2 са от стойностен тип и се разпо­лагат директно в стека (и заемат по 8 байта от него).

Променливите p1mov и p2mov са от референтен тип и се разполагат в динамичната памет. В стека за тях се пазят по 4 байта, които съдържат адреса на стойността им.

С помощта на дебъгера на VS.NET можем да проследим точното разполо­жение и стойностите на тези променливи. В горната таблица е пока­зано състоя­нието им точно преди завършване на програмата.

Напомняме, че при Intel архитектурата стекът расте надолу и свършва на адрес 0x00000000.




Сподели с приятели:
1   ...   39   40   41   42   43   44   45   46   ...   73




©obuch.info 2024
отнасят до администрацията

    Начална страница