Személyes eszközök
Keresés

 

A InfoWiki wikiből



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ű:


Kép:kep.ObjektumFa_Teglalap.gif

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;
Hernyák Zoltán
A lap eredeti címe: „http://wiki.ektf.hu/wiki/Mp3/ea17
Nézetek
nincs sb_3.142.200.226 cikk