A Nemerle programozási nyelv

Objektum-orientált programozás



Osztályok

A Nemerle támogatja az objektumorientált programozást. Általában egy program váza objektumorientált felépítésű; a beépített könyvtárak is ilyen elven készültek. A nyelv e tekintetben is sokban hasonlít a C#-hoz, a legtöbb eltérés szintaktikai eredetű.

Osztály létrehozására a class kulcsszó használatos. Ezt követi az osztály neve, majd opcionálisan kettőspont után a bázisosztály neve és az implementált interfészek, vesszővel elválasztva. Az ez utáni definíciós részt kapcsoszárójel-pár veszi körül.

class Example : BaseClass, IEnumerable, IComparable { ... }

Adattagok

Adattagok deklarálása a változók deklarálásához hasonlóan történik. Alapértelmezésben a deklarált adattagok módosíthatatlan értékek, melyek vagy a definíciójukban, vagy a konstruktorban kapnak értéket. Módosítható változók deklarációjához a mutable kulcsszót kell használnunk.

class Example : BaseClass { c = OtherClass(); mutable count : int; }

Metódusok

Metódusok definiálása a függvények definiálásához hasonló, külön kulcsszó nem vezeti be.

class Example : BaseClass { SomeMethod(a : int, _b : int, _ : string, _ : char) : void { ... } }

Az aláhúzásjellel kezdődő, ill. egyetlen aláhúzásjelből álló paraméternevek olyan paramétereket jelölnek, melyek értékeire nem hivatkozunk a metódus törzsében.

A típuskikövetkeztetés nem működik metódusok argumentumaira és visszatérési értékére, ezért ezek típusát mindig ki kell írnunk. Publikus metódusok esetén ugyanis a típuskikövetkeztetés az exportált interfész megváltozását eredményezhetné. Private metódusok esetén viszont a szolgáltatás tervbe van véve, a jövőben valószínűleg elérhető lesz.

Konstruktorok

Konstruktor deklarálásához a metódus neve helyén a this kulcsszót kell használni. Tetszőleges számú konstruktort definiálhatunk, tetszőleges számú paraméterrel. Ha nem adunk meg egyetlen konstruktort sem, akkor automatikusan létrejön egy paraméter nélküli, publikus (absztrakt osztályok esetén protected) alapértelemezett konstruktor. Ez csupán a bázisosztály paraméter nélküli konstruktorának hívását tartalmazza.

A konstruktor első utasításaként explicit meghívhatjuk a bázisosztály valamely konstruktorát a base néven keresztül. Ez a hívás általában elhagyható; ha nem használjuk, a fordító automatikusan elhelyezi a bázisosztály megfelelő paraméterezésű konstruktorára vonatkozó hívást.

