fel
le

Boxing, Unboxing

Mint ismert, a reference típusú változók 4 byte-on helyezkednek el, és egy memóriacímet tárolnak, ahol a hozzájuk tartozó tényleges érték tárolásra került. Ezzel szemben egy value típusú változó annyi byte-ot foglal el, amennyit az adott típus igényel, és ezen a területen közvetlenül tárolódik el a változó aktuális értéke.

Két reference típusú változó közötti értékadás során csak a 4 byte-on tárolt memóriacím másolódik át, így innentől kezdve a két változó ugyanarra az értékre mutat, bármelyiken keresztül az érték módosítható.

Két value típusú változó közötti értékadás során a tárolt érték átmásolódik, de a továbbiakban a két változónak "nincs közük" egymáshoz, bármelyik értékének módosítása semmilyen kihatással nincs a másik változó értékére.

Az is ismert, hogy az Object típus minden más típus ősosztályának minősül, így minden más típusú érték elhelyezhető egy Object típusú változóban, és megfelelő típuskonverzió révén onnan vissza is olvasható.

Az is ismert ,hogy az Object típus mint olyan, reference típuskategóriájú.

A fentiek ismeretében hogyan kell értelmezni az alábbi o=d értékadó utasítást?

double d = 12.4;
Object o = d;
 

Az biztosan kijelenthető, hogy az o=d értékadó utasítás típushelyes, hiszen a jobboldali típus (double) kompatibilis a bal oldali (Object) típussal. Azonban az o változó referencia-típusú, vagyis egy memóriacímet lehet beletölteni, de a jobb oldalon nem memóriacím áll, hanem egy lebegőpontos számérték.

Boxing

Amikor egy referencia-típusú változóba érték típusú (value type) értéket kívánunk elhelyezni, akkor a háttérben egy automatikus előkészítő lépés hajtódik végre, melyet boxing-nak, "becsomagolásnak" nevezünk.

Ennek során a memóriában kialakításra kerül egy megfelelően nagy méretű memóriaterület, melybe átmásolódik az érték (duplikálódik), majd ennek a memóriacíme kerül be a referencia típusú változóba:

1. ábra: Másolat készitese a boxing közben

Ez persze azt is jelenti, hogy a o=d értékadás elvégzése után a d változó értéke szabadon módosítható, az o változóban tárolt 12.4 értéket ez már nem fogja érinteni.

Ez a művelet automatikusan (implicit) lezajlik. Ismerete azért is fontos, mert egyrészt plusz memóriát köt le, másrészt időbe is kerül (memória-másolás), vagyis mindenképpen "költséges" művelet.

Unboxing

Sejthető, hogy a fenti műveletnek van ellentételes párja is:

double d = 12.4;
Object o = d;
...
double c = (double)o;
 

A c=(double)o értékadás a fenti esetben típushelyes, hiszen az o változóban most éppen egy double típusú érték van tárolva. A típuskonverzióra azért van szükség, mert e nélkül a jobb oldal típusa (Object) nem lenne kompatibilis a bal oldal típusával (double).

Mi történik ennek hatására. Nyilván nem az, hogy az o változó által tárolt memóriacímet mint egy lebegőpontos számot fogja fel a rendszer, és helyezi el a c változóba. Sokkal inkább az o által mutatott memóriaterületen lévő double érték visszanyeréséről van szó.

Alaposabban megvizsgálva a fenti értékadást, azt láthatjuk, hogy a bal oldalon egy value típusba egy reference típusú értéket próbálunk elhelyezni. Ennek során a rendszer "elballag" a referencia típusú változó által jelzett memóriaterületre, és ha az ott tárolt érték típusa megfelel a típuskonverziónak, akkor azt onnan visszamásolja a fogadó változó (c) memóriaterületére.

Ez a lépés automatikusan (implicit) lezajlik, a boxing művelet ellentéte, a tárolt érték "kicsomagolása", ezért ezt a lépést unboxing-nak nevezzük.

Boxing és Unboxing a gyakorlatban

A fentivel teljesen megegyező lépéssorozat zajlik le például az ArrayList használata során. Az ArrayList osztály .Add(...) metódusának paramétere ugyanis Object típusú:

class ArrayList
{
 public void Add( Object o )
 {
   ....
 }
}
// használata
ArrayList l = new ArrayList();
int x = 12;
l.Add( x );
 

A fenti l.Add(x) metódushívás során a háttérben az o=x hajtódik végre (a paraméterváltozó felveszi a hívás helyén feltüntetett értéket). Ez gyakorlatilag egy boxing műveletet is jelent a fenti példában.

Amikor az ArrayList-ből kiveszünk egy benne korábban elhelyezett értéket, akkor pedig ennek fordítottja zajlik le. Az ArrayList indexelője ugyanis Object típusú értéket ad vissza:

class ArrayList
{
 public Object this[int index]
 {
   get
   {
       ....
   }
 }
}
// használata
ArrayList l = new ArrayList();
int x = 12;
l.Add( x );
...
int y = (int)l[0];
 

Az y = (int)l[0] során az jobb oldalon található Object típusú értékre egy unboxing lépés is végrehajtódik, hogy a tárolt int típusú értéket az y változóba vissza tudjuk nyerni.

A Generic típusok haszna

A fenti példa is bizonyítja, hogy ArrayList-ben int típusú értéket tárolni igen gazdaságtalan:

  • a lista nem magát az int értéket tárolja, csak annak a memóriacímét
  • berakáskor boxing művelet zajlik a háttérben, az int típusú érték lemásolásra kerül
  • kiolvasáskor egy unboxing művelet zajlik le

Így egy int típusú érték tárolásának memóriaigénye relatíve nagyon magas, egyrészt maga az int érték tárolásán felül (4 byte) igényel egy memóriacím tárkapacitását is (szintén 4 byte).

A fenti nem túl hatékony megoldás helyett ezért ilyen esetben használjuk a Generic lista típust, mely value type típusú értékek tárolására minden szempontból sokkal hatékonyabb megoldást nyújt, és egyéb előnyökkel is rendelkezik!

Hernyák Zoltán
2013-03-17 18:47:36