C# esetén minden objektum az Object
osztályból származik. A közös ős típus előnye, hogy ezen típus felhasználásával tudunk olyan metódusokat, kódrészleteket írni, amelyek univerzálisak, vagyis típus megkötés nélkül működnek. Ez azért lehetséges, mivel a keretrendszer és a C# fordító egy ilyen metódus híváskor képes átalakítani egy adott érték típust Object
típusra. Ezt a folyamatot nevezzük boxing-nak, magyarul dobozolásnak.
int x = 33;
object o = x; //boxing
Ahogy a példán látható, a dobozolás folyamata implicit, külön jelzést nem igényel. A kódrészletben x típusa egész szám. Mivel minden típusnak az őse object
, ezért x konvertálható az ősévé, vagyis egy sima object
objektummá. A boxing folyamat közben x tulajdonságai ugyanúgy megmaradnak, csak egy speciális memóriaterületen tárolódnak.
Az unboxing folyamat, az, amikor egy dobozolt értéket konvertálunk vissza az eredeti típusára. Ez a folyamat explicit jelzést kíván, mivel a dobozolás folyamatában keletkezik egy object
és egy speciális memóriaterületen az eredeti típus tulajdonságai, amiket hozzáadott az object
típushoz. Viszont az object
típusban nem tárolódik semmi extra információ arról, hogy mi is volt az eredeti típus, ezért nekünk kell ezt a kódban jeleznünk.
A fenti példát folytatva:
int szam = (int)o; //unboxing
A folyamat során a korábban leválasztott tulajdonságok és az object
objektum kombinálódnak vissza az eredeti típusra. Az eredeti típust észben kell tartanunk, mivel ha rossz típust adunk meg, akkor a konverzió nem fog sikerülni.
var szam2 = (short)o; //hibát fog eredményezni
//jelen esetben kivédhető a Convert osztály használatával
A hiba futás időben fog jelentkezni InvalidCastException
formában, mivel egy nem kompatibilis típus konverziót próbálunk kikényszeríteni.
Ha ezt el szeretnénk kerülni és biztonságosan szerenténk típusok között konvertálni, akkor használhatjuk az as
kulcsszót. Ez attól lesz biztonságos, hogy ha a konverzió nem sikerül, akkor az as
konverzió eredménye null
lesz, amit lekezelhetünk.
Fontos megjegyezni, hogy ez a módszer csak referencia és nullable
típussal működik, illetve generikusoknál csak akkor, ha van megkötésünk a típusra (erről majd később részletesen lesz szó).
A használata rendkívül egyszerű:
var a = b as T
if (a==null)
{
// a cast nem sikerült
}
else
{
//az a változó T típusú
}
A kód egyébként egyenértékű azzal, mintha azt írnám, hogy
var a = b is T? (T)b : (T)null
Igyekezzünk az as
kulcsszóval konvertálást csak akkor alkalmazni, ha mindenképpen szükségünk van a futás idejű konverzióra és nem tudjuk a konverziónkat fordítási időben kiváltani egy explicit konverzióval. Az explicit konverzió minden esetben gyorsabb, mint egy as
hívás, mivel az as
operátor futás előtt még típust is ellenőriz.
Az alábbi kódrészlet a boxing és unboxing lehetséges hibáinak kezelését mutatja be:
using System;
namespace PeldaBoxingAs
{
class Program
{
static void Main(string[] args)
{
int ertek = 11;
object boxed = ertek;
if (boxed is int)
{
//explicit unbox
int unboxed = (int)boxed;
Console.WriteLine("A csomagolt adat int. Erteke: {0}", unboxed);
}
try
{
//mivel úgyis hibát dob
short short_unbox = (short)boxed;
//ez nem fog lefutni
Console.WriteLine(short_unbox);
}
catch (InvalidCastException)
{
//nem szép megoldás, igyekezzünk ne így használni.
Console.WriteLine("Konvertálási hiba");
}
//csak referencia típusok és nullable esetén
//használható az as operátor!
short? short_unbox2 = boxed as short?;
if (short_unbox2 == null)
{
Console.WriteLine("Short-ra nem sikerült konvertálni");
int? un_int = boxed as int?;
Console.WriteLine("A csomagolt adat int. Erteke: {0}", (int)un_int);
}
Console.ReadLine();
}
}
}
A program kimenete:
A csomagolt adat int. Erteke: 11
Konvertálási hiba
Short-ra nem sikerült konvertálni
A csomagolt adat int. Erteke: 11
A példában az as operátornál látható, hogy ha nullable típusra van átalakítva egy érték típus, akkor használható konvertálásra. Valójában a nullable típusra konvertálás egy objektumot hoz létre, amelybe az értéktípusunk tagváltozóként ágyazódik be.
A tagváltozó értékét a Value tulajdonságon keresztül is lekérdezhetjük, illetve az ilyen objektumok rendelkeznek egy HasValue
tulajdonsággal is, amely nullable bool típusú. Ezen keresztül azt tudjuk lekérdezni, hogy a Value által tárolt érték érvényes-e vagy sem.
A boxing és unboxing a sebességre negatív hatással van értéktípusok esetén. Mégpedig azért, mert a boxing folyamat során átkerül a stack-en tárolt értéktípusunk a heap-re egy referencia típusba (object) dobozolva. Az unboxing során az érték visszakerül a stack-re. Ez a folyamat memória másolással jár, aminek a költsége nem elhanyagolható, főleg, ha sokszor történik.