class Example : BaseClass { public this() { base(); // elhagyható ... } }

Hasonlóan más szemétgyűjtéssel rendelkező nyelvekhez, a Nemerlében sincsenek destruktorok.

Módosítók

A módosítókat az adattagok, beágyazott típusok, illetve metódusok deklarációjának legelején kell feltüntetni, tetszőleges sorrendben.

A láthatósági szinteket szabályozó módosítók minden adattagra, beágyazott típusra és metódusra használhatóak. Alapértelmezés szerint minden private. A public és internal módosítok a legfelső szintű osztályokra is alkalmazhatók, ezekre az alapértelmezés internal. A felsoroltak közül legfeljebb egy használható.

A következő módosítók az entitások szemantikáját változtatják meg.

Adattagokra vonatkozó módosítók:

Metódusokra vonatkozó módosítók:

Figyelem: más nyelvektől eltérően a statikus adattagok és metódusok csak az osztály nevén keresztül hivatkozhatóak, objektumon keresztül nem!

Típusdefiníciókra vonatkozó módosítók:

Minden entitásra használható módosítók:

Öröklődés

A C#-hoz hasonlóan Nemerlében is csak az egyszeres öröklés megengedett. Minden osztály közvetve vagy közvetlenül a System.Object osztály leszármazottja. Így az osztályok deklarációjában a bázisosztály megadása el is hagyható: ebben az esetben az osztály automatikusan a System.Object leszármazottja lesz.

class Example // bázisosztály: System.Object { ... }

Interfészek

A .NET keretrendszerben lehetőség van interfészek deklarációjára és használatára, így a Nemerle ezt is támogatja. Az interfészek deklarációja és használata a C# nyelvben megszokottakhoz hasonló. Új interfészt az interface kulcsszóval deklarálhatunk, ez után következik az interfész neve, majd opcionálisan egy kettőspont után egy vagy több interfész felsorolása (vesszőkkel elválasztva), melyekből származtatni szeretnénk.

Egy példa egy interfész deklarációjára és használatára:

using Nemerle.IO; interface IPrintable { Print () : void; } class RefrigeratorNG : Refrigerator, IPrintable { public Print () : void { printf ("I'm the refrigerator!\n") } } module RP { PrintTenTimes (p : IPrintable) : void { for (mutable i = 0; i < 10; ++i) p.Print () } Main () : void { def refr = RefrigeratorNG (); PrintTenTimes (refr) } }

Bár egy osztálynak csak egyetlen ősosztálya lehet, de több interfészt is implementálhat, így a többszörös öröklődés egyfajta megvalósítása interfészeken keresztül lehetségessé válik.

Property-k

A property egy szintaktikus cukor a get/set design pattern használatára. Egy property elérése olyan, mint egy adattagé, de a háttérben metódushívások valósítják meg.

class Button { text : string; public Text : string { get { text } set { text = value; Redraw () } } } def b = Button (); b.Text = b.Text + "..."

A get és set blokkok közül legfeljebb az egyik elhagyható. Ha csak get blokkot adunk meg, a property csak olvasható lesz, míg ha csak a set blokk szerepel, akkor csak írható.

A set blokkban value névvel hivatkozhatunk arra az értékre, melyet a property-nek értékül adtak.

Indexerek

Az indexerek speciális property-k, melyek egyetlen adattag helyett egy kollekcióhoz nyújtanak hozzáférést, tömb-szerű szintaxissal, vagyis egy- vagy többdimenziós indexeléssel. C#-ban csak alapértelmezett indexereket lehet definiálni, míg Nemerlében további indexerek is definiálhatóak, külön névvel ellátva. Az alapértelmezett indexer neve Item.

class Table { store : array [array [string]]; public Item [row : int, column : int] : string { get { System.Console.WriteLine ($"get ($row, $column)"); store[row][column] } set { System.Console.WriteLine ($"set ($row, $column)"); store[row][column] = value } } public this () { store = array (10); for (mutable i = 0; i < store.Length; ++i) store [i] = array (10); } } def t = Table (); t[2, 3] = "foo"; t[2, 2] = t[2, 3] + "bar";

Látható, hogy az alapértelmezett indexer eléréséhez nincs szükség a .Item kiírására, szintaktikailag közvetlenül az objektum indexelhető.

Delegate-ek

A .NET-es nyelvekkel való kompatibilitás érdekében Nemerlében is van lehetőség delegate-ek használatára, bár a nyelv rendelkezik valódi függvény típussal is erre a célra.

A delegate tulajdonképpen egy névvel ellátott függvény típus. A delegate kulcsszóval definiálható:

delegate Foo (_ : int, _ : string) : void; delegate Callback () : void;

A delegate neve használható delegate példányok létrehozására. Bármilyen megfelelő típusú függvény érték használható delegate példányok létrehozására (lokális függvények és lambda kifejezések is):

def my_fun (i : int, s : string) { // do something with i and s } def my_Foo_delegate = Foo (my_fun); def another_Foo_delegate = Foo (fun (i, s) { ... });

Egy delegate példány meghívására használható a szokásos függvényhívás-szintaxis, vagy az Invoke speciális metódus.

A += operátornak különleges jelentősége van delegate-ek esetében: meghívja a System.Delegate.Combine metódust, amely egy olyan függvényt hoz létre, amely két másik függvényt hív meg szekvenciálisan. Ez főleg eseményekkel kapcsolatban használatos.

module X { some_fun () : void { mutable f = Foo (fun (_, _) {}); f (3, "foo"); // same as f.Invoke (3, "foo"); f += fun (_, s) { System.Console.WriteLine (s) }; f (42, "bar"); } }

Egy delegate létrehozása valójában egy önálló osztály létrehozását jelenti, így ugyanolyan láthatósági szintekkel rendelkezhet, mint bármely osztály. Célszerű delegate-eket önálló osztályként létrehozni az egymásbaágyazás elkerülése érdekében.

Érdemes odafigyelni a láthatósági szabályokra delegate-ek esetében. Követelmény, hogy egy esemény legalább akkora láthatósággal rendelkezzen, mint annak delegate-je.

Események

Az események speciális delegate-ekhez kapcsolódó property-k. Egy GUI rendszer például arra használja őket, hogy szignálokat akciókhoz kössön (pl. egy gomb megnyomása esetén egy függvény meghívása).

Az események deklarációja az adattagokéhoz hasonló, de az event kulcsszó előzi meg őket, és mindig delegate típusúak. Az osztályon belül az események úgy látszanak, mint a delegate típusának megfelelő adattagok, de kívülről csak a += és -= operátorok használhatóak rajtuk. Ezek funkciója delegate-ek fel- és lecsatolása.

class Button { public event OnClick : Callback; } def b = Button (); // az OnClick hozzákötése valamilyen névtelen függvényhez ... b.OnClick += Callback (fun () { ... })

A leválasztás már nem ilyen egyszerű, ehhez előbb el kell tárolnunk a delegate-et:

def b = Button (); def my_cb = Callback (fun () { ... }); b.OnClick += my_cb; // ... b.OnClick -= my_cb;

A nyelv automatikusan konvertál függvény típusról delegate-re, így a delegate létrehozása elhagyható:

def b = Button (); b.OnClick += fun () { ... }

Két dolgot célszerű figyelembe venni eseményekkel kapcsolatban: