Loading presentation...

Present Remotely

Send the link below via email or IM

Copy

Present to your audience

Start remote presentation

  • Invited audience members will follow you as you navigate and present
  • People invited to a presentation do not need a Prezi account
  • This link expires 10 minutes after you close the presentation
  • A maximum of 30 users can follow your presentation
  • Learn more about this feature in our knowledge base article

Do you really want to delete this prezi?

Neither you, nor the coeditors you shared it with will be able to recover it again.

DeleteCancel

Saját osztályok implementálása

No description
by

Mónika Gál

on 9 November 2017

Comments (0)

Please log in to add your comment.

Report abuse

Transcript of Saját osztályok implementálása

Saját osztályok implementálása C#-ban
Öröklés - Metódusok
A .NET leggyakoribb típusa a class.
Az osztályok egyik legfontosabb feladata elfedni a mögöttük rejlő adatok fizikai szerkezetét, oly módon, hogy csak az adatokon végezhető műveleteket teszi láthatóvá.

class osztalyNeve {
// az osztály tagjai
}

A fentiekben leírt módon deklarált osztályból, mint egy mintából a new operátor segítségével tudunk létrehozni egy objektumot (példányt):

osztalyNeve o_osztalyNeve = new osztalyNeve();

A .NET-ben az osztály tagjai lehetnek:
adattagok
függvénytagok
jellemzők (property)
események (events)
Az osztályok
Egy osztály mezői tárolják az általa reprezentált adatokat, másképpen ezek tárolják az osztály állapotinformációit. Kétféle mezőt hozhatunk létre egy osztályon belül. Az egyik minden egyes, az osztályból létrehozott objektumpéldányban egyedileg, egymástól függetlenül létrejön, ezek a példányonkénti mezők (instance fields). – Ez az alapértelmezett típus. A másik típus közös minden objektumpéldányra, azaz osztályonként mindig csak egy van belőle. Ezek a statikus mezők (static fields).

class osztalyNeve
{
int valtozo1; // példányonként egyedi
static int valtozo2; // példányonként közös
}

A Visual Basic .NET-ben a statikus mezőt a shared kulcsszóval azonosítjuk, ami jobban kifejezi a működését, de az objektumorientált irodalom mindig a static kulcsszót favorizálja.
A metódusok az osztály mezőin végrehajtható műveleteket írják le. A mezőkhöz hasonlóan ezek is lehetnek példányonkénti metódusok vagy közös (static) metódusok, melyek közül az előbbiek mindig az osztály egy bizonyos példányán dolgoznak.

class osztalyNeve {
public int metodus1() {
// utasítások
}
static int metodus2() {
// utasítások
}
}
A mezők
A metódusok
Private
: csak az adott osztályból elérhető tagok (alapértelmezés)
Protected:
védett hozzáférési szinttel rendelkező tagok – elérhetőek a belőle származtatott osztályokban, de az osztály példányain keresztül nem.
Public:
mind a belőle származtatott osztályokban, mind az osztály példányain keresztül elérhetőek.
class osztalyNeve {
protected int valtozo1;
static int valtozo2;

public int metodus1()
{
// műveletek
return i;
}
public static void metodus2()
{
}
}
A láthatósági szintek
Az objektumorientált programozás irodalmának ajánlása szerint egy osztály adat-tagjait privát (private) vagy védett (protected) hozzáférési szinttel (hogy az osztály példányain keresztül közvetlenül ne legyenek elérhetőek), míg a metódusait a három hozzáférési szint közül az egyikkel deklaráljuk, annak megfelelően, hogy csak az osztályon belül, egy származtatott osztályban is, illetve az osztály példányain keresztül is el szeretnénk érni őket.
Az osztály publikus (public), nem közös metódusaira (esetleg mezőire) az osztály egy példányán keresztül tudunk hivatkozni:

osztalyNeve o_osztalyNeve = new osztalyNeve();

int valtozo1 = o_osztalyNeve.metodus1();

Ha az osztály egy publikus (public) metódusát (mezőjét) közös (static) metódusként (mezőként) deklaráltuk, akkor arra az osztály példányosítása nélkül is tudunk hivatkozni:

