A C# programozási nyelv

C# 2.0-3.0 Újdonságok

Attribútumok

Megjegyzés: ebben a fejezetben az attribútum szót nem a megszokott értelemben használjuk, azaz itt nem az objektumok egy-egy tulajdonságára utal a szó.

Az attribútumok olyan objektumok, amelyekkel entitásokat jelölhetünk meg, így deklaratív információkat is hozzákapcsolhatunk a programhoz.

Egy új attribútum mindig a System.Attribute osztályból származik. Megadható, hogy az attribútum milyen entitásokhoz legyen kapcsolható: például csak osztályhoz, vagy interfészhez. Megadható az is, hogy az attribútum milyen paramétereket vár, illetve hogy mik legyek a metódusai. Ettől kezdve az attribútum bármilyen - a definícióban megadott - entitáshoz kapcsolható.

Pozícionális és nevesített paraméterek

Az attribútum osztályok public konstruktorainak a paraméterlistája határozza meg a pozícionális paramétereket. Minden példányváltozó, ill. property nevesített paramétere lesz az attribútumnak. A pozícionális paraméterek mindig megelőzik a nevesítetteket az attribútum deklarációjában.

Példa:

[AttributeUsage(AttributeTargets.Class)]
public class HelpAttribute: System.Attribute
{
 public HelpAttribute(string url)
 {
  // url egy pozcionális paraméter
 }
 public string Topic
 {
  // Topic egy nevesített paraméter
  get {...}
  set {...}
 }
}

Használata:

[Help("http://www.mycompany.com/./Misc.htm", Topic ="Class2")]
class Class2 {...}

Némi megkötés van a paraméterek típusára vonatkozóan, ui. csak következő típusok használhatóak: bool, byte, char, double, float, int, long, short, string, object, System.Type.

A program futása alatt a felcímkézett entitás aktivációja előtt lefut az attribútum konstruktora. Az attribútumok értékei futás közben lekérdezhetőek.

Fontosabb beépített attribútumok

AttributeUsage attribútum

Ez egy általános célú attribútum, amelyet más attribútumok deklarálásánál használhatunk fel azért, hogy meghatározzuk, hogy milyen entitáshoz kapcsoljuk az attribútumunkat.

Például:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)]
public class SimpleAttribute: System.Attribute{}

A fenti esetben a [SimpleAttribute] attribútumunkat (amit [Simple]-ként is írhatnánk) osztályhoz vagy interfészhez kapcsolhatjuk.

Conditional attribútum

Segítségével feltételes metódusokat készíthetünk. Az attribútum paraméterét kiértékelve az adott metódus a hívásra vagy végrehajtódik vagy nem.

Példák:

[DllImport("kernel32", setLastError=true)]
static extern bool CreateDirectory(string name, SecurityAttribute sa);
[Obsolete("This class is obsolete; use class B instead")]
class A {...}
[Help("http://www.microsoft.com/.../Class1.htm")]
public class Class1
{
 [Help("http://www.microsoft.com/.../Class1.htm", Topic = "F")]
 public void F(...) {...}
}

A delegált (delegate) típus

A delegált típust függvénymutatók kiváltására vezették be.

Példa:

class SortClass
{
 static public int val1 = 30;
 static public int val2 = 10;
 //delegált
 public delegate void Sort(ref int a, ref int b);
 //fvek 2 db
 public static void Ascending(ref int a, ref int b)
 {
  if (a > b) { int t = a; a = b; b = t; }
  Console.WriteLine("Up : {0},{1}", a, b);
 }
 public static void Descending(ref int a, ref int b)
 {
  if (a < b) { int t = a; a = b; b = t; }
  Console.WriteLine("Down: {0},{1}", a, b);
 }
 //hozzárendel
 Sort up = new Sort(Ascending);
 Sort down = new Sort(Descending);
 //meghív
 public void Do() { up(ref val1, ref val2); down(ref val1, ref val2);}
}

static void Main(string[] args)
{
 //delegate
 SortClass s = new SortClass();
 s.Do();
 Proba p = new Proba();
 Proba.Main2();
}

Egy delegált típus definíciójában egy paramétertípus-listát és egy visszatérési értéket rögzít. Az így meghatározott delegált típussal azok a metódusok kompatibilisek, amelyek paraméterlistája és visszatérési értéke megegyezik a delegált típuséval.

Ha a függvény visszatérési értéke void akkor lehetőség van hívási listák (invocation list) megadására is. A + illetve - operátorokkal adhatóak kompatibilis metódusok a listához. Híváskor ezek fognak rendre végrehajtódni.

Példa:

class C {
 public static void M1(int i)
 {
  console.WriteLine("C.M1: " + i);
 }
 public static void M2(int i)
 {
  Console.WriteLine("C.M2: " + i);
 }
}

class Test
{
 static void Main()
 {
  D cd1 = new D(C.M1);
  cd1(-1); // call M1
  D cd2 = new D(C.M2);
  cd2(-2); // call M2
  D cd3 = cd1 + cd2;
  cd3(10); // call M1 then M2
  cd3 += cd1;
  cd3(20); // call M1, M2, then M1
 }
}

