Programozzunk C#-ban – 4. rész

A C# sorozat tovább folytatódik. Ezúttal az Objektum modellt tekintjük át részletesebben, megnézzük az Absztrakt osztályokat és a felületeket.

Objektum modell

C# esetén minden osztály implicit módon (külön jelzés nélkül) az object ősosztályból származik. A Java-hoz hasonlóan itt is csak egyszeres öröklődésre van lehetőség, vagyis egy osztálynak egy ősosztálya lehet. Cserébe viszont végtelen sok felületet megvalósíthat egy osztály.

Az Object ősosztály 3db függvényt biztosít, amelyek felülírhatóak a származtatott osztályokban. Mivel minden objektum (a struktúrák is) az object-ből származtatott, a következő függvények minden osztály esetén rendelkezésre állnak:

  • ToString()
    Szöveggé alakítja az objektumot.
  • Equals(object obj)
    Egy másik objektumhoz képest a jelenlegi objektum egyenlőségét vizsgálja. A keretrendszer belső működéséhez kell. Önmagában ezen függvény korrekt megvalósítása még nem fogja működőképessé tenni az objektumon az == és != operátorokat, mivel ezeket is felül kell írni.
  • GetHashCode()
    32 bites Hash értéket kell visszaadnia int típusban. A keretrendszer belső működéséhez kell. Ennek segítségével  működik a switch-case szerkezet a szövegeken, illetve a szótár objektumok esetén kell nagyon. Implemenetálni lehet úgy, hogy az osztály adattagjainak hash értéke között XOR művelet végzünk. Használható két objektum egyenlőségének vizsgálatára is.

A függvények az override kulcsszó segítségével írhatóak felül. Az alábbi példa ezt mutatja meg:

using System;
 
namespace _06_object
{
    class Ojjektum
    {
        public int Ertek;
 
        public Ojjektum(int value)
        {
            Ertek = value;
        }
 
        public override string ToString()
        {
            return "Én egy ojjektum vagyok. Értékem: " + Ertek.ToString();
        }
 
        public override bool Equals(object obj)
        {
            if (obj is Ojjektum) return obj.GetHashCode() == this.GetHashCode();
            return false;
        }
 
        public override int GetHashCode()
        {
            return Ertek.GetHashCode();
        }
    }
 
    class Program
    {
        static void Main(string[] args)
        {
            Ojjektum a = new Ojjektum(4);
            Ojjektum b = new Ojjektum(5);
            Ojjektum c = new Ojjektum(4);
 
            Console.WriteLine("Ojjektum a: {0}", a);
            Console.WriteLine("Ojjektum b: {0}", b);
            Console.WriteLine("Ojjektum c: {0}", c);
 
            Console.WriteLine("Ojjektum a és b egyenlősége: {0}", a.Equals(b));
            Console.WriteLine("Ojjektum a és c egyenlősége: {0}", a.Equals(c));
            Console.WriteLine("Ojjektum b és c egyenlősége: {0}", b.Equals(c));
 
            Console.ReadKey();
        }
    }
}

Felülírni csak olyan függvényeket lehet, amelyek az ősosztályban virtual kulcsszóval rendelkeznek. Azon osztályokból nem lehet örököltetni, amelyek sealed kulcsszóval vannak ellátva.

A static kulcsszóval megjelölt osztályok csak static függvényeket tartalmazhatnak, amelyek az osztályhoz kötöttek, nem pedig az objektumpéldányhoz.

Ezen osztályok rendelkezhetnek konstruktorral, de az paramétert nem vehet át és direkt módon nem hívható meg. A statikus osztály konstruktora az első statikus metódus hívása előtt fog lefutni és a konstruktort is static kulcsszóval kell megjelölni.

using System;
 
namespace _07_statikus_objektum
{
    static class Statikus
    {
        private static int Ertek; 
 
        static Statikus()
        {
            Ertek = 44;
        }
 
        public static void Fuggveny()
        {
            Console.WriteLine("Statikus függvény, statikus osztályban. A tárolt érték: {0}", Ertek);
        }
    }
 
    class Program
    {
        static void Main(string[] args)
        {
            Statikus.Fuggveny();
            Console.ReadKey();
        }
    }
}

Az osztályok elérési szerepköre a névtéren belül szabályozható a public, private és internal kulcsszavakkal.

A public megjelölésű osztályok a névtéren kívülről is meghívhatóak, illetve a szerelvényen kívülről is. Ha a programunk a using szekcióban használ egy szerelvényt, akkor a szerelvény public tagjai a szerelvényen kívül is használhatóak.

A private megjelölésű osztályok csak az adott névtéren belül látszódnak és hívhatóak meg. Szerelvényen kívülről csak akkor hívhatóak meg, ha a másik szerelvény is szintén ugyanazt a névteret bővíti.

Az internal megjelölésű osztályok csak az adott szerelvényen belül használhatóak. A szerelvényen belül az internal megjelölésű elemek úgy viselkednek, mintha publikusak lennének.

Absztrakt osztályok és felületek

Az absztrakt osztályok olyan osztályok, amelyek nem példányosíthatóak, csak öröklésben vehetnek részt. Tipikusan ősosztályok létrehozásában alkalmazhatóak.