int valtozo2 = osztalyNeve.metodus2();
Hivatkozás az osztály tagjaira
Minden osztálynak van legalább egy speciális metódusa, amit konstruktornak hívunk. Ha mi magunk nem hozunk létre, akkor is keletkezik egy, amit automatikusan a fordító hoz létre. A konstruktor neve megegyezik az őt tartalmazó osztály nevével, nincs visszatérési értéke, de lehetnek paraméterei.

class osztalyNeve {
// a konstruktor 1. változata
public osztalyNeve {
// utasítások
}
// a konstruktor 2. változata
public osztalyNeve(int _paremeter1) {
// utasítások
}
}
Konstruktorok
Az itt látható osztálynak mindjárt két konstruktora is van, az egyik nem vár paramétert, a másik igen. Az osztály konstruktora akkor hívódik meg, amikor létrejön az osztályból egy példány. Feladata az objektum belső kiinduló állapotának, például a mezői értékének beállítása induló értékre. Több konstruktor esetén az a változat hívódik meg, melynek deklarálása megfelel az osztály példányosítása során használt utasítás formátumának. Pl.:

osztalyNeve o_osztalyNeve1 = new osztalyNeve();

// a konstruktor 1. változatát hívja meg

osztalyNeve o_osztalyNeve2 = new osztalyNeve(2);

// a konstruktor 2. változatát hívja meg
Destruktorok
A C# a C++-os destruktor formátumot használja:
class osztalyNeve {
public ~osztalyNeve() {
// utasítások
}
}
Fontos tudni!
A .NET -ben a memória felszabadítását nem nekünk kell elvégezni, hanem ezt időnként egy ún. szemétgyűjtő algoritmus teszi meg helyettünk, mely egy bizonyos esemény hatására fut le, ilyenkor kidobja a már senki által nem hivatkozott, így senki által fel nem használható objektumokat, majd visszavonul.
Az osztálypéldány megsemmisülésének időpontja nem meghatározható, ezért nem is építhetünk a szó klasszikus értelmében vett destruktorok működésére, mert lehet, hogy csak egy óra múlva semmisül meg egy nem használt objektum. Ezért ne a destruktorba tegyük az erőforrásfelszabadító kódokat, hanem minden erőforrást zárjunk le azonnal, amint nincs rá szükségünk.
Valójában a .NET-fordító az ~osztalyNeve() destruktort a következőre fordítja le:

protected override void Finalize() {
try {
/ ide kerül az „~osztalyNeve()” destruktor kódja
}
finally {
base.Finalize();
}}

Azaz a destruktor helyett készül egy Finalize() virtuális metódus, ami felüldefiniálja az ősosztályból örökölt metódust. Ennek törzsében lefut a destruktorban megírt kódunk, majd meghívódik az alaposztály Finalize() metódusa is, hogy neki is legyen alkalma felszabadítani az általa (esetlegesen) lefoglalt erőforrásokat
Adott a következő osztály:

Készítsük el a hozzátartozó osztálydeklarációt.

Írjuk meg a főprogramot, amelyben létrehozunk egy muvelet objektumot, és bekérjük a felhasználótól az objektum x, y és muv értékeit, melyet az Init metódus meghívásával adunk meg. Ezután hívjuk meg az objektum Szamol metódusát, amely végrehajtja a megadott muveletet x és y között, majd a Kiir metódus meghívásával az eredmény kerüljön a képernyőre.

Cseréljük le az Init eljárásunkat egy konstruktorra.
Módosítsuk a főprogramot úgy, hogy az új osztályt legyen képes kezelni.

Írjunk egy olyan Számol metódust, amely három paraméterrel rendelkezik, és példány létrehozása nélkül képes a 4 alapművelet elvégzésére a paraméterként kapott adatokkal.
Példa
Példa2
Öröklés
http://msdn.microsoft.com/en-us/library/ms173153%28v=vs.80%29.aspx
Ez a mechanizmus teszi lehetővé, hogy bizonyos osztályokból más osztályokat származtassunk. Az osztályt, amelyből származtatunk alaposztálynak (esetleg ősosztálynak), gyerekét pedig származtatott osztálynak nevezzük. A származtatott osztály örökli alaposztályának adatmezőit és tagfüggvényeit. Az örökölt adatmezők és tagfüggvények megváltoztathatók és kibővíthetők.


