fel
le

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. Ezen (akár) eltérő típust 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-e. 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ű:

1. ábra: A fejlesztési fa

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 AOldal 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 BOldal nevű property-vel is rendelkezik:

public static void Akarmi(Negyzet n)
{
  n.AOldal = 12;
  if (n is Teglalap) n.BOldal = 20;
}
 

Az n.AOldal=... é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 AOldal 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.BOldal=... é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 BOldal 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).BOldal = 20;
 

Illetve az as operátor segítségével:

if (n is Teglalap) (n as Teglalap).BOldal = 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.BOldal alakja hibás lenne. Ennek jelentése: vedd az n példány BOldal 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 BOldal 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).BOldal. 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 BOldal 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).BOldal.

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 ritkán nem fordul elő.

Ilyen előfordulás lehet például az az eset, amikor egy fejlett objektumosztályban egy sokszorosan felüldefiniált metódusnak egy jóval korábbi ősosztálybeli változatát szeretnénk meghívni. Ekkor a (this as koraiOsosztaly).metodusom() formát írhatjuk le. Ebben a példában a this típusát kényszerítjük át a korai típusra, majd hívjuk meg az ebben az osztályban létező korai változatot.

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).BOldal = 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

Amennyiben valamely objektum típusú változóban null érték lenne, úgy ezen változónak a dinamikus típusa nem meghatározható. Az is operátorral történő típusellenőrzése biztosan false eredményt ad (a null érték nem kompatibilis semmilyen típussal).

Kacsa a = null;
 if (a is Object) Console.WriteLine("ennek ki kellene irodnia");
 // de megsem irodik ki
 
Hernyák Zoltán
2013-03-17 19:39:55