Az absztrakt osztály abstract kulcsszóval jelölt. tartalmazhat normál függvényeket, adattagokat, tulajdonságokat és ezek absztrakt változatait. Az absztrakt módosítóval megjelölt függvényeket és adattagokat a származtatott osztálynak implementálnia kell. Az absztrakt osztályok esetén az absztrakt adattagok hozzáférési szintjei (public, private, protected) ugyanúgy szabályozhatóak, mint más adattagok esetén.

A felületek egy kicsit hasonlítanak az absztakt osztályokhoz. Ezekből korlátlan számút megvalósíthat az osztály. A felületben tulajdonságok és metódusok adhatóak meg. A felület létrehozásakor ezek hozzáférés módosítóval nem jelölhetőek meg, mivel a felület által biztosított függvényeket az osztálynak publikus hozzáférési szinttel kell implementálnia. A keretrendszer számos felületet tartalmaz, amelyek kiemelt jelentőséggel bírnak. A felületek elnevezésénél az ajánlott szabály az, hogy a felület nevét I betűvel kezdjük.

Fontos felületek a keretrendszerben:

  • IDisposable
    Minden olyan osztálynak implementálnia kell, amely rendelkezik IDisposeable elemekkel. Arra használt, hogy a nem menedzselt memória (tipikusan Windows API hívások eredményei által foglalt memóriaterületek) felszabadítása helyesen megtörténjen és ne legyen memóriaszivárgás az alkalmazásban.
  • IEnumerable
    Minden adattároló osztálynak ajánlott implementálnia, lehetővé teszi a foreach ciklus használatát, illetve a LINQ kérésekhez is kell (A beépített tároló osztályok implementálják).
  • IQueryable
    LINQ kérésekhez kell, tároló osztályoknak ajánlott implementálni (A beépített tároló osztályok implementálják).
  • IComparable, IComparer
    Két objektum összehasonlítását teszik lehetővé.
  • IEquatable, IEqualityComparer
    Két objektum egyenlőségének vizsgálatát teszik lehetővé.
  • ICollection
    Tároló osztályok általános függvényeit biztosítja az egységesség kedvéért.

Az absztrakt osztályok és felületek függvényeinek és tulajdonságainak implementálását a Visual Studio nagymértékben megkönnyíti. A kódban, miután meghatároztuk, hogy az osztályunk melyik absztrakt osztályból öröklődik, illetve mely felületeket valósítja meg, egy egyszerű jobb egérgomb kattintás után felkínálja lehetőségként az adott osztály/felület függvényeinek implementálását. Ezen menüpontokra kattintva a szükséges kódrészletek legenerálódnak, nekünk csak a tényleges működési kódot kell megírnunk.

absztraktfelulet

Felületek implementálhatóak implicit és explicit módon. Az explicit implementáció esetén a felület tagjai előtt szerepel a felület neve is. Erre azért lehet szükség, mert előfordulhat, hogy egy osztály megvalósít két különböző felületet, amelyek azonos néven publikálnak függvényeket. Ekkor jelezni kell implementáció során, hogy melyik felületet valósítjuk meg.

Az alábbi példa az absztrakt osztályok és felületek használatát mutatja be:

using System;
 
namespace _08_absztrakt_felulet
{
    public abstract class AbsztraktOsztaly
    {
        public abstract void Bemutatkozas();
    }
 
    public interface IFelulet
    {
        string FeluletNeve();
    }
 
    public class Implementacio: AbsztraktOsztaly, IFelulet
    {
 
        public override void Bemutatkozas()
        {
            Console.WriteLine("Ez egy absztrakt osztályból örököltetett Implementáció");
        }
 
        public string FeluletNeve()
        {
            return "Az implentáció megvalósítja az IFelület felületet";
        }
    }
 
    class Program
    {
        static void Main(string[] args)
        {
            Implementacio teszt = new Implementacio();
            teszt.Bemutatkozas();
            Console.WriteLine(teszt.FeluletNeve());
            Console.ReadKey();
        }
    }
}

Az alábbi példa pedig az implicit és explicit felület implementációt:

using System;
 
namespace _09_felulet_explicit
{
    interface Elso
    {
        void Bemutatkozik();
    }
 
    interface Masodik
    {
        void Bemutatkozik();
    }
 
    class Test: Elso, Masodik
    {
        public void Bemutatkozik()
        {
            Console.WriteLine("Én az első felület metódusa vagyok");
            Console.WriteLine("Engem implicit módon deklaráltak");
            Console.WriteLine("Ezért én vagyok az alapértelmezett");
            Console.WriteLine("Ha a Bemutatkozik() metódust hívják");
            Console.WriteLine();
        }
 
        void Masodik.Bemutatkozik()
        {
            Console.WriteLine("Én a második felület metódusa vagyok");
            Console.WriteLine("Ezért engem csak explicit módon lehetett implementálni");
            Console.WriteLine("Illetve engem csak típus konverzió után lehet meghívni");
            Console.WriteLine();
        }
    }
 
    class Program
    {
        static void Main(string[] args)
        {
            Test teszt = new Test();
            teszt.Bemutatkozik();
            (teszt as Masodik).Bemutatkozik();
            Console.ReadKey();
        }
    }
}

A cikkhez tartozó példakódok a https://github.com/webmaster442/csharppeldak címen szerezhetőek be.