C#-ban az öröklést az alábbi módon implementálhatjuk:

Public class gyerek : szülő

Hozzáférési szintek:
Public : az objektum minden osztályból elérhető
Private: az objektum csak az adott osztályból érhető el
Protected : az objektum csak az adott osztályból és annak származtatott osztályaiból érhető el
Internal: az objektum az egész szerelvényből (assembly) elérhető
Protected internal: az objektum vagy az egész assemblyből vagy a származtatott osztályokból érhető el
Lehetőség van arra, hogy a származtatott osztályban az ősosztály konstruktorát hívjuk meg:

Public derivedclass(pareméterek) : base(paraméterek)

Mivel az osztályok nem öröklik a konstruktort, a származtatott osztályoknak saját konstruktort kell megvalósítaniuk, vagy meg kell hívniuk az ősosztály konstruktorát.
Ha egy származtatott osztályban új paraméterlistával rendelkező konstruktort hozunk létre, akkor a fordító meghívja az alaposztály üres paraméterlistájú konstruktorát. Ha ilyen nincs, akkor a fordító hibát jelez.
A származtatott osztály örökli az ősosztály objektumait, a változókat és metódusokat egyaránt.

A származtattott osztályból meghívhatjuk az ősosztály metódusait, ehhez a base.metódusnév(); hívást kell végrehajtanunk.

A származtatott osztály megvalósíthatja a metódusokat az ősosztálytól eltérő módon, ezt mi a

Public new metódusnév()

szintaxissal tehetjük meg, amely az ősosztály metódusát eltakarja és kicseréli a származtatott osztályéval vagy virtuális metódusokat használhatunk.
Öröklés - Konstruktorok
Ahhoz, hogy egy metódust hozzunk létre, amit támogatja a többalakúságot, az ősosztálybeli metódus neve elé kell írnunk a virtual kulcsszót. A C#-ban alapértelmezettként a metódusok nem virtuálisak.

public class Ősosztály
{
public virtual void Szamol()
{Utasítás();}
}

Most már minden származtatott osztály megvalósíthatja a Szamol() metódus saját verzióját, ha származtatott osztály metódusneve elé beszúrjuk az override kulcsszót.

public class Származtatott : Ősosztály
{
public override void Szamol()
{Utasítás2();}
}
Az override kulcsszó jelzi a fordítónak, hogy az ősosztálybeli metódust szándékosan töltöttük túl.
Öröklés - Metódusok - Polimorfizmus
A már elkészített osztályunkból származtassunk egy új osztályt Muvelet2 néven.
Az új osztály csupán abban különbözik a régitől, hogy képes egy új műveletet is elvégezni: a hatványozást. Ehhez a Szamol metódust kell felülírni.
Az új osztálynak természetesen szüksége lesz egy konstruktorra, amelyben el kell helyeznünk a szülőosztály konstruktorának hívását.
Írjuk meg a főprogramot, amelyben először a Muvelet2 osztály egy objektumpéldányát hozzuk létre.
Próbáljuk ki a következő eseteket:
A Muvelet osztály objektumváltozóját deklaráljuk, de az objektumpéldányt Muvelet2-ből hozzuk létre. Melyik Szamol metódus hívódik meg ha a new módosítót használjuk az új osztályban illetve ha virtuális metódust készítünk? Muvelet vagy Muvelet2 Szamol metódusa?
A Muvelet2 osztályban létrehozunk egy SzamolKiir metódust, amely meghívja az osztály Szamol és Kiir metódusait. A főprogramban Muvelet osztály objektumváltozóját deklaráljuk, de az objektumpéldányt Muvelet2-ből hozzuk létre. Szamol és Kiir metódusok helyett SzamolKiirt hívjuk meg. Mi történik? Miért?
Property-k
Az objektum adatai az objektum-orientált szabályok alapján kívülről nem elérhetők, de írhatunk olyan metódusokat, amelyek feladata az adatmódosítás és adatszolgáltatás. Ezekre a feladatokra használhatjuk a C#-ban a Propertyket.

Ilyenkor az x attribútum beállítása a következőképpen történhet:

muvelet m = new muvelet();
m._x=12.5;

A mezo attribútum értékét pedig a következőképpen szedhetjük elő:

float v;
v=m._x;

Tehát a Property-n keresztül úgy állíthatjuk be és kérhetjük el az attribútumértékeket, mintha a Property maga egy olyan attribútum lenne, ami public hozzáféréssel rendelkezik. Így betartjuk az objektum-orientáltság zártságra vonatkozó szabályát, de az a látszat, mintha közvetlenül férnénk hozzá az adatmezőhöz.

A Propertyk lehetőséget adnak arra is, hogy csak olvasható illetve csak írható adatmezőket hozzunk létre.
Csak olvasható, csak írható property-k
A csak olvasható adatmezőnek nem kell rendelkeznie Set (azaz beállító) résszel.

Ebben az esetben az attribútum beállítása nem lehetséges a fent ismertetett módszerrel, de az érték kiolvasása természetesen megvalósítható. (Az objektum metódusai természetesen továbbra is hozzáférnek az x adatmezőhöz mind írásra, mind olvasásra. A readonly tulajdonság csak a külső elérésre vonatkozik.)

A csak írható adatmezőnek nem kell rendelkeznie Get (azaz lekérdező) résszel.

Ebben az esetben az attribútumérték kiolvasása nem lehetséges a fent ismertetett módszerrel, de az attribútum értékének beállítása természetesen megvalósítható. (Az objektum metódusai természetesen továbbra is hozzáférnek az x adatmezőhöz mind írásra, mind olvasásra. A writeonly tulajdonság csak a külső elérésre vonatkozik.)
Konstruktorok, destruktorok, öröklés, property-k
Feladatok
Adott az itt látható osztály és struktúra, amelynek segítségével pixelenként hozzáférhetünk a képekhez, kiolvashatjuk és írhatjuk azokat. A Pixelek kétdimenziós struktúratömbben tároljuk a pixelek RGB értékeit.
Hozzunk létre egy ConsolApplication-t és valósítsuk meg az osztálydiagram alapján az osztályt.
Az egyes metódusok feladatai:
Konstruktor: a bmp adatmező értékül kapja a paraméterben érkező bitmap-et. Feltölti a Pixelek tömböt úgy, hogy kiolvassa a bitmap megfelelő értékeit. A width és height adatmezők a bmp szélessége és magassága alapján kapnak értéket.
SetPixel: Pixelek tömb egy értékét állítja a paraméterlista alapján
GetPixel: Pixelek tömb egy értékét olvassa ki a paraméterlista alapján
Draw: Pixelek tömb értékei alapján módosítja a bmp pixeleit.
Save: Elmenti a képet a paraméterben megadott fájlnéven.
Az osztály használatával végezzük el egy tetszőleges kép binarizálását. Ha az egyes RGB értékek mindegyike 100-nál nagyobb, akkor az a pixel legyen piros, egyébként legyen fekete.
(A Bitmap osztály használatához szükség van a következő névtérre: System Drawing - nézzük meg,
hogy a referenciák közt szerepel-e az osztály, ha nem, adjuk hozzá.)
Hozzunk létre egy WindowsApplication-t, és másoljuk át bele az MImage osztályt.
Implementáljuk az MImage osztály egy alosztályát az osztálydiagram alapján.
Az új metódusok feladatai:
Draw(): az ősosztálybeli Draw műveletein kívül a paraméterben megadott formon létrehoz egy pictureboxot, és megjeleníti a benne a képet.
Blur(): Elmossa a képek, úgy, hogy minden pixelt helyettesít közvetlen szomszédainak és önmagának átlagával.
Blur(Bitmap,Bitmap): Statikus metódus, az első paraméterben megadott képen végzi el ugyanazt, mint a paraméter nélküli Blur, és a második paraméterben adja vissza.
1. Hozzunk létre egy osztálypéldányt, és hívjuk meg a Blur függvényt az elmosás megvalósításához, majd jelenítsük meg a képernyőn a Draw hívásával.
2. A statikus Blur hívásával végezzük el az elmosást, majd a Bitmap osztály Save metódusával mentsük a képet.
Mivel a képfeldolgozásban sok művelet szürkeskálás képen hajtható végre, ezért származtassunk egy osztályt, amely a szürkeskálás képek feldolgozását végzi el. A gpixelek kétdimenziós tömb tartalmazza a szürkeskálás értékeket. Már a konstruktorban konvertáljuk át a pixelek RGB értékeit szürkeskálás értékké, és töltsük fel a gpixelek tömböt (gray=0.3*R+0.59*G+0.11*B). A többi metódus feladata ugyanaz, mint eddig, de nem a pixelek tömbön, hanem a gpixelek tömbön végzi el a műveleteket, ezért felül kell írnunk a Draw és Blur metódusokat, a SetPixel és GetPixel metódusokból, pedig új paraméterlistával rendelkezőt kell létrehozni.
1. Hozzunk létre egy GSImage osztálypéldányt, és hívjuk meg a megfelelő metódusokat az elmosáshoz, megjelenítéshez és mentéshez.
2. Deklaráljunk egy IMPImage objektumváltozót, és hozzunk létre egy GSImage példányt belőle. Próbáljuk ki, hogy mi történik, ha felülírt függvényeknél a new illetve override kulcsszót használjuk, illetve a SetPixel és GetPixel metódusokat próbáljuk meghívni. Mi a magyarázata a történteknek?
3. Hozzunk létre egy új konstruktort, amelynek paraméterlistájában a kép szélességét és magasságát kell csak megadni, és property segítségével lehessen beállítani a bmp mező értékét (ez a property feltölti a gpixelek tömböt is.)
Asszociáció-aggregáció
Asszociáció, operátor-túltöltés, események, interfészek
Aggregáció (rész-egész viszony) esetén a rész osztályt az egész osztály adatmezőjeként deklaráljuk. Ha a rész-egész viszony 1-n multiplicitású, akkor ez az adatmező egy objektumtömb lesz. (Tömb helyett természetesen használhatók a különböző absztrakt adattípusok is, mint pl. kapcsolt lista.)