Egy-egy osztály tagjaként definiálhatóak események (event), melyek az eseménykezelést egyszerűsítik. Fontos, hogy az event kulcsszó után csak delegált típus szerepelhet.

Példa:

public delegate void EventHandler(object sender, EventArgs e);

public class Button: Control
{
 public event EventHandler Click;
}

public class LoginDialog: Form
{
 Button OkButton;
 Button CancelButton;
 public LoginDialog()
 {
  OkButton = new Button(...);
  OkButton.Click += new EventHandler(OkButtonClick);
  CancelButton = new Button(...);
  CancelButton.Click += new EventHandler(CancelButtonClick);
 }

 void OkButtonClick(object sender, EventArgs e)
 {
  // Handle OkButton.Click event
 }

 void CancelButtonClick(object sender, EventArgs e)
 {
  // Handle CancelButton.Click event
 }
}

Névtelen Metódusok

A névtelen metódusok lehetővé teszik, hogy metódusokat (általában delegáltakat) definiáljunk közvetlenül egy esemény deklarálása után. A delegáltak definícióit oda lehet drótozni az eseményekhez. Ezek a metódusok az őket tartalmazó osztály részei, így láthatják az osztályt megvalósító adattagokat és meg is változtathatják azokat. További előny, hogy változókat lehet kötni a névtelen metódusokhoz, így hozzá lehet adni, illetve el lehet őket távolítani egy eseményből. Ugyanazok a lehetőségeink, mint az esemény deklarációknál.

Példa:

button.Click += new EventHandler (this.bClick);
// máshol
private void bClick (sender, e)
{
 MessageBox.Show ("click");
}

button.Click += new EventHandler (object sender, EventArgs e)
{
 MessageBox.Show ("click");
}

, vagy:

clickHandler = new EventHandler(sender, e)
{
 MessageBox.Show("Click");
};

button.Click += clickHandler;
button.Click -= clickHandler;

Szétválasztott osztályok

Ez a funkció lehetőséget ad arra, hogy egy osztály teljes definícióját fizikailag is különálló forrásfájlokban tároljuk. Ebből a célból bevezettek egy új kulcsszót, a partial -t. Egy szétválasztott osztály minden részének tartalmaznia kell a partial kulcsszót. Az osztály összes részének egy projektben kell lennie és minden formális követelménynek meg kell felelnie. Ekkor a fordító felkutatja az osztály összes részét, majd összevágja egy osztállyá, így futási időben semmi különbséget nem tapasztalunk.

A .NET keretrendszer is használja ezt a megoldást a generált és a kézzel írt kód elkülönítésére.

Példa:

//file1.cs
namespace Clock
{
 class Clock
 {
  ... //saját változók és metódusok
 }
}

----------

//file2.cs Általában "Clock.Designer.cs"

namespace Clock
{
 partial class Clock
 {
  ... //generált kód
 }
}

Szérializáció

A szérializáció egy folyamat, amely objektum példányokat ment merevlemezre. A művelet eltárolja az objektumok állapotait, pl.: az attribútumok értékét egy fájlba. Ennek ellentettje a deszérializáció, amely egy fájl egy adathalmazából objektumokat hoz létre.

Főbb lépések:

A deszerializáció menete hasonló, azzal az eltéréssel, hogy nem a Serialize függvényt, hanem a Deserialize -t kell meghívni.

Példa:

public void SerializeNow()
{
 ClassToSerialize c=new ClassToSerialize();
 File f=new File("temp.dat");
 Stream s=f.Open(FileMode.Create);
 BinaryFormatter b=new BinaryFormatter();
 b.Serialize(s,c);
 s.Close();
}

public void DeSerializeNow()
{
 ClassToSerialize c=new ClassToSerialize();
 File f=new File("temp.dat");
 Stream s=f.Open(FileMode.Open);
 BinaryFormatter b=new BinaryFormatter();
 c=(ClassToSerialize)b.Deserialize(s);
 Console.WriteLine(c.name);
 s.Close();
}

A környezet (context) fogalma

A C#-ban megjelent egy új módosító kulcsszó, az unsafe. Az unsafe kifejezi, hogy az entitás, melyre alkalmazzuk, olyan nyelvi elemet tartalmaz, amely nem "biztonságos". Biztonságosság alatt elsősorban a pointerkezelést kell érteni. Ilyenkor a fordító engedélyezi a pointerek kezelését, illetve a heap -ben létrehozott objektumokra a pointerállítást (fixed kulcsszó). Mivel a GC ilyenkor is működik, ezért külön jelezni kell a fordítónak, ha egy referenciát pointerrel is el akarunk érni. Az entitás lehet blokk, típus, metódus stb. A kulcsszó hatásköre a mindenkori entitás hatáskörével egyezik meg. A nem biztonságos programozási elemek az alábbiak: mutatók (akár void*), a veremfoglalás, tehát a dinamikus memóriakezelés.

Példa:

unsafe
{
 …
 // unsafe context: can use pointers here
 …
}

, de ahogy azt említettük, osztályok és metódusok megjelölésére is alkalmas a kulcsszó:

