• Nem Talált Eredményt

Névtelen metódusok kiváltása lambda kifejezésekkel

In document Microsoft .NET Framework (Pldal 189-196)

25. Események

27.4 Névtelen metódusok kiváltása lambda kifejezésekkel

Lambda kifejezést használhatunk minden olyan helyen, ahol névtelen metódus állhat. Nézzük meg pl., hogy hogyan használhatjuk így a List<T> típust:

using System;

using System.Collections.Generic;

class Program {

static public void Main() {

List<int> list = new List<int>();

for(int i = 1;i < 10;++i) {

list.Add(i);

}

int result = list.Find((item) => (item % 2 == 0));

Console.WriteLine("Az elsõ páros szám: {0}", result);

List<int> oddList = list.FindAll((item) => (item % 2 != 0));

Console.WriteLine("Az összes páratlan szám:");

oddList.ForEach((item) => Console.WriteLine(item));

} }

Eseménykezelőt is írhatunk így:

- 190 -

class Test {

public event EventHandler TestEvent;

public void OnTestEvent() {

if(TestEvent != null) {

TestEvent(this, null);

} }

}

Az EventHandler általános delegate–et használtuk az esemény deklarációjánál. Az esemény elindításánál nincs szükségünk most EventArgs objektumra, ezért itt nyugodtan használhatunk null értéket.

Most nézzük a programot:

class Program {

static public void Main() {

Test t = new Test();

t.TestEvent += (sender, e) =>

{

Console.WriteLine("Eseménykezelõ!");

};

t.OnTestEvent();

} }

Lambda kifejezés helyett ún. lambda állítást írtunk, így akár több soros utasításokat is adhatunk.

- 191 - 28

Attribútumok

Már találkoztunk nyelvbe épített módosítókkal, mint amilyen a static vagy a virtual.

Ezek általában be vannak betonozva az adott nyelvbe, mi magunk nem készíthetünk újakat – kivéve, ha a .NET Framework–kel dolgozunk.

Egy attribútum a fordítás alatt beépül a Metadata információkba, amelyeket a futtató környezet (a CLR) felhasznál majd az objektumok kreálása során.

Egy tesztelés során általánosan használt attribútum a Conditional, amely egy előfordító által definiált szimbólumhoz köti programrészek végrehajtását. A Conditional a System.Diagnostics névtérben rejtőzik:

#define DEBUG // definiáljuk a DEBUG szimbólumot using System;

using System.Diagnostics; // ez is kell class DebugClass

{

[Conditional("DEBUG")] // ha a DEBUG létezik static public void DebugMessage(string message) {

Console.WriteLine("Debugger üzenet: {0}", message);

} }

class Program {

static public void Main() {

DebugClass.DebugMessage("Main metódus");

} }

Egy attribútumot mindig szögletes zárójelek közt adunk meg. Ha a programban nem lenne definiálva az adott szimbólum a metódus nem futna le.

Szimbólumok definícióját mindig a forrásfile elején kell megtennünk, ellenkező esetben a program nem fordul le.

Minden olyan osztály, amely bármilyen módon a System.Attribute absztrakt osztályból származik felhasználható attribútumként. Konvenció szerint minden attribútum osztály neve a névből és az utána írt „Attribute” szóból áll, így a Conditional eredeti neve is ConditionalAttribute, de az utótagot tetszés szerint elhagyhatjuk, mivel a fordító így is képes értelmezni a kódot.

Ahogy az már elhangzott, mi magunk is készíthetünk attribútumokat:

class TestAttribute : System.Attribute { } [Test]

class C { }

- 192 -

Ez nem volt túl nehéz, persze az attribútum sem nem túl hasznos. Módosítsuk egy kicsit! Egy attribútum-osztályhoz több szabályt is köthetünk a használatára vonatkozóan, pl. megadhatjuk, hogy milyen típusokon használhatjuk. Ezeket a szabályokat az AttributeUsage osztállyal deklarálhatjuk. Ennek az osztálynak egy kötelezően megadandó és két opcionális paramétere van: ValidOn illetve AllowMultiple és Inherited. Kezdjük az elsővel: a ValidOn azt határozza meg, hogy hol használhatjuk az adott attribútumot, pl. csak referenciatípuson vagy csak metódusoknál (ezeket akár kombinálhatjuk is a bitenkénti vagy operátorral(|)).