Asszociáció esetén 1-1 illetve 1-n kapcsolatnál ugyanúgy járhatunk el mint rész-egész viszonynál. (Hiszen az aggregáció az asszociáció speciális esete.) Kicsit bonyolultabb a helyzet ha a két osztály között n-m kapcsolat van. Ebben az esetben először el kell végeznünk egy közbülső lépést: az n-m kapcsolatot két 1-n kapcsolatra kell bontanunk egy ún. asszociációs osztály beiktatásával. Például ha egy dolgozó több cégnél is dolgozhat, akkor az osztálydiagram a következő:





Az Dolgozik asszociációs osztály segítségével jutunk két 1-n típusú asszociációhoz:






(A módszer hasonló ahhoz, mint amikor az adatbázis-kezelésben ER-modellből relációs modellt képezünk.)
Operátor-overloading
A C# lehetővé teszi, hogy a nyelv által definiált operátorokhoz újabb jelentést rendeljünk. Ennek segítségével saját osztályaink objektumain végezhetünk már létező operátorokkal műveleteket.

Az operátor-overloading szintaktikája:
public static adattipus operator +(osztalynev p1, adattipus p2)

Szabályok:
Az operátoroknak a C# előre definiált típusaira vonatkozó jelentése nem változtatható meg.
Az operátorfelülírás csak osztályként definiált típusok esetében alkalmazható.
Az operátorok argumentumszámát a felülírás során nem változtathatjuk meg. Pl.: a ++ operátornak továbbra is egyoperandusúnak kell lennie.
A precedencia és asszociativitás szabályai nem módosíthatóak.

A következő súgóban szereplő példában egy complex számot megvalósító példában az összeadás operátorát töltik túl. Így lehetővé válik complex számot összeadásának kijelölése úgy, mintha az racionális szám volna.
http://msdn.microsoft.com/en-us/library/6fbs5e2h.aspx
Felülírható operátorok:
+, -,
!
, ~, ++, --
+, -, *, /, %, &, |, ^, <<, >>
==, !=, <, >, <=, >=
Eseménykezelés
Osztályaink tartalmazhatnak saját eseményeket. Az eseményekhez tartozó eseményvezérelt eljárások minden az osztályhoz tartozó objektum esetében különbözőek lehetnek.