unsafe class Class1 {}
static unsafe void FastMove ( int* pi, int* pdi, int length) {...}

Pointerek

A CRL háromféle pointert támogat: a kezelt, a kezeletlen és a kezeletlen függvény pointereket.

A kezelt pointerek a memória használt, a heap -ben tárolt blokkjaira hivatkoznak.

A kezeletlen pointerek a tradicionális C++ pointerek, ezek csak unsafe blokkban használhatóak.

A kezeletlen függvény pointerek is tradicionális C++ pointerek, amelyek függvények címére mutatnak (a „delegate” típust tekinthetjük kezeletlen függvénypointereknek).

Pointer deklaráció példa:

type* variable_name;

A típus lehet: sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, bool, felsorolási típus, más pointer típusok, bármely a felhasználó által definiált, kezeletlen rekord típus...

Példa:

int* pi; // egész változóra mutató pointer
float* pf, pq; // két, lebegőpontos változóra mutató pointer
char* pz; // karakterre mutató pointer

Az unsafe környezeten belül használható még a fixed utasítás. A pointerek használata a C#-ban nagyobb figyelmet igényel, mint a C++-ban. Ennek oka a garbage collector. Tisztítás alatt a garbage collector megváltoztatja az objektumok fizikai helyét, így az objektumra állított pointer rossz helyre fog mutatni a memóriában. Ezeknek a problémáknak az elkerülésére tartalmazza a C# a „fixed” kulcsszót, mely a benne létrehozott pointer helyét rögzíti a memóriában.

Példa:

fixed (pointer-típus változó) {blokk}
Colour cl = new Colour();
fixed ( int* pi = &cl.R)
{
 *pi = 1;
}

, valamint pointerek inicializációja:

fixed (int* pi = &cl.G)
fixed (double* pd = &array[10])
fixed (byte* pb = sarr, pd = darr) {...}
// ha egyszerre több azonos típusú pointert szeretnénk inicializálni

Ha elhagyjuk a „fixed” kulcsszót, a fordító figyelmeztetni fog a potenciális hibára, de a fordítás végrehajtódik.

Példa:

class Test
{
 public int x;
}

unsafe class SimpleTest
{
 [STAThread]
 static void Main(string[] args)
 {
  Test test = new Test();
  int* pi;
  fixed (int* px = &test.x)
  {
   *px = 100;
   pi = px;
  }
  Console.WriteLine("before g.c.: " + *pi);
  System.GC.Collect(2);
  Console.WriteLine("after g.c.: " + *pi);
 }
}
before g.c.: 100
after g.c.: 132

Látható, hogy ugyanarra a pointerre két különböző értéket kaptunk. A valóságban nagyon kicsi az ilyen egyszerű hibák esélye, de ennek csupán az az oka, hogy kicsi az esélye a garbage collector elindulásának.

Szabályként azonban érdemes betartani azt, hogy pointereket nem használunk fixed blokkokon kívül! A mi esetünkben a ’pi’ pointer minden használata a fixed blokkon kívül esett. Ez olyan hibákhoz vezethet, amelyeket futási időben nagyon nehéz felderíteni.

Másik unsafe környezeten belüli kulcsszó a stackalloc, ami veremből foglal le területet egy változónak.

Példa:

char* buf = stackalloc char[32];

Bár lehet, hogy objektumorientált környezetben furcsának tűnhet a mutatók használata, a nyelv tervezői a kritikus algoritmusok és rendszerszintű elérések lehetőségének fenntartása miatt hagyták meg.

A környezet fogalmához tartozik még a DLL függvények importálásakor alkalmazott paraméterátadási érték, vagy a struktúrák igazítási módjai. Ezeket a tulajdonságokat a fent említett attribútumként lehet megadni unsafe környezetben. (PIS = Platform Invocation Services)

A másik módosító kulcsszó-pár a checked és unchecked. Ezek alkalmazhatóak blokkra és operátorként is: az egész típusú aritmetikai műveletek és konverziók túlcsordulás-ellenőrzésének be- illetve kikapcsolására használhatóak.

Megjegyzés: a mutatók használata lényegében megegyezik a C++-ban megszokottakkal, ugyanúgy használhatóak a *, -> & operátorok, valamint megtalálható pointer-aritmetika.

C# 3.0

A C# 3.0 a nyelv következő verziója. A változások nagy részét olyan funkcionális nyelvek inspirálták, mint a Haskell, az ML, és a Language Integrated Query minta bevezetése a Common Language Runtime-ba.

A C# 3.0 -át a 2005-ös Professional Developers Conference -en mutatták be először. Az előzetes és a specifikáció a Microsoft Visual C# oldalán elérhető. Még nincs szabványosítva egy szervezet által sem, de feltehetőleg az ECMA és az ISO ezen hamarosan változtat.

A Microsoft azt nyilatkozta, hogy semmilyen változás nem lesz az új verzió futtatásában, ami azt jelenti, hogy a régi 2.0 -ás kód változtatás nélkül fordítható lesz az új compiler -rel.

Az új nyelvi elemek csak kis mértékben változtatják meg a memórián belüli lekérdezéseket, mint például a List.FindAll vagy a List.RemoveAll metódusokat.