[AttributeUsage(AttributeTargets.Class)]

class TestAttribute : System.Attribute { } [Test]

class C { }

[Test] // ez nem lesz jó struct S { }

Így nem használhatjuk ezt az attribútumot. Módosítsunk rajta:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]

class TestAttribute : System.Attribute { }

Most már működni fog érték- és referenciatípusokkal is. Az AttributeTargets.All pedig azt mondja meg, hogy bárhol használhatjuk az attribútumot.

Lépjünk az AllowMultiple tulajdonságra! Ő azt fogja jelezni, hogy egy szerkezet használhat-e többet is egy adott attribútumból:

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]

class TestAttribute : System.Attribute { } [Test]

[Test] // ez már sok class C { }

Végül az Inherited tulajdonság azt jelöli, hogy az attribútummal ellátott osztály leszármazottai is öröklik-e az attribútumot:

[AttributeUsage(AttributeTargets.Class, Inherited = true)]

class TestAttribute : System.Attribute { } [Test]

class C { } class CD : C { }

A CD osztály a C osztály leszármazottja és mivel az attribútum Inherited tulajdonsága true értéket kapott ezért ő is örökli az ősosztálya attribútumát.

Attribútumok kétféle paraméterrel rendelkezhetnek: positional és named. Előbbinek mindig kötelező értéket adnunk, tulajdonképpen ez a konstruktor paramétere lesz (ilyen az AttributeUsage osztály ValidOn tulajdonsága, amelynek mindig kell értéket

- 193 -

adnunk). Utóbbiak az opcionális paraméterek, amelyeknek van alapértelmezett értéke, így nem kell kötelezően megadnunk őket.

Eljött az ideje, hogy egy használható attribútumot készítsünk, méghozzá egy olyat, amellyel megjegyzéseket fűzhetünk egy osztályhoz vagy struktúrához. Az attribútum-osztály nagyon egyszerű lesz:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)]

class DescriptionAttribute : System.Attribute {

public DescriptionAttribute(string description) {

this.Description = description;

}

public string Description { get; set; } }

Az attribútumok értékeihez pedig így férünk hozzá:

using System;

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)]

class DescriptionAttribute : System.Attribute {

public DescriptionAttribute(string description) {

this.Description = description;

}

public string Description { get; set; } }

[Description("Ez egy osztály")]

class C { } class Program {

static public void Main() {

Attribute[] a = Attribute.GetCustomAttributes(typeof(C));

foreach(Attribute attribute in a) {

if(attribute is DescriptionAttribute) {

Console.WriteLine(((DescriptionAttribute)attribute).Description);

} }

} }

- 194 - 29

Unsafe kód

A .NET platform legnagyobb eltérése a natív nyelvektől a memória kezelésében rejlik. A menedzselt kód nem enged közvetlen hozzáférést a memóriához, vagyis annyi a dolgunk, hogy megmondjuk, hogy szeretnénk egy ilyen és ilyen típusú objektumot, a rendszer elkészíti nekünk és kapunk hozzá egy referenciát, amelyen elérjük. Nem fogjuk tudni, hogy a memóriában hol van és nem is tudjuk áthelyezni.

Épp ezért a menedzselt kód biztonságosabb, mint a natív, mivel a fentiek miatt egy egész sor hibalehetőség egész egyszerűen eltűnik. Nyílván ennek ára van, méghozzá a sebesség, de ezt behozzuk a memória gyorsabb elérésével/kezelésével ezért a két módszer között lényegében nincs teljesítménybeli különbség.

Vannak azonban helyzetek, amikor igenis fontos, hogy közvetlenül elérjük a memóriát:

- A lehető legjobb teljesítményt szeretnénk elérni egy rendkívül számításigényes feladathoz (pl.: számítógépes grafika).

- .NET–en kívüli osztálykönyvtárakat akarunk használni (pl.: Windows API hívások).