Esemény létrehozása

Készítsünk fel egy osztályt arra, hogy kezelni tudja a három kattintás eseményét.

A Kattintottak metódust akkor kell meghívni, amikor egy kattintás történt. Ez a metódus számolja a kattintásokat, és ha azok száma eléri a hármat, akkor a HaromKattintas eseményt megtörténtté tesszük egy metódushívás segítségével.

Használjuk ennek az osztálynak egy objektumát arra, hogy egy formra történő háromszori kattintás esetén jelenítsünk meg egy üzenetablakot.

Az eseményhez az eljárást ugyanúgy rendeljük, mintha egy beépített vezérlőelemről lenne szó. (A kódablakban a legördülő menüben kiválasztjuk.)
class Kattintas
{
private byte katt;
public event HaromKattintasEventHandler HaromKattintas;
public void Kattintottak()
{
katt += 1;
if (katt == 3) {
katt = 0;
if (HaromKattintas != null) {
HaromKattintas();
}
}
}
}
Kattintas k = new Kattintas();

private void Form1_Click(object sender, System.EventArgs e)
{
k.Kattintottak();
}
//eseménykezelő
private void k_HaromKattintas()
{
MsgBox("Háromszor kattintottak");
}
Delegate alapú események
Előfordulhat, hogy az esemény kezelését nem a form osztályunk, hanem egy általunk létrehozott egyéb osztály valósítja meg.
Fontos fogalmak az eseményekkel kapcsolatban:
Eseményforrás (source, publisher): Az eseményt közzétevő objektum
Feliratkozó (subscriber): Az eseményre feliratkozó, az esemény kezelését végző objektum
Delegate: típusbiztos hivatkozás egy metódusra (C-ben a függvénymutató ilyen) A következőben bemutatott delegate típusú eseményre minden olyan metódust feliratkozhat, amelynek nincs visszatérési értéke, és egy Integer paramétere van.

public delegate void EgyEvent(int param);
Teendők
:
Az eseményforráson belül létre kell hozni egy ilyen típusú eseményt, és azt közzé kell tenni
A feliratkozónak meg kell valósítania egy ilyen paraméterezésű metódust (az eseménykezelőt).
Az eseménykezelő metódust és az eseményt össze kell rendelni.
Nézzük végig egy példán a felsorolt lépések megvalósítását:







A feliratkozó és az eseményforrás egy objektumának létrehozása. Figyeljünk arra, hogy a két objektum az esemény és kezelőjének összerendelése előtt létrejöjjön.
public delegate void EgyEvent(int szam);
public class EgyEsemForras
{
public event EgyEvent SzamValtEvent;
public void EsemenyKozzetetel(int szam)
{
if (SzamValtEvent != null) {
SzamValtEvent(szam);
}
}
}
public class EgyFeliratkozo
{
public void OnSzamValtEvent(int szam)
{
MsgBox("A szám új értéke: " + szam);
}
}
EgyEsemForras ef = new EgyEsemForras();
EgyFeliratkozo f = new EgyFeliratkozo();
//Esemény és kezelőjének összerendelése
ef.SzamValtEvent += new EventHandler(f.OnSzamValtEvent);
//Eseményforrás eseményt kiváltó metódusának meghívása:
ef.EsemenyKozzetetel(8);
//Összerendelés megszüntetése
ef.SzamValtEvent -= new EventHandler(f.OnSzamValtEvent);
Mivel egy feliratkozó egy eseménykezelő eljárása több különböző objektum eseményét is kezelheti, ezért sokszor célszerű a paraméterlistában a küldő objektumot is feltüntetni.

public delegate void EgyEvent(object sender, int szam);

A feliratkozó egy meghatározott argumentumkészletet vár. Ha később megváltoztatjuk ezeket az argumentumokat, az az összes feliratkozóra hatással lesz. Ezért célszerű a .Net által biztosított általános eseményargumentum-tárolót használni: EventArgs. Ilyenkor a delegate a következőképpen alakul át:

public delegate void EgyEvent(object sender, EventArgs e);
Események - object paraméter
Események - EventArgs paraméter
Ha az eseményforrásnak nincs szüksége a 2. paraméterre, akkor az EventArgs osztály megosztott Empty tagját adja át.

Ha van szükség paraméterre, akkor létrehozza az EventArgs egy alosztályát, amelyet kibővít a szükséges adatmezőkkel, figyelembe véve az objektumorientált alapszabályokat (pl. egységbezárás).
Pl. a fenti példához kapcsolódóan:
public class SzamValtEventArgs : EventArgs
{
protected int m_szam;
public SzamValtEventArgs(int sz)
{
m_szam = sz;
}
public int szam {
get {
return m_szam;
}
}
}
Ebben az esetben az Eseményforrás kódja a következőképpen módosulhat:
public class EgyEsemForras
{
public event EgyEvent SzamValtEvent;

public void EsemenyKozzetetel(int szam)
{
SzamValtEventArgs szv = new SzamValtEventArgs(szam);
if (SzamValtEvent != null) {
SzamValtEvent(this, szv);
}
}
}
A Feliratkozó kódja pedig a következőképpen módosulhat:
public class EgyFeliratkozo
{
public void OnSzamValtEvent(object sender, EventArgs e)
{
SzamValtEventArgs szv = ((SzamValtEventArgs)(e));
MsgBox("A szám új értéke: " + szv.szam);
}
}
Ha nem kívánjuk átadni a szám paramétert, akkor használjuk az EventArgs megosztott Empty mezőjét! Ekkor az Eseményforrás kódja a következő:
public class EgyEsemForras
{
public event EgyEvent SzamValtEvent;
public void EsemenyKozzetetel(int szam)
{
if (SzamValtEvent != null) {
SzamValtEvent(this, EventArgs.Empty);
}
}
}
Interfész
Egy osztály definiálása során a legfontosabb feladat az, hogy a készítendő típus adatait, metódusait megadjuk. Gyakran felmerül az az igény, hogy egy további fejlesztés, általánosabb leírás érdekében ne egy osztály keretében fogalmazzuk meg a legjellemzőbb tulajdonságokat, hanem kisebb tulajdonságcsoportok alapján definiáljunk. Ez viszont ellentmond annak az elvárásnak, hogy minél részletesebben fogalmazzunk meg a típusainkat.
A fenti ellentmondás feloldására alakult ki az a megoldás, hogy engedjünk meg olyan típust, interface-t definiálni, ami nem tartalmaz semmilyen konkrétumot, de rendelkezik a kívánt előírásokkal:
interface név
{//deklarációs fejlécek}
Az osztály az interface által előírt tulajdonságokkal biztosan rendelkezni fog. Ezen kötelező tulajdonságok mellett természetesen tetszőleges egyéb jellemzőkkel is felruházhatjuk majd.
Az interfészek általában az I betűvel kezdődnek, utalva ezzel a név által jelzett tartalomra.
Az interfész használatával elzárhatjuk osztályunk (a kiszolgáló) metódusait más osztályok (az ügyfelek) elől. Az ügyfél osztály csak az interfészen keresztül kommunikálhat a kiszolgáló osztállyal.
Az interfész és az implementáció különválasztásakor az ügyfél egy absztrakt szolgáltatást vesz igénybe és nem annak egy bizonyos implementációját (vagyis magát a kiszolgáló objektumot). Így az implementációs részletek megváltozása a kiszolgáló oldalon nem jelent problémát.
Az interfész csak public hozzáférésű tagokat tartalmazhat.
Nem lehetnek példányai.
Metódusai nem implementálhatóak.
Nem lehetnek adatmezői.
A .Net osztályoknak csak egy főosztályuk lehet, de lehet több interfészük, tehát több interfészt is megvalósíthatnak.
Az interfészek konstruktorral nem rendelkezhetnek.
Az interfészt megvalósító osztálynak az összes metódust meg kell valósítania.
Interfész csak interfész leszármazottja lehet.
Ugyanazt az interfészt megvalósíthatja több osztály.
Az interfész egy absztrakt osztály, az alábbi megkötésekkel:
Az interfész többféle probléma megoldásánál is jól jöhet. Például C# nyelvben nem lehet egy osztálynak több szülője, csak egyetlenegy. Lehet viszont több interfésze, ezzel megvalósíthatunk közel ugyanolyan viselkedést, mintha több szülője lenne. A többalakúság is nagyon jól kezelhető interfészek segítségével, ha egy interfészen belül deklarált metódust több különböző osztályban fejtünk ki. Az interfészek használatára lássuk az alábbi példát.
Interfész létrehozása:

(Javasolt az "I" előtag használata)

Az interfészben felsorolt metódusok kifejtésére abban az osztályban kerül sor, amelynek ez lesz az interfésze:



Dolgozo objektum létrehozása:

Interfészek segítségével nemcsak metódusokat, hanem tulajdonságokat is létrehozhatunk. Például


Egy-egy interfészbe 3-5 metódust csoportosítsunk.
public interface IDolgozo
{
void SetFizetes(int fizetes);
}
public class Szemely:IDolgozo
{
void IDolgozo.SetFizetes(int fizetes)
{
...
}
}
IDolgozo dolgozo1;
dolgozo1=new Szemely;
dolgozo1.SetFizetes(250000);
public interface IDolgozo
{
void FizetesEmeles(int osszeg);
int fizetes
{
get;
}
}
Feladat (operátor overloading)
Írjunk az MImage osztályba egy statikus függvényt, amely a "-" operátort tölti túl.
Két MImage típusú objektum között lesz értelmezett ez a művelet, és egy MImage típusú objektumot ad vissza végeredményül.
A függvény az azonos koordinátájú pixelek RGB értékeit vonja ki egymásból. Kezeli a túlcsordulást, ebben az esetben az érték 0 lesz.
Jelenítsük meg 3 pictureboxban a műveletek tagjait és eredményét.
Feladat (eseménykezelés)
Feladat (interfészek)
1. Eseménykezelés segítségével valósítsuk meg, hogy a Pictureboxra kattintáskor a kattintás helyén lévő pixel színe cserélődjön feketére.
Melyik objektum az esemény közzétevője, és melyik a feliratkozó? Nézzük meg hol történik meg az esemény és kezelőjének összerendelése.

2. Két egymás mellett lévő pictureboxban jelenítsünk meg két ugyanolyan GSImage típusú képet. Eseménykezelés segítségével valósítsuk meg, hogy ha a bal oldali képen megváltozik a pixel színe, akkor a jobb oldali pictureboxban lévő kép megfelelő pixele is legyen ugyanolyan. Az esemény közzétevője és feliratkozója is a GSImage osztály egy objektumpéldánya legyen.
Adott az itt látható osztálydiagram:
Ez alapján bővítsük ki eddigi kódunkat, vagyis
Az MImage osztály valósítsa meg az ISlide interfészt. (Írjunk egy ADraw nevű metódust, amely megjeleníti a paraméterül átadott pictureboxban a bmp adatmezőben szereplő bitmap-et
Hozzunk létre egy új osztályt (Dia), amely szintén megvalósítja az ISlide interfészt.
Hozzunk létre egy Diavetito osztályt, amely egyetlen statikus metódussal rendelkezik. Utolsó paramétere egy params tömb, amelyben ISlide interfészt megvalósító osztályok objektumait várja. Ezeket a 3. paraméterben megadott idejű várakozás után egymás után megjeleníti a képernyőn (az első és második paraméterben megadott helyen).
Ezután hozzunk létre egy MImage objektumot és egy Dia objektumot két különböző bitmap értékkel. Hívjuk meg a Diavetítő osztály statikus Draw metódusát úgy, hogy az utolsó paraméter legyen a két objektum kétszer megismételve.
http://msdn.microsoft.com/en-us/library/vstudio/ms173156.aspx
Generic-ek: http://msdn.microsoft.com/en-us/library/ms379564%28vs.80%29.aspx
Forrás:
Juval Löwy: .NET komponensek programozása, Kossuth Kiadó, 2004
Dr. Kondorosi Károly, Dr. László Zoltán, Dr. Szirmay-Kalos László: Objektum-orientált szoftverfejlesztés, ComputerBooks Kiadói, Szolgáltató és Kereskedelmi Kft., 1997
Varga László, Sike Sándor: Szoftvertechnológia és UML, ELTE Eötvös Kiadó, 2003
Full transcript