A InfoWiki wikiből
WikiSysop (vita | szerkesztései)
(Új oldal, tartalma: „<cim cim3="Az IS és AS operátorok" cim2="Objektum-orientált programozás" cim1="Magasszintű Programozási Nyelvek II." prev="mp3/ea16" next="mp3/ea18" back...”)
Újabb szerkesztés →
Aktuális változat
Tartalomjegyzék |
Az IS és AS operátorok
A típuskompatibilitási szabály szerint a gyermekosztály mint típus kompatibilis az ős osztályával, mint típussal. A típuskompatibilitás egyirányú, vagyis a gyermek kompatibilis az ősével, de az ős nem kompatibilis a gyermekosztállyal.
Ha egy 'B' típus kompatibilis egy 'A' típussal, akkor egy 'A' típusú változó felvehet egy 'B' típusú értéket.
Egy változó deklarálásakor megadott típusás statikus típusnak nevezzük. Ebbe a változóba ettől eltérő típusú példány referenciája is bekerülhet. Ez utóbbit a változó dinamikus típusának nevezzük.
class Negyzet { ... } class Teglalap: Negyzet { ... } // ...... Negyzet a; Teglalap b = new Teglalap(); a = b;
A fenti kódban az 'a' változó statikus típusa 'Negyzet', de az 'a=b' értékadás után a dinamikus típusa 'Teglalap'.
A null érték
Minden referencia típusú változó képes egy speciális referencia, a 'null' referencia tárolására. Ezen érték azt jelöli, hogy a változó nem tárol hasznos memóriacímet. Az ilyen változókra nem alkalmazható a '.' (pont) operátor, nem választható ki rajta keresztül mező, nem hívható meg rajta keresztül metódus (értelemszerűen). Ha mégis megpróbálnánk - NullReferenceException-t kapnánk, futási hibát. Ezt sajnos a fordítóprogram nem tudja kiszűrni a forráskódból, mivel csak futás közben derül ki, hogy az adott változónak 'null'-e az aktuális értéke.
Ha egy változóban előfordulhat 'null' érték, akkor használata előtt javasolt azt ellenőrízni és kizárni:
public static void Akarmi(Negyzet n) { if (n==null) return; Console.WriteLine( n.Kerulet() ); }
A típusellenőrzés (IS)
Felmerülhet igényként, hogy a kódból lehetőségünk legyen ellenőrízni, hogy egy adott változó dinamikus típusa milyen osztályhoz tartozik. Ezt az 'is' operátorral tudjuk elvégezni. Használata:
<változónév> 'is' <osztálynév>
Az 'is' operátor megvizsgálja, hogy a megadott változóban lévő példány aktuális típusa 'kompatibilis-e' a megadott osztálynévvel. Ennek megfelelően logikai 'true' vagy 'false' értéket állít elő.
public static void Akarmi(Negyzet n) { if (n==null) return; if (n is Teglalap) Console.WriteLine( "De hisz ez egy téglalap!" ); else Console.WriteLine("Ez nem téglalap"); }
Jegyezzük meg, hogy az 'n is Teglalap' feltétel nem azt jelöli, hogy az 'n' értéke egy 'Teglalap' példány, hanem egy 'Teglalap' kompatibilis példány. Vagyis lehet akát a 'Teglalap' valamely leszármazott osztályának példánya is!
public static void Akarmi(Negyzet n) { if (n==null) return; if (n is Teglalap) Console.WriteLine( "De hisz ez egy téglalap!" ); else Console.WriteLine("Ez nem téglalap"); }
Tegyük fel, hogy a fejlesztési fánk az alábbi felépítésű:
Milyen vizsgálatoknak nincs értelme a fenti függvény belsejében?
public static void Akarmi(Negyzet n) { if (n is Teglalap) Console.WriteLine( "Ez lehet Teglalap, Paralelogramma vagy Trapez" ); if (n is Negyzet) Console.WriteLine("Ez egy negyzet"); if (n is Sokszog) Console.WriteLine("Ez egy sokszog"); if (n is Kor) Console.WriteLine("Ez egy kor"); if (n is Object) Console.WriteLine("Ez egy object"); }
A fenti ellenőrzések közül igazából csak az elsőnek van értelme ('is Teglalap'), hiszen az 'n' értéke valóban lehetséges, hogy ezen gyermekosztályból származik, de van esélye annak is, hogy nem ebből származik.
A további vizsgálatok azonban valójában haszontalanok, néhol értelmetlenek:
- az 'n is Negyzet' mindíg 'true' értékű, hiszen az 'n' változónk statikus típusa is ez, és a fordítóprogram figyel arra, hogy semmilyen értékadó utasítással ne lehessen ebbe a típusának ellentmondó értéket elhelyezni.
- az 'n is Sokszog' mindíg 'true' értékű, hiszen ez típuskomaptibilitást vizsgál, és az előző megállapítás értelmében az 'n' értéke minden esetben legalább 'Negyzet', így egyúttal legalább 'Sokszog' is.
- az 'n is Kor' viszont értelmetlen, az 'n' változónkba nem kerülhet bele 'Kor' típusú érték, ez ellentmondana az értékadás szabályának, nem helyezhetünk el egy változóba egy olyan értéket, amellyel nem kompatibilis.
- az 'n is Object' szintén mindíg 'true' értékű, csakúgy mint az 'n is Sokszog'.
A típusellenőrzés és a null
Amennyiben egy változó értéke 'null', úgy minden rá vonatkozó "értelmes" 'IS' vizsgálat igaz (true) eredményt fog adni. Ezt így is fel lehet fogni, hogy a 'null' érték minden referencia-típussal kompatibilis.
A típuskényszerítés (AS)
Amennyiben az 'is' operátorral felderítettük, hogy egy változónk dinamikus típusa pontosan mi is,
úgy felmerülhet bennünk az, hogy ezt ki is használjuk.
Tegyük fel, hogy a 'Negyzet' osztály tartalmaz egy 'A_oldal' nevű írható-olvasható property-t, melyen keresztül be lehet állítani a négyzet oldalának értékét. A 'Teglalap' osztály ezt örökli, de ezen felül értelemszerűen egy 'B_oldal' nevű property-vel is rendelkezik:
public static void Akarmi(Negyzet n) { n.A_oldal = 12; if (n is Teglalap) n.B_oldal = 20; }
Az 'n.A_oldal=...' értékadással nincsen semmi probléma, a '.' (pont) operátor a statikus típussal dolgozik, az 'n' változó statikus típusa 'Negyzet', annak van 'A_oldal' property-je, az 'int' típusú, az érték ('12') is 'int' típusú, az értékadás végrehajtható.
A második értékadással azonban komoly gondok vannak. Hiába ellenőrízzük le az 'n' dinamikus típusát, hiába kapjuk azt, hogy az 'Teglalap', a 'n.B_Oldal=...' értékadásunk hibás! Miért is? Egyszerű: a '.' (pont) operátor a "statikus" típussal dolgozik! Hiába ellenőríztük le az 'if' feltételében hogy a dinamikus típusunk jelenleg 'Teglalap'-e, a '.' operátor nem fogja "visszafele elolvasni" a forráskódunkat. A '.' operátor függetlenül működik az őt megelőző kódtól, ennek értelmében ő továbbra is csak annyit "lát", hogy az 'n' statikus típusa 'Negyzet', márpedig egy négyzetnek nincs 'B_oldal' mezője.
Segítenünk kell a '.' operátornak: típuskényszerítést kell alkalmaznunk. A típuskényszerítésnek két formája létezik. A hagyományos forma:
if (n is Teglalap) ((Teglalap)n).B_oldal = 20;
Illetve az 'as' operátor segítségével:
if (n is Teglalap) (n as Teglalap).B_oldal = 20;
A hagyományos forma szerint a változó neve elé zárójelben odaírjuk a kívánt típus nevét '(Teglalap)n'. Ilyen módon konvertáljuk általában az érték típusokat (value-type), például '(int)a', vagy '(double)f'. Ezen típuskényszerítés forma már a 'C' nyelvben is létezett ebben az alakjában, ezért hívjuk hagyományos formának.
A hagyományos forma használata OOP környezetben, a '.' operátor mellett nehézkes, mivel a típuskényszerítő operátor, és a pont operátor egyetlen kifejezésben fordul így elő. Ennek megfelelően figyelmbe kell venni az operátorok precedenciáját. Sajnos (vagy szerencsére) a pont operátor precedenciája erősebb mint a hagyományos típuskényszerítő operátor precedenciája, így a '(Teglalap)n.B_oldal' alakja hibás lenne. Ennek jelentése: vedd az 'n' példány 'B_oldal' mezőjét, és ezen mezőnek a típusát módosítsd 'Teglalap'-ra. Ebben két hiba is van: az 'n' példánynak nincsen 'B_oldal' mezője (épp ezen küzdünk most), másrészt még ha lenne is, annak típusa 'int' lenne, melyet nem lehet 'Teglalap'-ra típusmódosítani.
Ezért hagyományos típuskényszerítés esetén még egy zárójellel kell rásegíteni: '((Teglalap)n).B_oldal'. Ez utóbbit úgy értelmezi a fordító, hogy az 'n' típusát módosítsuk 'Tegalalap'-ra, majd az így kapott példánynak érjük el a 'B_oldal' mezőjét.
Az 'as' operátor használata végül is ugyanezt eredményezi, de jobban olvashatóbb, egyszerűbb formában: '(n as Teglalap).B_oldal'.
Jegyezzük meg, hogy:
- a hagyományos formájú típuskényszerítés mind a reference, mind a value type esetén használható!
- a pont operátorral együtt alkalmazva extra zárójelet kell kitennünk a precedencia-szintek miatt
- az 'as' operátor azonban csak a reference type esetén működik!
Az utolsó megjegyzés értelmében nem lehet ilyet írni: 'x as int', mivel az 'int' az nem referencia típusú, nem alkalmazható rá az 'as' operátoros típuskényszerítés. Ennek oka fontos működési különbségben rejlik: "a pont operátor segítségével végzett típuskényszerítés - amennyiben az nem kivitelezhető - Exception kiváltását jelenti! Az 'as' operátoros típuskényszerítés ez esetben 'null' értéket állít elő, és nem okoz Exception-t. Ezért nem alkalmazható az 'as' value type-ok esetén, mivel annak értéke nem lehet 'null'."
A típuskényszerítés veszélyei
Bizonyos típuskényszerítések eleve lehetetlenek. A fenti példánál maradva az 'n as Kor' biztosan lehetetlen, az 'n' példányba sohasem kerülhet 'Kor' típusú példány referenciája. Az ilyen jellegű értelmetlen típuskényszerítéseket a fordító már fordítási időben észreveszi, és szintaktikai hibának jelzi.
Bizonyos típuskényszerítések értelmetlenek. Az 'n as Object', vagy az 'n as Sokszog' például lehetséges, de gyakorlatilag értelmetlen. A típuskényszerítések célja a típus pontosítása. Ennek során a statikus típust "javítani" szoktuk, nem "rontani". Az 'n as Sokszog' során az 'n' típusát "butítjuk", egy korábbi, fejletlenebb szintre toljuk le. Ez lehetséges, de a gyakorlati életben nem fordul elő.
A szokásos alkalmazása a típuskényszerítéseknek a típus "feljavítása", vagyis egy gyermek-típusra alakítása. Ez azonban nem veszélytelen!
Amennyiben a hagyományos forma segítségével kényszerítjük egy változó típusát egy gyermekosztályéra, úgy két eset lehetséges:
- a példányunk dinamikus típusa a típuskényszerítéssel kompatibilis típus, a típuskényszerítés során nem lép fel hiba. Ez az ellenőrzés bele van értve a pont operátor működésébe.
- a példány dinamikus típusa a kért típuskényszerítést nem teszi lehetővé. Ekkor futási hiba, InvalidCastException lép fel. Kezeletlen hiba a program leállásához vezet!
Amennyiben az 'as' operátor segítségével kényszerítjük egy változó típusát egy gyermekosztályéra, úgy két eset lehetséges:
- a példányunk dinamikus típusa a típuskényszerítéssel kompatibilis típus, a típuskényszerítés során nem lép fel hiba. Ez az ellenőrzés bele van értve a pont operátor működésébe.
- a példány dinamikus típusa a kért típuskényszerítést nem teszi lehetővé. Ekkor a típuskényszerítés eredménye a 'null' érték. Mivel azonban gyakori, hogy a típuskényszerített értékre azonnal alkalmazunk valamely mezőkiválasztást, vagy metódushívást - a null eredmény esetén ezek Exception-t okoznak!
Az Exception-ok felbukkanásától való félelem miatt javasolt minden típuskényszerítés előtt annak sikerességét előre) az 'is' operátorral ellenőrízni:
if (n is Teglalap) { (n as Teglalap).B_oldal = 20; }
Vagyis az 'as' operátort akkor alkalmazzuk, ha az 'is' operátor segítségével annak sikerességéről meggyőződtünk. Ha az 'is' operátor szerint az 'n' típusa kompatibilis a 'Teglalap' típusával, akkor az 'n as Teglalap' már garantáltan nem okoz majd 'null' értéke, és a mezőkiválasztás sem fog Exception megjelenését okozni.
A null érték típuskényszerítése
Ha a fenti esetben az 'n' értéke 'null' lenne, akkor azonban újra gond lenne: a 'null' érték ugyanis típuskompatibilis gyakorlatilag minden referencia-típussal, vagyis az 'is' operátor 'true' értéket ad a vizsgálat esetén. Az 'as' operátor sem ad hibát ugyanezen oknál fogva, a típuskényszerítés is végrehajtható. Azonban a 'pont' operátor a 'null' értéknél nem tud működni, ezért ott futási hibát, 'NullReferenceException'-t kapunk. A helyes, biztos megoldás, hogy a 'null' értéket is ellenőrízzük le:
if (n!=null && n is Teglalap) (n as Teglalap).B_oldal = 20;