A C# a memória direkt elérését mutatókon (pointer) keresztül teszi lehetővé. Ahhoz, hogy mutatókat használhassunk az adott metódust, osztályt, adattagot vagy blokkot az unsafe kulcsszóval kell jelölnünk (ez az ún. unsafe context). Egy osztályon belül egy adattagot vagy metódust jelölhetünk unsafe módosítóval, de ez nem jelenti azt, hogy maga az osztály is unsafe lenne. Nézzük a következő példát:

using System;

class Test {

public unsafe int* x;

}

class Program {

static public void Main() {

unsafe {

Test t = new Test();

int y = 10;

t.x = &y;

Console.WriteLine(*t.x);

} }

}

Először deklaráltunk egy unsafe adattagot, méghozzá egy int típusra mutató pointert.

A pointer típus az érték- és referenciatípusok mellett a harmadik típuskategória. A pointerek nem származnak a System.Object–ből és konverziós kapcsolat sincs közöttük (bár az egyszerű numerikus típusokról létezik explicit konverzió).

Értelemszerűen boxing/unboxing sem alkalmazható rajtuk. Egy pointer mindig egy memóriacímet hordoz, amely memóriaterületen egy teljesen normális objektum van.

- 195 -

Ebből következően a fenti deklarációban nem adhatok azonnal értéket az unsafe pointernek, mivel numerikus értékadás esetén nem fordul le a program (hiszen nem egy int objektumról van szó), más objektum memóriacímét viszont nem tudom.

Sebaj, erre való az ún. „címe–operátor” (&) amellyel átadhatom egy hagyományos objektum címét.

A programban ki akarjuk írni a memóriaterületen lévő objektum értékét, ezt a dereference operátorral (*) tehetjük meg. Ez visszaadja a mutatott értéket, míg ha magát a változót használjuk az „csak” a memóriacímet. A memóriacím a memória egy adott byte–jára mutat (vagyis a pointer növelése/csökkentése a pointer típusának megfelelő mennyiségű byte–al rakja odébb a mutatót, tehát egy int pointer esetén ez négy byte lesz), amely az adott objektum kezdőcíme. A pointer úgy tudja visszaadni az értékét, hogy tudja mekkora méretű az objektum (pl. egy int pointer egy 32 bites – 4 byte méretű – területet vesz majd elő).

A programot parancssorból a /unsafe kapcsolóval fordíthatjuk le, Visual Studio esetén jobb klikk a projecten, Properties és ott állítsuk be.

csc /unsafe main.cs

A megfelelő explicit konverzióval a memóriacímet is lekérhetjük:

using System;

class Program {

static public void Main() {

unsafe {

int x = 10;

int* y = &x;

Console.WriteLine((int)y);

} }

}

Pointer csakis a beépített numerikus típusokra (beleértve a char is), logikai típusokra, felsorolt típusokra, más pointerekre illetve minden olyan általunk készített struktúrára hivatkozhat, amely nem tartalmaz az eddig felsoroltakon kívül mást. Ezeket a típusokat összefoglaló néven unmanaged típusoknak nevezzük.

Explicit konverzió létezik bármely két pointer típus között, ezért fennállhat a veszélye, hogy ha A és B pointer nem ugyanakkora méretű területre mutat akkor az A–ról B–re való konverzió nem definiált működést okoz:

int x = 10;

byte y = 20;

int* p1 = &x; // ez jó

p1 = (int*)&y; // ez nem biztos, hogy jó

Implicit konverzió van viszont bármely pointer típusról a void* univerzális pointer típusra. A void*-on nem használható a dereference operátor:

- 196 -

using System;

class Program {

static public void Main() {

unsafe {

int x = 10;

void* p1 = &x;

Console.WriteLine( *((int*)p1) );

} }

}

Egy struktúrára is hivatkozhatunk pointerrel, ekkor a tagjait kétféleképpen érhetjük el:

vagy a mutatón keresztül, vagy a nyíl (->) operátorral amely tulajdonképpen az előbbi rövidítése:

using System;

struct Test {

public int x;

}

class Program {

static public void Main() {

unsafe {

Test t = new Test();

t.x = 10;

Test* p = &t;

Console.WriteLine((*p).x); // 10 Console.WriteLine(p->x); // 10 }

} }

In document Microsoft .NET Framework (Pldal 189-196)