Professional Documents
Culture Documents
Mali Referentni Priručnik
Mali Referentni Priručnik
C# je objektno orijentisan programski jezik opte namene, s obaveznom proverom tipova podataka. Cilj jezika je produktivnost programera, pa je stoga on istovremeno jednostavan, izraajan i efikasan.
C# je neutralan u pogledu platforme, ali je napisan tako da odlino
radi u okruenju Microsoft .NET Framework. Verzija C# 5.0 razvijena je za .NET Framework 4.5.
N apomena
Programi i odlomci koda u ovoj knjizi prate one iz poglavlja 24
iz knjige C# 5.0 za programere (C# 5.0 in a Nutshell) i svi su
na raspolaganju kao interaktivni primeri u aplikaciji LINQPad.
Rad na tim primerima uz ovaj mali prirunik ubrzava uenje jer
ih moete menjati i odmah videti rezultate a da pritom ne morate postavljati projekte i reenja u integrisano razvojno okruenje Visual Studio.
Da biste preuzeli primere, pritisnite jeziak kartice Samples u
LINQPadu, a zatim vezu Download more samples. LINQPad
je besplatan idite na adresu http://www.linqpad.net.
S avet
Ukazuje na savet, predlog ili optu napomenu.
U pozorenje
Ukazuje na oprez ili upozorenje.
kue OReilly, dozvola je neophodna. Dozvola vam ne treba kada odgovarate na pitanja tako to citirate ovu knjigu i navodite primer koda
iz nje. S druge strane, obavezno nabavite dozvolu ukoliko u dokumentaciji svog proizvoda citirate znaajan deo primera koda iz ove knjige.
Ne zahtevamo, ali svakako cenimo navoenje izvora. To obino ukljuuje sledee podatke: autor, izdava i ISBN. Na primer: C# 5.0 Pocket Reference, Joseph Albahari i Ben Albahari (OReilly). Copyright
2012 Joseph Albahari i Ben Albahari, 978-1-449-3201-71.
Ako smatrate da se upotreba primera koda u vaem sluaju ne uklapa
u gore navedene uslove fer korienja ili dozvole, slobodno nam piite
na adresu permissions@oreilly.com.
Safari Books Online (www.safaribooksoline.com) digitalna je biblioteka koja funkcionie na zahtev i korisnicima obezbeuje ekspertski sadraj u obliku knjiga ili videa; autori
sadraja su vodei svetski autori u oblastima tehnologije i biznisa.
Inenjeri, projektanti softvera, veb dizajneri i kreativci koriste Safari
Books Online kao svoj primarni izvor za istraivanje, reavanje problema, uenje i obuku za sertifikate.
Safari Books Online nudi niz kompleta proizvoda i cenovnih kategorija za organizacije, vladine agencije i pojedince. Pretplatnici imaju pristup hiljadama knjiga, videa za obuku i ranih rukopisa publikacija
u jednoj potpuno pretraivoj bazi podataka, i to od izdavaa kao to
su OReilly Media, Prentice Hall Professional, Addison-Wesley Profes
sional, Microsoft Press, Sams, Que, Peachpit Press, Focal Press, Cisco Press, John Wiley & Sons, Syngress, Morgan Kaufmann, IBM Redbooks, Packt, Adobe Press, FT Press, Apress, Manning, New Riders,
McGraw-Hill, Jones & Bartlett, Course Technology i desetina drugih.
Ako elite vie informacija o servisu Safari Books Online, molimo vas
da nas posetite na vebu.
int x = 12 * 30;
Console.WriteLine (x);
}
}
//
//
//
//
Naredba 1
Naredba 2
Kraj metode
Kraj klase
N apomena
Niz (kao to je string[]) predstavlja fiksan broj elemenata odreenog tipa (vie informacija nai ete u odeljku Nizovi, na
strani 33).
Direktiva using je tu radi pogodnosti; tip moete referencirati i navoenjem njegovog potpuno kvalifikovanog imena, tj. imena tipa kojem prethodi njegov imenski prostor na primer, System.Text.String Builder.
Prevoenje programa
C# kompajler (prevodilac programa) pakuje izvorni kd, zadat kao
skup datoteka s nastavkom .cs, u sklop (engl. assembly). Sklop je jedinica pakovanja i primene u okruenju .NET. Sklop moe biti ili apli
kacija ili biblioteka. Standardna konzolna ili Windows aplikacija jeste
datoteka tipa .exe i ima metodu Main. Biblioteka je datoteka tipa .dll i
ekvivalentna je .exe datoteci, s tim to nema ulaznu taku. Nju poziva (referencira) neka aplikacija ili druge biblioteke. .NET Framework
je skup biblioteka.
C# kompajler se zove csc.exe. Za kompajliranje (prevoenje) moete
koristiti neko integrisano razvojno okruenje (IDE), kao to je Visual Studio, ili runo pozvati kompajler csc s komandne linije. Da biste
program preveli runo, prvo ga snimite u datoteku, npr. MyFirstPro
gram.cs, a zatim idite na komandnu liniju i pozovite csc (smeten u
%SystemRoot%\Microsoft.NET\Framework\<framework-version> gde
je %SystemRoot% va Windows direktorijum) na sledei nain:
Prvi program na jeziku C# | 7
csc MyFirstProgram.cs
Sintaksa
Sintaksa jezika C# inspirisana je sintaksom programskih jezika C i
C++. U ovom odeljku opisaemo elemente sintakse jezika C# na primeru sledeeg programa:
using System;
class Test
{
static void Main()
{
int x = 12 * 30;
Console.WriteLine (x);
}
}
Test
Main
Console
WriteLine
Identifikator mora biti cela re, sastavljena iskljuivo od Unicode znakova, i mora poinjati slovom ili znakom za podvlaenje. U C# identifikatorima pravi se razlika izmeu malih i velikih slova. Po konvenciji,
parametri, lokalne promenljive i privatna polja piu se tzv. kamiljom
notacijom CamelCase (npr., mojaPromenljiva), a svi drugi identifikatori Pascal notacijom (npr., MojaMetoda).
Rezervisane rei (engl. keywords) jesu imena koja je rezervisao kompajler i koja ne moete koristiti kao identifikatore. U naem primeru,
to su sledee rei:
using
class
static
void
int
long
stackalloc
as
event
namespace static
base
explicit new
string
float
char for
override true
params try
goto
const if
protected uint
public ulong
ref
default int
return ushort
unsafe
using
double is
short void
else lock
sizeof while
Izbegavanje konflikata
Ako ipak elite da koristite rezervisanu re kao identifikator, morate
ispred nje staviti znak @. Na primer:
class class {...}
class @class {...}
// Neispravno
// Ispravno
Sintaksa | 9
equals join
set
value
async get
on var
await
by
descending in
remove
Znak jednakosti obavlja operaciju dodele (engl. assignment), dok dvostruki znak jednakosti, ==, poredi dve vrednosti kako bi se utvrdilo da
li su jednake.
Komentari
C# podrava dva stila dokumentovanja izvornog koda: jednoredne
komentare i vieredne komentare. Jednoredni komentar poinje sa dve
kose crte i nastavlja se do kraja reda. Na primer:
int x = 3; // Komentar o dodeljivanju vrednosti 3 promenljivoj x
Osnove tipova
Tip definie prirodu date vrednosti. U naem primeru koristili smo
dva literala tipa int s vrednostima 12 i 30. Uz to, deklarisali smo i pro
menljivu tipa int po imenu x.
Promenljiva (engl. variable) oznaava lokaciju u memoriji koja moe
da sadri razliite vrednosti u razliito vreme. Nasuprot tome, kon
stanta uvek predstavlja istu vrednost (vie o tome kasnije).
U jeziku C#, svaka vrednost je instanca odreenog tipa. Znaenje
same vrednosti i skup moguih vrednosti date promenljive, odreeni su njenim tipom.
// Hello world2014
Unapred definisan tip bool ima samo dve mogue vrednosti: true i
false. Tip bool se obino koristi za uslovno grananje izvrnog toka
programa s naredbom if. Na primer:
bool simpleVar = false;
if (simpleVar)
Console.WriteLine (This will not print);
int x = 5000;
bool lessThanAMile = x < 5280;
if (lessThanAMile)
Console.WriteLine (This will print);
N apomena
Imenski prostor System u .NET Frameworku sadri mnoge vane
tipove koji u jeziku C# nisu unapred definisani (npr., DateTime).
// Polje
// Konstruktor
// Metoda
}
class Test
{
static void Main()
{
UnitConverter feetToInches = new UnitConverter(12);
UnitConverter milesToFeet = new UnitConverter(5280);
Console.Write (feetToInches.Convert(30));
Console.Write (feetToInches.Convert(100));
Console.Write (feetToInches.Convert
(milesToFeet.Convert(1)));
// 360
// 1200
// 63360
}
}
Osnove tipova | 13
lanovi tipa
Tip sadri podatke lanove (engl. data members) i funkcije lanice (engl.
function members). Podatak lan namenskog tipa UnitConverter jeste
polje (engl. field) po imenu ratio. Funkcije lanice tipa UnitConverter
jesu metoda Convert i konstruktor UnitConvertera.
Konstruktori i instanciranje
Podaci nastaju instanciranjem tipa. Unapred definisani tipovi instanciraju se jednostavno korienjem literala kao to je 12 ili Hello,
world.
Instance namenskog tipa pravi operator new. Metodu Main smo zapoeli tako to smo napravili dve instance tipa UnitConverter. Odmah
nakon to operator new instancira objekat, poziva se konstruktor tog
objekta da ga inicijalizuje. Konstruktor se definie kao metoda, s tim
to su ime metode i tip rezultata (povratne vrednosti) ime samog tipa:
public UnitConverter (int unitRatio)
{
ratio = unitRatio;
}
// Konstruktor
// Konstruktor
//
//
Population = Population+1; //
//
}
}
// Pan Dee
// Pan Dah
// 2
Rezervisana re public
Rezervisana re public izlae lanove klase drugim klasama. U ovom
primeru, kada polje Name klase Panda ne bi bilo javno, klasa Test ne
bi mogla da mu pristupi. Oznaavanjem svog lana rezervisanom reju
Osnove tipova | 15
p ublic, tip kae: Evo ta elim da vide ostali tipovi sve ostalo su moji
privatni detalji implementacije. U terminologiji objektno orijentisanog
programiranja, kaemo da javni lanovi kapsuliraju (engl. encapsulate)
privatne lanove klase.
Konverzije
C# moe da konvertuje instance kompatibilnih tipova jedne u druge. Konverzijom uvek nastaje nova vrednost od postojee. Konverzije mogu biti implicitne ili eksplicitne: implicitne konverzije se obavljaju automatski, dok je za eksplicitne konverzije potrebno pretvaranje
(engl. cast). U sledeem primeru, implicitno konvertujemo tip int u
tip long (koji ima dvaput vei kapacitet u bitovima od int) i eksplicit
no konvertujemo tip int u tip short (koji ima upola manji kapacitet
u bitovima od int):
int x = 12345;
long y = x;
short z = (short)x;
//
//
//
//
//
U optem sluaju, implicitne konverzije su dozvoljene kada kompajler moe da garantuje da e one uvek uspeti bez gubljenja informacija. U suprotnom, morate obaviti eksplicitnu konverziju izmeu kompatibilnih tipova.
Vrednosni tipovi
Sadraj pomenljive ili konstante vrednosnog tipa jeste samo neka vrednost. Na primer, ugraen vrednosni tip int sadri 32 bita podataka.
Namenski vrednosni tip moete definisati pomou rezervisane rei
struct (slika 1):
public struct Point { public int X, Y; }
Struktura Point
X
Y
Vrednost/Instanca
Kada se instanca vrednosnog tipa dodeljuje drugoj instanci, postojea instanca se uvek kopira. Na primer:
Point p1 = new Point();
p1.X = 7;
Point p2 = p1;
Console.WriteLine (p1.X);
Console.WriteLine (p2.X);
p1.X = 9; // Promena p1.X
Console.WriteLine (p1.X);
Console.WriteLine (p2.X);
Struktura Point
p2
7
0
Osnove tipova | 17
Referentni tipovi
Referentni tip je sloeniji od vrednosnog, i ima dva dela: objekat i refe
rencu na taj objekat. Promenljiva ili konstanta referentnog tipa sadri
referencu na objekat u kome se nalazi vrednost. Evo tipa Point iz prethodnog primera, s tim to je sada napisan kao klasa (slika 3):
public class Point { public int X, Y; }
Klasa Point
Referenca
Referenca
Objekat
Metapodaci
o objektu
X
Y
Vrednost/Instanca
(p1.X);
(p2.X);
(p1.X);
(p2.X);
//
//
//
//
//
//
Kopira referencu na p1
7
7
Promena p1.X
9
9
Klasa Point
p1
Referenca
Metapodaci
o objektu
p2
Referenca
9
7
Vrednost null
Referentnom tipu se moe dodeliti literal null, to znai da ta referenca
ne upuuje ni na jedan objekat. Pod pretpostavkom da je Point klasa:
Point p = null;
Console.WriteLine (p == null); // true
N apomena
C# ima specijalan konstrukt pod imenom tip koji prihvata vred
nost null (engl. nullable type) za predstavljanje praznih elemenata vrednosnog tipa (vie informacija u odeljku Tipovi koji prihvataju null na strani 135).
Osnove tipova | 19
Numeriki tipovi
C# sadri sledee unapred definisane numerike tipove:
C# tip
Sistemski tip
Sufiks
Veliina
Opseg
Celobrojni s predznakom
sbyte
SByte
8 bitova
od 27 do 271
short
Int16
16 bitova
od 215 do 2151
int
Int32
32 bita
od 231 do 2311
long
Int64
64 bita
od 263 do 2631
C# tip
Sistemski tip
Sufiks
Veliina
Opseg
Byte
8 bitova
od 0 do 281
ushort
UInt16
16 bitova
od 0 do 2161
uint
UInt32
32 bita
od 0 do 2321
ulong
UInt64
UL
64 bita
od 0 do 2641
float
Single
32 bita
double
Double
64 bita
decimal
Decimal
Realan broj
Meu celobrojnim tipovima, int i long su graani prvog reda i favorizuju ih i C# i CLR. Ostali celobrojni tipovi se obino koriste radi
postizanja interoperabilnosti ili kada je najvanije efikasno korienje memorije.
Meu tipovima realnih brojeva, float i double se zovu tipovi s pokret
nim zarezom (engl. floating-point types) i obino se koriste za nauna
izraunavanja. Tip decimal se najee upotrebljava za finansijske proraune, gde su potrebni aritmetika brojeva sa osnovom 10 i velika preciznost. (Tehniki, decimal je takoe tip s pokretnim zarezom, mada
se retko tako naziva.)
Numeriki literali
Za celobrojne literale moe da se koristi decimalna ili heksadecimalna
notacija; heksadecimalni literal se oznaava prefiksom 0x (na primer,
0x7f je isto to i 127). Realni literali mogu se pisati pomou decimalne
ili eksponencijalne notacije recimo, 1E06.
Numeriki tipovi | 21
(
1.0.GetType());
(
1E06.GetType());
(
1.GetType());
(0xF0000000.GetType());
//
//
//
//
Double (double)
Double (double)
Int32 (int)
UInt32 (uint)
Numeriki sufiksi
Numeriki sufiksi navedeni u prethodnoj tabeli eksplicitno definiu
tip literala:
decimal d = 3.5M;
Tehniki, sufiks D je suvian, zato to se pretpostavlja da su svi literali s decimalnom takom tipa double (a uvek moete dodati decimalnu taku numerikom literalu). Sufiksi F i M su najkorisniji i obavezni
su pri navoenju frakcionih literala tipa float ili decimal. Bez sufiksa,
sledei iskaz se ne bi preveo, jer bi se smatralo da je vrednost 4.5 tipa
double, koja se ne konvertuje implicitno u float ili decimal:
float f = 4.5F;
decimal d = -1.23M;
Konverzije brojeva
Konverzije celih brojeva u cele brojeve
Konverzije celih brojeva su implicitne kada odredini tip moe da
predstavlja svaku moguu vrednost izvornog tipa. U svim drugim sluajevima, neophodna je eksplicitna konverzija. Na primer:
int x = 12345;
long y = x;
short z = (short)x;
//
//
//
//
//
Aritmetiki operatori
Aritmetiki operatori (+, -, *, /, %) definisani su za sve numerike tipove osim za 8-bitne i 16-bitne celobrojne tipove. Operator % izraunava ostatak deljenja.
Numeriki tipovi | 23
// Rezultat je 0; x je sada 1
// Rezultat je 2; x je sada 2
// Rezultat je 1; x je sada 1
// true
checked
{
c = a * b;
...
}
Moete uiniti da se aritmetiko prekoraenje podrazumevano proverava za sve izraze u programu tako to ete za kompajliranje koristiti opciju komandne linije /checked+ (u okruenju Visual Studio, idite na Advanced Build Settings). Ukoliko nakon toga bude trebalo da
iskljuite proveru prekoraenja samo za odreene izraze ili naredbe,
to moete postii pomou operatora unchecked.
Znaenje
Komplement
I
Ili
Ekskluzivno ili
Pomeranje ulevo
Pomeranje udesno
Primer izraza
Rezultat
~0xfU
0xfffffff0U
0x30
0xf0 | 0x33
0xf3
0xff00 ^ 0x0ff0
0xf0f0
0x20 << 2
0x80
0x20 >> 1
0x10
Numeriki tipovi | 25
U ovom sluaju, x i y se implicitno konvertuju u int da bi se mogli sabrati. To znai da je rezultat takoe tipa int, koji se ne moe implicitno vratiti u tip short (jer bi se mogli izgubiti podaci). Da bi se ovaj kd
preveo, moramo dodati eksplicitno konvertovanje:
short z = (short) (x + y);
// OK
( 1.0
(1.0
( 1.0
(1.0
/
/
/
/
0.0);
0.0);
0.0);
0.0);
//
//
//
//
Beskonano
Minus beskonano
Minus beskonano
Beskonano
// NaN
// NaN
Kada se koristi operator ==, NaN vrednost nikada nije jednaka drugoj
vrednosti, ak ni drugoj NaN vrednosti. Da biste proverili da li je neka
vrednost NaN, morate koristiti metodu float.IsNaN ili double.IsNaN:
// False
// True
double
Osnova 2
1516 znaajnih cifara
(od ~10324 do ~10308)
+0, 0, +, i NaN
Zavisi od procesora
decimal
Osnova 10
2829 znaajnih cifara
(od ~1028 do ~1028)
Nema
Ne zavisi od procesora
(oko 10 puta sporije od tipa double)
// Nije ba 0.1
// -1.490116E-08
Zbog toga tipovi float i double nisu pogodni za finansijska izraunavanja. Za razliku od njih, tip decimal koristi osnovu 10 pa moe precizno da predstavi decimalne brojeve kao to je 0.1 (u ijem prikazu sa
osnovom 10 nema periodinog ponavljanja cifara).
Numeriki tipovi | 27
Jednakost referentnih tipova se, podrazumevano, zasniva na jednakosti referenci, a ne na jednakosti stvarnih vrednosti objekata. Prema
tome, dve instance objekta sa identinim podacima ne smatraju se
jednakim osim kada je operator == posebno preklopljen da bi se to
postiglo (vie informacija potraite u odeljcima Tip object na strani
81 i Preklapanje operatora na strani 140).
Operatori jednakosti i poreenja, ==, !=, <, >, >= i <=, rade sa svim numerikim tipovima, ali ih treba paljivo koristiti s realnim brojevima
(videti odeljak Greke zaokruivanja realnih brojeva na strani 27).
Operatori poreenja rade i sa nabrajanjima, tj. sa lanovima tipa enum,
tako to porede njihove pripadajue celobrojne vrednosti.
Uslovni operatori
Operatori && i || ispituju uslove and i or. esto se koriste zajedno s
operatorom !, koji znai not (nije). U ovom primeru, metoda UseUm28 | C# 5.0 Mali referentni prirunik
brella vraa true ako je kiovito ili sunano (da bismo se zatitili od
kie ili sunca), pod uslovom da nije i vetrovito (poto su kiobrani neupotrebljivi po vetru):
static bool UseUmbrella (bool rainy, bool sunny,
bool windy)
{
return !windy && (rainy || sunny);
}
Razlika je to to oni ne zaobilaze izraze, pa se stoga retko koriste umesto uslovnih operatora. Ternarni uslovni operator (koji se jednostavno zove uslovni operator, engl. conditional operator) ima oblik q ? a :
b, pri emu vai sledee: ako je uslov q ispunjen, izraunava se izraz a,
inae se izraunava b. Na primer:
static int Max (int a, int b)
{
return (a > b) ? a : b;
}
// Jednostavan znak
Znaenje
Vrednost
Polunavodnik
0x0027
Navodnik
0x0022
\\
0x005C
\0
0x0000
\a
Alarm
0x0007
\b
0x0008
\f
0x000C
\n
0x000A
\r
0x000D
\t
Horizontalni tabulator
Vertikalni tabulator
0x0009
0x000B
\v
Implicitna konverzija tipa char u numeriki tip mogua je za numerike tipove koji mogu da prime vrednost tipa short bez predznaka.
Za ostale numerike tipove neophodna je eksplicitna konverzija.
Tip string
U jeziku C#, tip string (alijas tipa System.String) predstavlja nepromenljivu sekvencu Unicode znakova. Literal tipa string zadaje se
unutar navodnika:
30 | C# 5.0 Mali referentni prirunik
string a = Heat;
N apomena
string je referentni tip a ne vrednosni. Meutim, njegovi opera-
Cena toga je sledea: kad god vam treba ba obrnuta kosa crta, morate je napisati dvaput:
string a1 = \\\\server\\fileshare\\helloworld.cs;
Doslovni znakovni literal moe se protezati i na vie redova. Znak navoda ete uvrstiti u doslovni literal tako to ete ga napisati dvaput.
Jedan od operanada ne mora biti znakovni niz; u tom sluaju, metoda ToString se poziva za tu vrednost. Na primer:
string s = a + 5; // a5
Viekratno korienje operatora + da bi se napravio znakovni niz esto je neefikasno: bolje je koristiti tip System.Text.String Builder
on predstavlja izmenljiv znakovni niz i ima metode za efikasno dodavanje (Append), umetanje (Insert), uklanjanje (Remove) i zamenu
(Replace) podnizova.
Nizovi
Niz (engl. array) predstavlja fiksan broj elemenata odreenog tipa.
Elementi niza se uvek uvaju u kontinualnom bloku memorije, pa im
se moe veoma efikasno pristupati.
Niz se oznaava uglastim zagradama iza tipa elemenata. Sledeim
izrazom deklarie se niz od pet znakova:
char[] vowels = new char[5];
// e
// aeiou
// aeiou
Tokom izvravanja se proverava da li su svi indeksi niza u zadatom opsegu. Ukoliko upotrebite nevaei indeks, generie se izuzetak Index
OutOfRangeException:
vowels[5] = y;
Svojstvo Length niza vraa broj elemenata niza. Kada je niz napra
vljen, ne moe mu se menjati broj elemenata. Imenski prostor System.
Collection i njemu podreeni imenski prostori obezbeuju strukture podataka vieg nivoa, kao to su dinamiki dimenzionirani nizovi i renici.
Nizovi | 33
ili, jednostavno:
char[] vowels = {a,e,i,o,u};
Svi nizovi nasleuju klasu System.Array, koja definie uobiajene metode i svojstva za sve nizove. To ukljuuje i svojstva instance kao to
su Length i Rank, te statike metode za:
dinamiku izradu niza (CreateInstance)
uitavanje i zadavanje elemenata nezavisno od tipa niza
(GetValue/SetValue)
pretraivanje sortiranog niza (BinarySearch) ili nesortiranog
niza (IndexOf, LastIndexOf, Find, FindIndex, FindLastIndex)
sortiranje niza (Sort)
kopiranje niza (Copy)
// 0
Viedimenzioni nizovi
Postoje dve varijante viedimenzionih nizova: pravougaoni i nazublje
ni. Pravougaoni (engl. rectangular) nizovi predstavljaju n-dimenzioni
blok memorije, dok su nazubljeni (engl. jagged) nizovi nizovi nizova.
Pravougaoni nizovi
U deklaracijama pravougaonih nizova, zarezi razdvajaju svaku dimenziju. Sledeom naredbom deklarie se pravougaoni dvodimenzioni niz, dimenzija 3 3:
int[,] matrix = new int [3, 3];
Nazubljeni nizovi
Nazubljeni nizovi se deklariu pomou uzastopnih ugaonih zagrada
koje predstavljaju svaku dimenziju niza. U sledeem primeru deklarie se nazubljen dvodimenzioni niz ija je najvea spoljna dimenzija 3:
int[][] matrix = new int[3][];
Nizovi | 35
Unutranje dimenzije nisu zadate u deklaraciji jer za razliku od pravougaonog niza svaki unutranji niz moe biti proizvoljne duine.
Svaki unutranji niz se implicitno inicijalizuje na vrednost null a ne
na prazan niz. Svaki unutranji niz mora se napraviti runo:
for (int i = 0; i < matrix.Length; i++)
{
matrix[i] = new int [3]; // Izrada unutranjeg niza
for (int j = 0; j < matrix[i].Length; j++)
matrix[i][j] = i * 3 + j;
}
Evo kako emo ovu metodu pozvati pomou niza koji pravimo na
licu mesta:
Foo ( new char[] {a,e,i,o,u} );
Foo ( new[] {a,e,i,o,u} );
// Pun oblik
// Preica
Promenljive i parametri
Promenljiva predstavlja memorijsku lokaciju ija je vrednost izmenljiva. Promenljiva moe da bude lokalna promenljiva, parametar (po vred
nosti, po referenci ili izlazni), polje (instance ili statiko), ili element niza.
Stek i hip
Stek i hip su mesta gde se smetaju promenljive i konstante. Svako od
njih ima veoma razliitu semantiku tokom ivotnog veka.
Stek
Stek je memorijski blok u koji se smetaju lokalne promenljive i parametri. On logiki raste i saima se kako funkcija zapone odnosno zavri rad. Razmotrite sledeu metodu (radi jednostavnosti, zanemaruje se provera ulaznih argumenata):
static int Factorial (int x)
{
if (x == 0) return 1;
return x * Factorial (x-1);
}
Promenljive i parametri | 37
Hip
Hip je memorijski blok u koji se smetaju objekti (tj., instance referentnog tipa). Kad god se napravi nov objekat, on se alocira na hipu, i vraa
se referenca na taj objekat. Tokom izvravanja programa, hip poinje da
se puni kako se stvaraju novi objekti. U izvrnom okruenju postoji sa
kuplja smea (engl. garbage collector) koji povremeno dealocira objekte
sa hipa, da raunar ne bi ostao bez memorije. Objekat je podloan de
alociranju im ga vie ne referencira nita to je i dalje ivo.
Instance vrednosnog tipa (i reference objekata) ive na mestima gde
je promenljiva deklarisana. Ukoliko je instanca deklarisana kao polje
unutar objekta ili kao element niza, ta instanca ivi na hipu.
N apomena
U jeziku C# ne moete eksplicitno brisati objekte kao to moete u jeziku C++. Nereferenciran objekat na kraju pokupi sakuplja smea.
Izriita dodela
C# namee politiku izriite dodele (engl. definite assignment). U praksi, to znai da je van unsafe konteksta nemogue pristupiti neinicijalizovanoj memoriji. Izriita dodela ima tri posledice:
Lokalnim promenljivama se mora dodeliti vrednost da bi se
mogle uitati.
Funkciji moraju biti predati argumenti pre nego to se pozove
metoda (osim ukoliko su oznaeni kao opcioni vie informacija u odeljku Opcioni parametri na strani 42).
Sve ostale promenljive (kao to su polja i elementi niza), automatski inicijalizuje izvrno okruenje.
Podrazumevane vrednosti
Sve instance tipa imaju podrazumevanu vrednost. Podrazumevana
vrednost unapred definisanih (ugraenih) tipova rezultat je posta
vljanja svih bitova instance u memoriji na nulu, i za referentne tipove je null, 0 za numerike i nabrojane tipove (enum), \0 za tip char,
i false za tip bool.
Podrazumevanu vrednost svakog tipa podataka saznaete pomou rezervisane rei default (u praksi, to je korisno za generike tipove,
kao to emo videti kasnije). Podrazumevana vrednost u namenskom
vrednosnom tipu (tj., struct) jednaka je podrazumevanoj vrednosti
svakog polja definisanog u tom namenskom tipu.
Parametri
Metoda ima niz parametara. Parametri definiu skup argumenata
koji se moraju proslediti metodi. U ovom primeru, metoda Foo ima
samo jedan parametar, po imenu p, tipa int:
static void Foo (int p) // p je parametar
{
...
}
static void Main() { Foo (8); } // 8 je argument
Promenljive i parametri | 39
Modifikator parametra
Nema
ref
out
Prosleuje se po
Vrednosti
Referenci
Referenci
// Inkrementira p za 1
// Ispisuje p na ekranu
// Pravi kopiju x
// x e i dalje biti 8
Dodeljivanjem nove vrednosti parametru p ne menja se sadraj promenljive x, poto se p i x nalaze na razliitim lokacijama u memoriji.
Pri prosleivanju argumenta referentnog tipa po vrednosti, kopira se
referenca, ali ne i objekat. U sledeem primeru, Foo vidi isti onaj objekat StringBuilder koji je instancirala metoda Main, ali ima nezavisnu
referencu na njega. Drugim reima, sb i fooSB su posebne promenljive koje referenciraju isti objekat StringBuilder:
static void Foo (StringBuilder fooSB)
{
fooSB.Append (test);
fooSB = null;
}
static void Main()
{
Modifikator ref
Za prosleivanje po referenci, C# ima modifikator parametara ref. U
narednom primeru, p i x referenciraju iste memorijske lokacije:
static void Foo (ref int p)
{
p = p + 1;
Console.WriteLine (p);
}
static void Main()
{
int x = 8;
Foo (ref x);
Console.WriteLine (x);
}
// Prosleuje x po referenci
// x je sada 9
Dodeljivanje nove vrednosti parametru p sada menja sadraj promenljive x. Obratite panju na to da je modifikator ref neophodan i pri
pisanju i pri pozivanju metode. Zahvaljujui tome, potpuno je jasno
ta se deava.
N apomena
Parametar se moe proslediti po referenci ili po vrednosti, nezavisno od toga da li je referentnog ili vrednosnog tipa.
Promenljive i parametri | 41
Modifikator out
Argument out slian je argumentu ref, osim to:
ne mora imati dodeljenu vrednost pre ulaska u funkciju
mora imati dodeljenu vrednost pre nego to se vrati iz funkcije
Modifikator out se najee koristi da bi se kao rezultat metode dobilo vie izlaznih vrednosti.
Modifikator params
Modifikator params moe se zadati uz poslednji parametar metode,
tako da ona prihvata proizvoljan broj parametara odreenog tipa. Tip
parametra se mora deklarisati kao niz. Na primer:
static int Sum (params int[] ints)
{
int sum = 0;
for (int i = 0; i < ints.Length; i++) sum += ints[i];
return sum;
}
// 10
Opcioni parametri
Poevi od verzije C# 4.0, metode, konstruktori i indekseri mogu da
deklariu opcione parametre. Parametar je opcioni ako je u njegovoj
deklaraciji navedena podrazumevana vrednost:
void Foo (int x = 23) { Console.WriteLine (x); }
// 23
Podrazumevani argument 23 u stvari se prosleuje opcionom parametru x kompajler upisuje vrednost 23 u prevedeni kd na pozivajuoj
strani. Prethodni poziv funkcije Foo semantiki je identian sledeem:
Foo (23);
U pozorenje
Dodavanje opcionog parametra javnoj metodi koja se poziva iz
drugog sklopa zahteva ponovno kompajliranje oba sklopa isto
kao da su parametri obavezni.
Podrazumevana vrednost opcionog parametra mora da se navede pomou konstantnog izraza, ili konstruktora vrednosnog tipa bez parametara. Opcioni parametri se ne mogu oznaiti modifikatorom ref
ili out.
Obavezni parametri moraju biti ispred opcionih i u deklaraciji i u pozivu metode (izuzetak su params argumenti, koji su i dalje uvek na
kraju). U sledeem primeru, eksplicitna vrednost 1 prosleuje se promenljivoj x, a podrazumevana vrednost 0 promenljivoj y:
void Foo (int x = 0, int y = 0)
{
Console.WriteLine (x + , + y);
}
void Test()
{
Foo(1);
// 1, 0
}
Da biste uradili suprotno (prosledili podrazumevanu vrednost promenljivoj x a eksplicitnu y), morate kombinovati opcione parametre
sa imenovanim argumentima.
Promenljive i parametri | 43
Imenovani argumenti
Umesto da identifikujete argument po poloaju, moete ga identifikovati po imenu. Na primer:
void Foo (int x, int y)
{
Console.WriteLine (x + , + y);
}
void Test()
{
Foo (x:1, y:2); // 1, 2}
Imenovani argumenti mogu biti navedeni bilo kojim redosledom. Naredni pozivi funkcije Foo semantiki su identini:
Foo (x:1, y:2);
Foo (y:2, x:1);
Imenovani argumenti su posebno korisni kada se upotrebljavaju zajedno sa opcionim parametrima. Na primer, razmotrite sledeu metodu:
void Bar (int a=0, int b=0, int c=0, int d=0) { ... }
Zbog ove direktne ekvivalencije, promenljive kojima se tip zadaje implicitno (automatski), postaju statike. Recimo, sledei kd generie
greku pri prevoenju:
var x = 5;
x = hello;
Izrazi i operatori
U osnovi, izraz oznaava odreenu vrednost. Najjednostavnije vrste
izraza su konstante (npr. 123) i promenljive (npr. x). Izrazi se mogu
transformisati i kombinovati pomou operatora. Operatori deluju na
jedan ili vie ulaznih operanada i kao rezultat daju nov izraz:
12 * 30
// * je operator; 12 i 30 su operandi.
Izrazi i operatori | 45
Izrazi dodele
U izrazu dodele (engl. assignment expression) koristi se operator =
kako bi se rezultat drugog izraza dodelio promenljivoj. Na primer:
x = x * 5
Sloeni operatori dodele (engl. compound assignment operators) sintaksike su preice u kojima se kombinuje dodela s nekim drugim operatorom. Na primer:
x *= 2
x <<= 1
// isto to i x = x * 2
// isto to i x = x << 1
Prioritet
Izraz 1 + 2 * 3 izraunava se kao 1 + (2 * 3) poto * ima vii prioritet od +.
Tabela operatora
U sledeoj tabeli, operatori iz jezika C# navedeni su po redosledu prioriteta. Operatori navedeni pod istim podnaslovom imaju isti prioritet. Operatore koje korisnici mogu preklapati objanjavamo u odeljku
Preklapanje operatora na strani 140.
Operator
Ime operatora
Primarni (najvii prioritet)
.
Pristup lanu
->
Pokaziva na strukturu
(nebezbedno)
Primer
Preklopiv
x.y
x->y
Ne
Ne
()
Poziv funkcije
x()
Ne
[]
Niz/indeks
a[x]
Preko indeksera
++
Post-inkrement
x++
Da
Post-dekrement
x-
Da
Pravljenje instance
new Foo()
Ne
new
Izrazi i operatori | 47
Operator
stackalloc
typeof
checked
unchecked
default
await
Ime operatora
Nebezbedna alokacija steka
Pribavljanje tipa identifikatora
Ukljuena provera prekoraenja
u celobrojnoj aritmetici
Iskljuena provera prekoraenja
u celobrojnoj aritmetici
Podrazumevana vrednost
ekanje
Primer
typeof(int)
Preklopiv
Ne
Ne
checked(x)
Ne
unchecked(x)
Ne
default(char)
await mytask
Ne
Ne
stackalloc(10)
Unarni
sizeof
sizeof(int)
Ne
Pozitivna vrednost
+x
Da
Negativna vrednost
-x
Da
Nije
!x
Da
~x
Da
++
Pre-inkrement
++x
Da
--
Post-inkrement
--x
Da
()
Eksplicitna konverzija
(int)x
Ne
*x
Ne
&
&x
Ne
Mnoenje
x * y
Da
Deljenje
x / y
Da
Ostatak
x % y
Da
Sabiranje
x + y
Da
Oduzimanje
x - y
Da
<<
Pomeranje ulevo
x << 1
Da
>>
Pomeranje udesno
x >> 1
Da
Multiplikativni
Aditivni
Pomeranja
Operator
Ime operatora
Primer
Preklopiv
<
Manje od
x < y
Da
>
Vee od
x > y
Da
<=
x <= y
Da
>=
x >= y
Da
is
x is y
Ne
as
Konverzija tipa
x as y
Ne
==
Jednako
x == y
Da
!=
Nije jednako
x != y
Da
x & y
Da
Ekskluzivno ili
x ^ y
Da
Ili
x | y
Da
Uslovno i
x && y
Preko &
Uslovno ili
x || y
Preko |
isTrue ? then
Ne
Relacioni
Jednakosti
Logiko And
&
Logiko Xor
^
Logiko Or
|
Uslovno And
&&
Uslovno Or
||
Uslovni (ternarni)
? :
Uslovni
This : elseThis
Dodela
x = y
Ne
*=
Mnoenje i dodela
x *= 2
Preko *
/=
Deljenje i dodela
x /= 2
Preko /
+=
Sabiranje i dodela
x += 2
Preko +
Izrazi i operatori | 49
Operator
Ime operatora
Primer
Preklopiv
-=
Oduzimanje i dodela
x -= 2
Preko -
<<=
x <<= 2
Preko <<
>>=
x >>= 2
Preko >>
&=
x &= 2
Preko &
^=
x ^= 2
Preko ^
|=
Operacija Or i dodela
x |= 2
Preko |
=>
Lambda
x => x + 1
Ne
Naredbe
Funkcije se sastoje od naredaba koje se izvravaju sekvencijalno, po
redosledu kojim se pojavljuju. Blok naredaba je niz naredaba navedenih izmeu vitiastih zagrada (simbola {}).
Naredbe za deklarisanje
Naredba za deklarisanje deklarie novu promenljivu, opciono je inicijalizujui pomou nekog izraza. Naredba za deklarisanje zavrava se
znakom taka i zarez. Vie promenljivih istog tipa moete deklarisati
pomou liste u kojoj su te promenljive razdvojene zarezima. Na primer:
bool rich = true, famous = false;
Naredbe za izraze
Naredbe za izraze (engl. expression statements) jesu izrazi koji su
istovremeno i ispravne naredbe. U praksi, to znai da ti izrazi neto
rade; drugim reima, to su izrazi koji:
dodeljuju vrednost promenljivoj ili modifikuju promenljivu
instanciraju objekat
pozivaju metodu
Izrazi koji ne rade nita od toga, nisu ispravne naredbe:
string s = foo;
s.Length;
Naredbe za biranje
Naredbe za biranje (engl. selection statements) upravljaju tokom izvravanja programa.
Naredba if
Naredba if izvrava odreenu naredbu ako je logiki (bool) izraz istinit. Na primer:
if (5 < 2 * 3)
Console.WriteLine (true);
// true
// true
Naredbe | 51
Odredba else
Naredba if moe da sadri i odredbu else:
if (2 + 2 == 5)
Console.WriteLine (Does not compute);
else
Console.WriteLine (False);
// False
Console.WriteLine();
}
else
Console.WriteLine (does not execute);
C# nema rezervisanu re elseif; meutim, pomou sledeeg ablona postie se isti rezultat:
static void TellMeWhatICanDo (int age)
{
if (age >= 35)
Console.WriteLine (You can be president!);
else if (age >= 21)
Console.WriteLine (You can drink!);
else if (age >= 18)
Console.WriteLine (You can vote!);
else
Console.WriteLine (You can wait!);
}
Naredba switch
Naredbe switch omoguavaju da se izvravanje programa grana na
osnovu moguih vrednosti promenljive. Naredbe switch mogu dati
istiji kd nego kada se koristi vie naredaba if, poto s naredbama
s witch izraz mora da se izraunava samo jednom. Na primer:
static void ShowCard (int cardNumber)
{
switch (cardNumber)
{
case 13:
Console.WriteLine (King);
break;
case 12:
Console.WriteLine (Queen);
break;
case 11:
Console.WriteLine (Jack);
break;
default: // Bilo koji drugi cardNumber
Naredbe | 53
Console.WriteLine (cardNumber);
break;
}
}
Naredbom switch moe upravljati samo izraz tipa koji se moe izraunati statiki, to je ograniava na tip string, ugraene celobrojne tipove, enum tipove, i verzije tih tipova koje prihvataju null (videti Tipovi koji prihvataju null, na strani 135). Na kraju svake odredbe case,
morate eksplicitno navesti pomou neke naredbe za skok odakle
se nastavlja izvravanje. Evo raspoloivih opcija:
break (skae na kraj naredbe switch)
goto case x (skae na neki drugu odredbu case)
goto default (skae na odredbu default)
Bilo koja druga naredba za skok konkretno, return, throw,
continue ili goto oznaka (engl. label)
Kada isti kd treba izvriti za vie od jedne vrednosti, odredbe case
moete navesti u obliku liste:
switch (cardNumber)
{
case 13:
case 12:
case 11:
Console.WriteLine (Face card);
break;
default:
Console.WriteLine (Plain card);
break;
}
Naredbe za iteraciju
C# omoguava da se ponavlja izvravanje grupe naredaba; za to slue naredbe while, do-while, for i foreach.
Petlje do-while razlikuju se od petlji while samo po tome to se u njima izraz ispituje nakon izvravanja bloka naredaba (to obezbeuje
da se blok uvek izvri bar jednom). Evo prethodnog primera napisanog uz korienje petlje do-while:
int i = 0;
do
{
Console.WriteLine (i++);
}
while (i < 3);
Petlje for
Petlje for su sline petljama while s posebnim odredbama za inicijalizaciju i iteraciju promenljive petlje. Petlja for sadri sledee tri odredbe:
for (odredba_za_inicijalizaciju; odredba_uslov;
odredba_za_iteraciju)
naredbe ili blok naredaba
Odredba_za_inicijalizaciju se izvrava pre poetka petlje, i obino inicijalizuje jednu ili vie promenljivih iteracije.
Odredba_uslov je logiki izraz koji se ispituje pre svake iteracije petlje.
Telo programa se izvrava sve dok je taj uslov istinit.
Odredba_za_iteraciju se izvrava nakon svake iteracije tela programa.
Obino se koristi za auriranje iteracione promenljive.
Naredbe | 55
Svaki od tri dela naredbe for moe se izostaviti. Mogue je implementirati beskonanu petlju kao to je sledea (mada se umesto toga moe
koristiti while(true)):
for (;;) Console.WriteLine (interrupt me);
Petlje foreach
Naredba foreach prolazi (iterira) kroz svaki element u nabrojivom
objektu. U jeziku C# i.NET Frameworku, veina tipova koji predsta
vljaju skup ili listu elemenata nabrojivi su. Recimo, i niz i znakovni
niz su nabrojivi. Evo primera nabrajanja znakova iz niza, od prvog do
poslednjeg znaka:
foreach (char c in beer)
Console.WriteLine (c + );
// b e e r
Naredbe za skok
U jeziku C#, naredbe za skok su break, continue, goto, return i throw.
Rezervisanu re throw opisujemo u odeljku Naredbe try i izuzeci na
strani 122.
Naredba break
Naredba break prekida izvravanje tela iteracione petlje ili naredbe
switch:
int x = 0;
while (true)
{
if (x++ > 5) break;
// izlazak iz petlje
}
// izvravanje se nastavlja ovde nakon prekida
...
Naredba continue
Naredba continue zanemaruje ostale naredbe u petlji i zapoinje sledeu iteraciju. Naredna petlja preskae parne brojeve:
for (int i = 0; i < 10; i++)
{
if ((i % 2) == 0) continue;
Console.Write (i + );
// 1 3 5 7 9
Naredba goto
Naredba goto prenosi izvravanje na oznaku (obeleenu dvotakom
na kraju) unutar bloka naredaba. Sledei kd prolazi kroz brojeve od
1 do 5, imitirajui petlju for:
int i = 1;
startLoop:
if (i <= 5)
{
Console.Write (i + ); // 1 2 3 4 5
i++;
goto startLoop;
}
Naredba return
Naredba return slui za izlazak iz metode i ukoliko metoda nije
void mora da vrati izraz s povratnim tipom te metode:
Naredbe | 57
Imenski prostori
Imenski prostor je domen unutar koga imena tipova moraju biti jedinstvena. Tipovi su obino organizovani u hijerarhijske imenske prostore i da bi se izbegli konflikti pri imenovanju i da bi se imena tipova lake pronala. Na primer, tip RSA koji slui za ifrovanje javnim
kljuem definisan je u sledeem imenskom prostoru:
System.Security.Cryptography
Imenski prostor je sastavni deo imena tipa. Sledei kd poziva metodu Create tipa RSA:
System.Security.Cryptography.RSA rsa =
System.Security.Cryptography.RSA.Create();
N apomena
Imenski prostori ne zavise od sklopova, koji su jedinice primene
koda, kao to je jedna .exe ili .dll datoteka.
Imenski prostori ne utiu ni na mogunost pristupanja lanovima public, internal, private itd.
class Class2 {}
}
Take u imenskom prostoru predstavljaju hijerarhiju ugneenih imenskih prostora. Sledei kd je semantiki identian prethodnom primeru.
namespace Outer
{
namespace Middle
{
namespace Inner
{
class Class1 {}
class Class2 {}
}
}
}
Direktiva using
Direktiva using uvozi imenski prostor i pogodna je za referenciranje tipova bez izriitog navoenja njihovih potpuno kvalifikovanih imena.
Recimo, Class1 iz prethodnog primera moemo referencirati ovako:
using Outer.Middle.Inner;
class Test
// Test je u globalnom imenskom prostoru
{
static void Main()
{
Imenski prostori | 59
Class1 c;
...
}
}
Sakrivanje imena
Ako se isto ime tipa pojavljuje i u unutranjem i u spoljanjem imenskom prostoru, vai ono iz unutranjeg. Da biste referencirali tip iz
spoljanjeg imenskog prostora, morate mu kvalifikovati ime.
N apomena
Sva imena tipova konvertuju se u potpuno kvalifikovana imena tokom kompajliranja. Meukod (engl. Intermediate Langua
ge code, IL code) ne sadri ni nekvalifikovana ni delimino kvalifikovana imena.
Kvalifikator global::
Ponekad i potpuno kvalifikovano ime tipa moe da bude u sukobu sa
unutranjim imenom. C# e koristiti potpuno kvalifikovano ime ako
ispred njega napiete global:::
global::System.Text.StringBuilder sb;
Imenski prostori | 61
Klase
Meu referentnim tipovima, najee se koristi klasa. Evo najjednostavnije mogue deklaracije klase:
class Foo
{
}
Unutar vitiastih
zagrada
Polja
Polje je promenljiva koja je lanica klase ili strukture. Na primer:
class Octopus
{
string name;
public int Age = 10;
}
Metode
Metoda izvrava odreenu operaciju definisanu kao grupa naredaba.
Moe da primi ulazne podatke od pozivaoca tako to joj se zadaju parametri i da vrati rezultat pozivaocu tako to joj se zada povratni tip.
Povratni tip metode moe da bude void, to znai da ne vraa nikakvu vrednost svom pozivaocu. Metoda moe takoe da vraa pozivaocu izlazne podatke (rezultat) preko parametara ref i out.
Potpis (engl. signature) metode mora biti jedinstven unutar datog tipa.
Potpis metode se sastoji od tipova njenog imena i parametara (ali ne
od imena parametara, niti od povratnog tipa).
Preklapanje metoda
Tip moe da preklapa (engl. overloads) svoje metode (tj. da ima vie
metoda istog imena), sve dok su tipovi parametara metode razliiti. Recimo, sve sledee metode mogu da postoje istovremeno u istom
tipu:
void
void
void
void
Foo
Foo
Foo
Foo
(int x);
(double x);
(int x, float y);
(float x, int y);
Konstruktori instanci
Konstruktori izvravaju inicijalizacioni kd nad klasom ili strukturom. Konstruktor se definie kao metoda, s tim to su ime metode i
povratni tip isti kao ime sadranog tipa:
public class Panda
{
Klase | 63
string name;
public Panda (string n)
{
name = n;
}
}
...
Panda p = new Panda (Petey);
// Definie polje
// Definie konstruktor
// Inicijalizacioni kd
// Poziva konstruktor
Klasa ili struktura mogu da preklapaju konstruktore. Jedno preklapanje moe da poziva drugo, pomou rezervisane rei this:
public class Wine
{
public Wine (decimal price) {...}
public Wine (decimal price, int year)
: this (price) {...}
}
Inicijalizatori objekata
Da bi se objekti lake inicijalizovali, dostupna polja ili svojstva objekta mogu se inicijalizovati pomou inicijalizatora objekata, odmah nakon konstruisanja. Na primer, razmotrite sledeu klasu:
public class Bunny
{
public string Name;
public bool LikesCarrots, LikesHumans;
public Bunny () {}
public Bunny (string n) { Name = n; }
}
Referenca this
Referenca this upuuje na samu instancu. U sledeem primeru, metoda Marry koristi this za inicijalizovanje polja mate promenljive partner:
Klase | 65
Referenca this je upotrebljiva samo unutar nestatikih lanova klase ili strukture.
Svojstva
Svojstva (engl. properties) spolja izgledaju kao polja, ali sadre unutranju logiku, kao metode. Na primer, gledajui sledei kd ne moete zakljuiti da li je CurrentPrice polje ili svojstvo:
Stock msft = new Stock();
msft.CurrentPrice = 30;
msft.CurrentPrice -= 3;
Console.WriteLine (msft.CurrentPrice);
svojstvo, koji obino dodeljujete nekom privatnom polju (u ovom sluaju, currentPrice).
Mada se svojstvima pristupa na isti nain kao poljima, ona se razlikuju po tome to onome ko ih implementira daju potpunu kontrolu
nad uitavanjem i zadavanjem njihove vrednosti. Zahvaljujui tome,
osoba koja implementira svojstva moe da izabere potreban nain internog predstavljanja a da pritom ne otkriva interne detalje korisniku
svojstva. U ovom primeru, metoda set bi generisala izuzetak kada bi
value bila izvan vaeeg opsega vrednosti.
N apomena
Kroz celu knjigu koristimo javna polja da bi primeri bili jedno
stavniji. U stvarnoj aplikaciji, najee biste davali prednost
javnim svojstvima nad javnim poljima kako biste podstakli
kapsuliranje.
Klase | 67
Automatska svojstva
Najea implementacija svojstva je metoda get i/ili set koja samo ita
iz privatnog polja istog tipa kao svojstvo, odnosno upisuje u njega. Deklaracija automatskog svojstva nalae kompajleru da omogui tu implementaciju. Evo kako emo redeklarisati prvi primer iz ovog odeljka:
public class Stock
{
public decimal CurrentPrice { get; set; }
}
Zapazite da smo svojstvo deklariete sa slobodnijim nivoom pristupa (u ovom sluaju, public), a modifikator dodajete onoj pristupnoj
metodi koju elite da uinite manje pristupanom.
Indekseri
Indekseri obezbeuju prirodnu sintaksu za pojedinano pristupanje
elementima klase ili strukture koja kapsulira listu ili renik vrednosti. Indekseri su slini svojstvima, ali im se pristupa preko argumenta indeksa umesto preko imena svojstva. Klasa string ima indekser
koji omoguava da pristupite svakoj od njenih char vrednosti preko
int indeksa:
68 | C# 5.0 Mali referentni prirunik
string s = hello;
Console.WriteLine (s[0]); // h
Console.WriteLine (s[3]); // l
Sintaksa za upotrebu indeksera ista je kao ona za nizove, s tim to argument(i) indeksa moe biti bilo kog tipa.
Implementiranje indeksera
Da biste napisali indekser, definiite svojstvo po imenu this, zadajui
argumente u uglastim zagradama. Na primer:
class Sentence
{
string[] words = The quick brown fox.Split();
public string this [int wordNum]
{
get { return words [wordNum]; }
set { words [wordNum] = value; }
}
// indekser
// fox
// kangaroo
Jedan tip moe da deklarie vie indeksera, od kojih svaki ima parametre razliitog tipa. Indekser takoe moe da ima vie od jednog
parametra:
public string this [int arg1, string arg2]
{
get { ... } set { ... }
}
Klase | 69
Konstante
Konstanta je statiko polje ija se vrednost nikada ne moe promeniti. Konstanta se izraunava statiki u vreme prevoenja i kompajler
doslovno zamenjuje njenu vrednost gde god je nae (slino kao makro u jeziku C++). Konstanta moe da bude bilo kog od ugraenih
numerikih tipova bool, char, string ili neki nabrojivi tip.
Konstanta se deklarie pomou rezervisane rei const i mora da se
inicijalizuje nekom vrednou. Na primer:
public class Test
{
public const string Message = Hello World;
}
Statiki konstruktori
Statiki konstruktor se izvrava jednom po tipu umesto jednom po
svakoj instanci tipa. Jedan tip moe da definie samo jedan statiki
konstruktor, koji ne sme imati parametre i mora imati isto ime kao tip:
class Test
{
static Test() { Console.Write (Type Initialized); }
}
Izvrno okruenje automatski poziva statiki konstruktor neposredno pre upotrebe tipa. To se deava u dva sluaja: instanciranje tipa i
pristupanje statikom lanu u tipu.
U pozorenje
Ako statiki konstruktor generie neobraen izuzetak, taj tip
postaje neupotrebljiv tokom ivota aplikacije.
Statike klase
Klasa moe biti oznaena kao static, to znai da mora sadrati samo
statike lanove i ne moe imati potklase. Klase System.Console i
System.Math dobri su primeri statikih klasa.
Finalizatori
Finalizatori su metode koje se mogu deklarisati samo unutar klasa i
izvravaju se pre nego to sakuplja smea ponovo zatrai memoriju
za nereferenciran objekat. Sintaksu finalizatora ini ime klase kojem
prethodi simbol ~:
class Class1
{
~Class1() { ... }
}
C# prevodi finalizator u metodu koja redefinie (engl. overrides) metodu Finalize u klasi object. Sakupljanje smea i finalizatori detaljno
su opisani u poglavlju 12 knjige C# 5.0 za programere.
Klase | 71
Parcijalne metode
Parcijalni tip moe da sadri parcijalne metode. One omoguavaju
da automatski generisan parcijalni tip obezbedi prilagodljive ablone
(engl. hooks) za runo dopunjavanje. Na primer:
partial class PaymentForm // U automatski generisanoj datoteci
{
partial void ValidatePayment (decimal amount);
}
partial class PaymentForm // U datoteci napisanoj runo
{
partial void ValidatePayment (decimal amount)
{
if (amount > 100) Console.Write (Expensive!);
}
}
Nasleivanje
Klasa moe da nasleuje drugu klasu kako bi se originalna klasa proirila ili prilagodila. Nasleivanje (engl. inheriting) klase omoguava
da ponovo koristite funkcionalnost te klase umesto da je piete iz poetka. Klasa moe da nasleuje samo jednu klasu, ali nju mogu da
naslede mnoge druge klase i da se tako formira hijerarhija klasa. U
ovom primeru poinjemo tako to definiemo klasu po imenu Asset:
public class Asset { public string Name; }
// nasleuje od Asset
// nasleuje od Asset
{
public decimal Mortgage;
}
// MSFT
Nasleivanje | 73
Console.WriteLine (msft.SharesOwned);
// 1000
// Mansion
// 250000
Polimorfizam
Reference su polimorfne. To znai da promenljiva tipa x moe referencirati objekat koji je potklasa od x. Na primer, razmotrite sledeu metodu:
public static void Display (Asset asset)
{
System.Console.WriteLine (asset.Name);
}
Ova metoda moe da prikae i Stock i House, poto su obe Asset. Polimorfizam funkcionie na osnovu toga to potklase (Stock i House)
imaju sve osobine svoje osnovne klase (Asset). Meutim, obrnuto ne
vai. Kada bi metoda Display bila prepravljena da prihvati House, ne
biste mogli da joj prosledite Asset.
Konvertovanje referenci
Referenca na objekat moe da bude:
implicitno konvertovana navie (engl. upcast) u referencu osnovne klase
eksplicitno konvertovana nanie (engl. downcast) u referencu
potklase
Konvertovanjem navie i nanie izmeu kompatibilnih referentnih tipova obavljaju se konverzije referenci: pravi se nova referenca koja pokazuje na isti objekat. Konvertovanje navie uvek uspeva; konvertovanje nanie uspeva samo ako je objekat odgovarajueg tipa.
Konvertovanje navie
Pomou operacije konvertovanja navie pravi se referenca na osnovnu
klase od reference potklase. Na primer:
Stock msft = new Stock();
Asset a = msft;
// Iz prethodnog primera
// Konvertovanje navie
Nakon konvertovanja navie, promenljiva a i dalje referencira isti objekat Stock kao promenljiva msft. Sm referencirani objekat se ne menja niti se konvertuje:
Console.WriteLine (a == msft);
// True
// OK
// Greka
Poslednji red koda generie greku pri prevoenju zato to je promenljiva a tipa Asset, bez obzira na to to referencira objekat tipa Stock.
Da biste doli do njenog polja SharesOwned, morate Asset konvertovati nanie u Stock.
Konvertovanje nanie
Pomou operacije konvertovanja nanie pravi se referenca potklase od
reference osnovne klase. Na primer:
Stock msft = new Stock();
Asset a = msft;
Stock s = (Stock)a;
Console.WriteLine (s.SharesOwned);
Console.WriteLine (s == a);
Console.WriteLine (s == msft);
//
//
//
//
//
Konvertovanje navie
Konvertovanje nanie
<Nema greke>
True
True
citna konverzija zato to, tokom izvravanja, ta operacija se moe zavriti i neuspehom:
House h = new House();
Asset a = h;
// Konvertovanje navie uvek uspeva
Stock s = (Stock)a;
// Konvertovanje nanie nije uspelo:
// a nije Stock
Operator as
Operator as obavlja konvertovanje nanie u kome se, u sluaju ne
uspeha, dobija null (a ne izuzetak):
Asset a = new Asset();
Stock s = a as Stock;
Operator is
Operator is ispituje da li e konverzija reference uspeti; drugim reima, da li se objekat izvodi iz zadate klase (ili implementira interfejs).
esto se koristi za ispitivanje pre konvertovanja nanie:
if (a is Stock) Console.Write (((Stock)a).SharesOwned);
Operator is ne razmatra namenske i numerike konverzije, ali razmatra konverzije raspakivanja, engl. unboxing conversions (videti Tip
object na strani 81).
Potpisi, povratni tipovi i dostupnost virtuelnih i redefinisanih metoda moraju biti identini. Redefinisana metoda moe da pozove implementaciju svoje osnovne klase pomou rezervisane rei base (videti
Rezervisana re base na strani 79).
Nasleivanje | 77
Modifikator new saoptava vau nameru kompajleru i drugim programerima da duplirani lan nije sluajnost.
Moete zatvoriti i samu klasu, ime e se implicitno zatvoriti sve virtuelne funkcije, tako to ete modifikator sealed primeniti na samu klasu.
Rezervisana re base
Rezervisana re base slina je rezervisanoj rei this. Ona ima dve
osnovne svrhe: pristupanje redefinisanoj funkciji lanici iz potklase, i
pozivanje konstruktora osnovne klase (videti naredni odeljak).
U ovom primeru, potklasa House koristi rezervisanu re base da bi pristupila implementaciji svojstva Liability klase Asset:
public class House : Asset
{
...
public override decimal Liability
{
get { return base.Liability + Mortgage; }
}
}
Pomou rezervisane rei base, svojstvu Liability klase Asset pristupamo nevirtuelno. To znai da emo uvek pristupati Asset-ovoj verziji
ovog svojstva bez obzira na stvarni tip instance u vreme izvravanja.
Isti pristup vai i kada je svojstvo Liability skriveno a ne redefinisano.
(Skrivenim lanovima moete pristupiti i tako to ete ih eksplicitno
konvertovati u osnovnu klasu pre pozivanja funkcije.)
Konstruktori i nasleivanje
Potklasa mora da deklarie sopstvene konstruktore. Recimo, ako definiemo Baseclass i Subclass ovako:
public class Baseclass
{
public int X;
public Baseclass () { }
public Baseclass (int x) { this.X = x; }
Nasleivanje | 79
}
public class Subclass : Baseclass { }
Preklapanje i razreavanje
Nasleivanje ima zanimljiv uticaj na preklapanje metoda. Razmotrite
sledea dva preklapanja:
// Poziva Foo(House)
Koje e se preklapanje pozvati, odreuje se statiki (u vreme prevoenja) a ne u vreme izvravanja. Sledei kd poziva Foo(Asset), mada je
a tipa House u vreme izvravanja:
Asset a = new House (...);
Foo(a);
// Poziva Foo(Asset)
N apomena
Ako konvertujete Asset u dynamic (videti Dinamiko povezivanje na strani 171), odluka o tome koje preklapanje pozvati odlae
se do vremena izvravanja i zasniva se na stvarnom tipu objekta.
Tip object
object (System.Object) predstavlja vrhovnu osnovnu klasu za sve tipove. Svaki tip se moe implicitno konvertovati navie u tip object.
Tip object | 81
Poto Stack radi s tipom object, moemo koristiti komande Push i Pop
da bismo instance bilo kog tipa stavljali na stek, odnosno da bismo
ih skidali sa steka:
Stack stack = new Stack();
stack.Push (sausage);
string s = (string) stack.Pop();
Console.WriteLine (s);
// Konvertovanje nanie
// sausage
lan posao kako bi se premostile razlike izmeu vrednosnih i referentnih tipova. Taj proces se zove pakovanje (engl. boxing) i raspakivanje
(engl.unboxing).
N apomena
U odeljku Generike komponente na strani 95, opisujemo
kako poboljati klasu Stack da bi bolje radila sa stekovima koji
sadre elemente istog tipa.
Pakovanje i raspakivanje
Pakovanje je operacija konvertovanja instance vrednosnog tipa u instancu referentnog tipa. Referentni tip moe biti klasa object ili interfejs (videti Interfejsi na strani 88). U ovom primeru, pakujemo int
u objekat:
int x = 9;
object obj = x;
Za raspakivanje je neophodna eksplicitna konverzija. Izvrno okruenje proverava da li zadati vrednosni tip odgovara stvarnom tipu objekta, i ako provera ne uspe generie izuzetak InvalidCastException.
82 | C# 5.0 Mali referentni prirunik
Na primer, sledei kd generie izuzetak poto tip long ne odgovara tipu int:
object obj = 9;
// InvalidCastException
Kao i ovaj:
object obj = 3.5;
// x je sada 3
// 3
Tip object | 83
(x.GetType().Name);
(typeof(int).Name);
(x.GetType().FullName);
(x.GetType() == typeof(int));
// Int32
// Int32
// System.Int32
// true
System.Type takoe ima metode koje slue kao prolaz ka modelu re-
// false
// true
Tip object | 85
Metoda ToString
Metoda ToString vraa podrazumevan tekstualni prikaz instance
tipa. Ovu metodu redefiniu svi ugraeni tipovi:
string s1 = 1.ToString();
string s2 = true.ToString();
// s1 je 1
// s2 je True
Strukture
Struktura je slina klasi, uz sledee kljune razlike:
Struktura je vrednosni tip, dok je klasa referentni.
Struktura ne podrava nasleivanje (osim implicitnog izvoenja iz tipa object, tj., preciznije, iz tipa System.Value).
Struktura moe da ima sve lanove kao i klasa, osim besparametarskog konstruktora, finalizatora i virtuelnih lanova.
Struktura se koristi umesto klase kada je poeljna semantika vrednosnog tipa. Dobri primeri su numeriki tipovi, gde je prirodnije da operacija dodeljivanja kopira vrednost nego referencu. Poto je struktura vrednosni tip, nije potrebno instanciranje objekta na hipu za svaku
instancu; to moe da bude korisna uteda pri izradi velikog broja instanci tipa. Recimo, za izradu niza vrednosnog tipa potrebna je samo
jedna operacija dodele na hipu.
Modifikatori pristupa
Radi podsticanja kapsulacije, moe se ograniiti dostupnost tipa ili
lana tipa drugim tipovima i sklopovima tako to e se deklaraciji dodati jedan od pet modifikatora pristupa:
public
Dostupan samo unutar sadranog sklopa ili prijateljskih sklopova. Podrazumevana dostupnost za neugneene tipove.
private
Dostupan samo unutar sadranog tipa. Podrazumevana dostupnost za lanove klase ili strukture.
protected
Unija dostupnosti protected i internal (omoguava veu dostupnost od pojedinanih modifikatora protected ili internal, zahvaljujui tome to je lan dostupniji na dva naina)
U sledeem primeru, klasi Class2 moe se pristupiti izvan njenog
sklopa, a klasi Class1 ne moe:
class Class1 {}
public class Class2 {}
Modifikatori pristupa | 87
Prijateljski sklopovi
U naprednim scenarijima, internal lanove moete izloiti drugim
prijateljskim sklopovima tako to ete dodati atribut sklopa System.
Runtime.CompilerServices.InternalsVisibleTo, zadajui ime prijateljskog sklopa na sledei nain:
[assembly: InternalsVisibleTo (Friend)]
Ako je prijateljski sklop potpisan jakim imenom, morate zadati njegov pun 160-bajtni javni klju. Taj klju moete dobiti pomou LINQ
upita interaktivan primer je dat u LINQPadovoj besplatnoj biblioteci primera za knjigu C# 5.0 za programere (C# 5.0 in a Nutshell).
Nadjaavanje dostupnosti
Tip nadjaava dostupnost svojih deklarisanih lanova. Najei primer nadjaavanja je kada imate internal tip sa public lanovima. Na
primer:
class C { public void Foo() {} }
Interfejsi
Interfejs je slian klasi, ali sadri samo specifikaciju a ne i implementaciju svojih lanova. Evo po emu je interfejs poseban:
Svi lanovi interfejsa su implicitno apstraktni. Sutrotno tome,
klasa moe da obezbedi implementaciju i apstraktnih i konkretnih lanova.
Klasa (ili struktura) moe da implementira vie interfejsa. Suprotno tome, klasa moe da nasleuje samo jednu klasu, a
struktura ne moe da nasleuje nita (ali moe da se izvodi iz
System.ValueType).
Deklaracija interfejsa ista je kao deklaracija klase, ali ne sadri implementaciju svojih lanova poto su svi oni implicitno apstraktni. Te lanove e implementirati klase i strukture koje implementiraju interfejs.
Interfejs moe da sadri samo metode, svojstva, dogaaje i indeksere,
koji su ne sluajno ba lanovi klase koja moe da bude apstraktna.
Evo malo pojednostavljene verzije interfejsa IEnumerator, definisanog
u System.Collections:
public interface IEnumerator
{
bool MoveNext();
object Current { get; }
}
lanovi interfejsa su uvek implicitno javni i ne mogu deklarisati modifikator pristupa. Implementiranje interfejsa znai obezbeivanje
public implementacije za sve njegove lanove:
internal class Countdown : IEnumerator
{
int count = 11;
public bool MoveNext() { return count-- > 0 ; }
public object Current
{ get { return count; } }
}
// 109876543210
Proirivanje interfejsa
Interfejsi se mogu izvesti iz drugih interfejsa. Na primer:
public interface IUndoable
public interface IRedoable : IUndoable
{ void Undo(); }
{ void Redo(); }
Interfejsi | 89
Poto i I1 i I2 imaju sukobljene potpise za Foo, Widget eksplicitno implementira metodu Foo lana I2. To omoguava da te dve metode postoje u istoj klasi. Jedini nain da pozovete eksplicitno implementiran
lan jeste da ga konvertujete u njegov interfejs:
Widget w = new Widget();
w.Foo();
// Klasa Widget implementira I1.Foo
((I1)w).Foo();
// Klasa Widget implementira I1.Foo
((I2)w).Foo();
// Klasa Widget implementira I2.Foo
Interfejsi | 91
U ovom sluaju, metoda Undo je implementirana eksplicitno. Implicitno implementirani lanovi takoe se mogu reimplementirati, ali efekat nije potpun poto osnovna klasa pokree osnovnu implementaciju.
Nabrajanja
enum je specijalan vrednosni tip (nabrojivi tip) koji omoguava da za-
// true
Svaki lan nabrajanja ima pridruenu celobrojnu vrednost. Te vrednosti su podrazumevano tipa int, a lanovima nabrajanja su dodeljene
konstante 0, 1, 2... (po redosledu deklarisanja lanova). Alternativan
celobrojni tip moete zadati na sledei nain:
public enum BorderSide : byte { Left,Right,Top,Bottom }
Konverzije nabrajanja
Instancu nabrojivog tipa moete eksplicitno konvertovati u pridruenu celobrojnu vrednost i iz nje:
int i = (int) BorderSide.Left;
BorderSide side = (BorderSide) i;
bool leftOrRight = (int) side <= 2;
Nabrajanja | 93
// Includes Left
// Left, Right
BorderSides s = BorderSides.Left;
s |= BorderSides.Right;
Console.WriteLine (s == leftRight);
// True
Na nabrojive tipove koji se mogu kombinovati treba primeniti atribut Flags; ako to ne uradite, pozivanjem metode ToString za instancu
enum dobija se broj umesto niza imena.
Pogodnosti radi, kombinovane lanove moete ukljuiti u deklaraciju samog nabrajanja:
[Flags] public enum BorderSides
{
None=0,
Left=1, Right=2, Top=4, Bottom=8,
LeftRight = Left | Right,
TopBottom = Top | Bottom,
All
= LeftRight | TopBottom
}
Operatori za nabrajanja
S nabrojivim tipovima koriste se sledei operatori:
= == != < > <= >= + -^
+= -= ++ -sizeof
&
| ~
Ugneeni tipovi
Ugneeni tip (engl. nested type) deklarie se unutar opsega nekog
drugog tipa. Na primer:
94 | C# 5.0 Mali referentni prirunik
Generike komponente
C# ima dva zasebna mehanizma za pisanje koda koji se moe viekratno koristiti s razliitim tipovima: nasleivanje i generike kom
ponente. Dok nasleivanje izraava mogunost ponovne upotrebe
pomou osnovnog tipa, generiki mehanizam je izraava pomou
ablona koji sadri uzorke (engl. placeholders). Generiki mehanizam, u poreenju s nasleivanjem, moe da povea bezbednost tipo
va i smanji konvertovanje i pakovanje.
Generike komponente | 95
Generiki tipovi
Generiki tip deklarie parametre tipa zamenske tipove koje korisnik generikog tipa zamenjuje stvarnim tipovima tako to zadaje ar
gumente tipa. Evo generikog tipa, Stack<T>, koji skladiti instance
tipa T. Stack<T> deklarie samo jedan parametar tipa, T:
public class Stack<T>
{
int position;
T[] data = new T[100];
public void Push (T obj) { data[position++] = obj; }
public T Pop()
{ return data[--position]; }
}
N apomena
Obratite panju na to da u poslednja dva reda nije potrebno konvertovanje nanie, ime se izbegava greka pri izvravanju i eliminie potreba za pakovanjem/raspakivanjem. Zahvaljujui tome,
na generiki stek je bolji od negenerikog steka koji koristi tip
object umesto T (videti primer u odeljku Tip object na strani 81).
Generike metode
Generika metoda deklarie parametre tipa u okviru potpisa metode.
Pomou generikih metoda, mnogi algoritmi se mogu implementirati
kao opti, bez odreenih tipova. Evo generike metode koja meusobno
zamenjuje sadraje dve promenljive generikog (opteg) tipa T:
static void Swap<T> (ref T a, ref T b)
{
T temp = a; a = b; b = temp;
}
Generike komponente | 97
Slino tome, konstruktori mogu da uestvuju u postojeim parametrima tipa, ali ne mogu da ih uvedu.
Za instanciranje:
var myDic = new Dictionary<int,string>();
N apomena
Po konvenciji, generiki tipovi i metode sa samo jednim parametrom tipa, daju svom parametru ime T, pod uslovom da je namena tog parametra jasna. Kada ima vie parametara tipa, svaki parametar ima opisnije ime (s prefiksom T).
Generike komponente | 99
T
T
T
T
T
:
:
:
:
:
osnovna_klasa
interfejs
class
struct
new()
where U : T
//
//
//
//
//
//
//
Ogranienje
Ogranienje
Ogranienje
Ogranienje
Ogranienje
konstruktor
Ogranienje
na
na
na
na
na
osnovnu klasu
interfejs
referentni tip
vrednosni tip
besparametarski
na generiki tip
Ili, potklasa moe da zatvori parametre generikog tipa pomou konkretnog tipa:
class IntStack : Stack<int> {...}
I ovo je ispravno:
class Foo<T> where T : IComparable<T> { ... }
class Bar<T> where T : Bar<T> { ... }
Statiki podaci
Statiki podaci su jedinstveni za svaki zatvoren tip:
class Bob<T> { public static int Count; }...
Console.WriteLine (++Bob<int>.Count);
//
Console.WriteLine (++Bob<int>.Count);
//
Console.WriteLine (++Bob<string>.Count);
//
Console.WriteLine (++Bob<object>.Count);
//
1
2
1
1
Kovarijansa
Pod pretpostavkom da se A moe konvertovati u B, X je kovarijantno
ako se X<A> moe konvertovati u X<B>.
N apomena
Kovarijansa i kontravarijansa su napredni koncepti. Motiv za njihovo uvoenje u C# bio je omoguavanje da generiki interfejsi i druge generike komponente (naroito one definisane u Frameworku, kao to je IEnumerable<T>) rade vie u skladu s oeki
vanjima. To vam moe koristiti ak i ako ne razumete detalje koji
stoje iza kovarijanse i kontravarijanse.
(U jeziku C# i njegovom poimanju varijanse, moe se konvertovati znai moe se konvertovati pomou implicitne konverzije reference
npr. A je potklasa klase B, ili A implementira B. To ne vai za numerike konverzije, konverzije pri pakovanju (engl. boxing conversions) i
namenske konverzije.)
Na primer, tip IFoo<T> je kovarijantan za T ukoliko je sledei kd
ispravan:
IFoo<string> s = ...;
IFoo<object> b = s;
Od verzije C# 4.0, generiki interfejsi dozvoljavaju kovarijansu za parametre tipa oznaene modifikatorom out (poput generikih delegata).
N apomena
Interfejsi IEnumerator<T> i IEnumerable<T> (videti Nabrajanja
i iteratori na strani 130) oznaeni su kao kovarijantni od verzije Framework 4.0. To vam omoguava da konvertujete IEnumerable<string> u IEnumerable<object>, na primer.
Kompajler e generisati greku ako kovarijantni parametar tipa upotrebite na ulaznoj poziciji (npr., kao parametar metode ili upisivo svojstvo). Namena ovog ogranienja je garantovanje bezbednosti tipa u
vreme prevoenja. Na primer, ono nas spreava da interfejsu dodamo
metodu Push(T) koju bi korisnici mogli da zloupotrebe pomou naizgled bezazlene operacije dodeljivanja objekta tipa Camel kao vrednost objekta IPoppable<Animal> (ne zaboravite da je u naem primeru pridruen tip stek objekata tipa Bear). Da bismo definisali metodu
Push(T), T mora biti kontravarijantan.
Generike komponente | 103
N apomena
C# podrava kovarijansu (i kontravarijansu) samo za elemente
s konverzijama referenci ne i konverzijama pri pakovanju. Prema tome, ako ste napisali metodu koja prihvata parametar tipa
IPoppable<object>, moete da je pozovete sa IPoppable<string>,
ali ne i sa IPoppable<int>.
Kontravarijansa
Prethodno smo videli sledee: pod uslovom da A dozvoljava implicitnu konverziju reference u B, tip X je kovarijansa ako X<A> dozvoljava konverziju reference u X<B>. Tip je kontravarijantan kada moete da obavite konverziju u suprotnom smeru iz X<B> u X<A>. Ovo je podrano za
interfejse i delegate kada se parametar tipa pojavljuje samo na ulaznim
pozicijama, koje su oznaene modifikatorom in. Proirujui prethodni
primer, ako klasa Stack<T> implementira sledei interfejs:
public interface IPushable<in T> { void Push (T obj); }
Preslikavajui kovarjansu, kompajler e prijaviti greku ako pokuate da upotrebite kontravarijantni parametar tipa na izlaznoj poziciji
(npr., kao povratnu vrednost, ili u itljivom svojstvu).
Delegati
Delegat povezuje pozivajuu metodu s pozvanom metodom u vreme izvravanja. Postoje dva aspekta delegata: tip i instanca. Tip delegata definie protokol kome se pokoravaju pozivalac i pozvani, koji obuhvata
listu parametara tipa i povratni tip. Instanca delegata je objekat koji referencira jednu (ili vie) pozvanih metoda usklaenih s tim protokolom.
Instanca delegata bukvalno slui pozivaocu kao delegat: pozivalac poziva delegata, a zatim taj delegat poziva odredinu metodu. To pre
usmeravanje razdvaja pozivaoca od odredine metode.
Deklaraciji tipa delegata prethodi rezervisana re delegate, ali je inae
ona slina deklaraciji (apstraktne) metode. Na primer:
delegate int Transformer (int x);
Pokretanje delegata je kao pokretanje metode (poto je namena delegata samo da obezbedi peusmeravanje):
t(3);
// 1 4 9
Vieznani delegati
Sve instance delegata karakterie vieznanost (engl. multicast). To
znai da jedna instanca delegata moe da referencira ne samo jednu
odredinu metodu ve i listu takvih metoda. Operatori + i += kombinuju instance delegata. Na primer:
SomeDelegate d = SomeMethod1;
d += SomeMethod2;
Kada se pozove d, time se sada pozivaju i metoda SomeMethod1 i metoda SomeMethod2. Delegati se pozivaju redosledom kojim su dodati.
Operatori - i -= uklanjaju desni operand delegata iz levog. Na primer:
d -= SomeMethod1;
N apomena
Delegati su nepromenljivi, pa kada pozovete += ili -=, vi u stvari pravite novu instancu delegata i dodeljujete je postojeoj
promenljivoj.
Delegati | 107
();
<in T> (T arg);
<in T1, in T2> (T1 arg1, T2 arg2);
T16
Ovi delegati su krajnje opti. Delegat Transformer iz naeg prethodnog primera moe se zameniti delegatom Func koji prima jedan argument tipa T i vraa vrednost istog tipa:
public static void Transform<T> (
T[] values, Func<T,T> transformer)
{
Kompatibilnost delegata
Svi delegatski tipovi meusobno su nekompatibilni, ak i ako su im
potpisi isti:
delegate void D1(); delegate void D2();
...
D1 d1 = Method1;
D2 d2 = d1;
// Greka pri prevoenju
Delegati | 109
Varijansa parametra
Kada pozovete metodu, moete joj proslediti argumente iji su tipovi specifiniji od parametara te metode. To je uobiajeno polimorfno
ponaanje. U skladu s tim, odredina metoda delegata moe imati manje specifine tipove parametara od onih koje opisuje delegat. To se
zove kontravarijansa:
delegate void StringAction (string s);
...
static void Main()
{
StringAction sa = new StringAction (ActOnObject);
sa (hello);
}
static void ActOnObject (object o)
{
Console.WriteLine (o); // hello
}
N apomena
Standardan ablon dogaaja zamiljen je tako da vam omogui da upotrebom osnovne klase EventArgs iskoristite kontravarijansu parametara delegata. Na primer, jednu metodu mogu pozvati dva razliita delegata, od kojih joj jedan prosleuje argumente tipa MouseEventArgs a drugi tipa KeyEventArgs.
Parametar tipa koji se koristi samo za povratnu vrednost oznaiti kao kovarijantni (out).
Svaki parametar tipa koji se koristi samo za ulazne parametre
oznaiti kao kontravarijantni (in).
Tako omoguavate prirodno obavljanje konverzija uz potovanje naslednih veza izmeu tipova. Sledei delegat (definisan u imenskom
prostoru System) kovarijantan je za TResult:
delegate TResult Func<out TResult>();
to omoguava da se napie:
Func<string> x = ...;
Func<object> y = x;
to omoguava da se napie:
Action<object> x = ...;
Action<string> y = x;
Dogaaji
Pri korienju delegata obino se javljaju dve vane uloge: emiter ili
izvor (engl. broadcaster) i pretplatnik (engl. subscriber). Emiter je tip
koji sadri polje tipa delegat. Emiter odluuje kada e emitovati, tako
to poziva delegata. Pretplatnici su primaoci ije metode obrauju dogaaje. Pretplatnik odluuje kada da zapone i prekine oslukivanje,
tako to primenjuje operatore += i -= na delegata datog emitera. Jedan
pretplatnik ne zna za druge pretplatnike, niti se mea u njihov rad.
Dogaaji su komponenta programskog jezika pomou koje se formalizuje ovakav nain rada. event je konstrukt koji eksponira samo onaj
podskup osobina delegata koji je potreban modelu emiter/pretplatnik. Glavna namena dogaaja je da spree pretplatnike da ometaju jed
ni druge.
Dogaaji | 111
Ako iz naeg primera uklonimo rezervisanu re event tako da PriceChanged postane obino polje tipa delegat, dobiemo isti rezultat. Meutim, klasa Stock nee vie biti tako robusna jer e pretplatnici moi
da urade sledee kako bi uticali jedan na drugog:
da zamene druge pretplatnike tako to e dodeliti drugu metodu delegatu PriceChanged (umesto da koriste operator +=).
da obriu sve pretplatnike klase (tako to e delegat PriceChanged postaviti na vrednost null).
da emituju ka drugim pretplatnicima pokretanjem delegata.
Dogaaji mogu biti virtuelni, redefinisani, apstraktni ili zatvoreni.
Mogu takoe biti i statiki.
Dogaaji | 113
U sreditu standardnog ablona za dogaaje nalazi se System.EventArgs: unapred definisana Framework klasa bez lanova (izuzev statikog
svojstva Empty). EventArgs je osnovna klasa za prenos informacija dogaaju. U ovom primeru, pravimo potklasu EventArgs da bismo preneli
staru i novu cenu kada se pokrene dogaaj PriceChanged.
Generiki delegat System.EventHandler
meworka i definisan je ovako:
N apomena
Pre verzije C# 2.0 (kada su jeziku dodate generike komponente) reenje je bilo da se napie namenski delegat za obradu dogaaja za svaki EventArgs tip, na sledei nain:
Iz istorijskih razloga, veina dogaaja unutar Frameworka koristi ovako definisane delegate.
Za dogaaje koji ne nose dodatne informacije, Framework obezbeuje negeneriki delegat EventHandler. Da bismo to pokazali, ponovo
emo napisati klasu Stock tako da se dogaaj PriceChanged pokree
nakon promene cene. To znai da nikakve dodatne informacije ne treba preneti s dogaajem:
public class Stock
{
string symbol; decimal price;
Dogaaji | 115
EventHandler _priceChanged;
// Privatni delegat
public event EventHandler PriceChanged
{
add { _priceChanged += value; }
remove { _priceChanged -= value; }
}
Ovaj primer je funkcionalno identian podrazumevanoj implementaciji metoda za pristupanje dogaajima u jeziku C# (osim to se C# stara
za bezbedne vienitne operacije pri auriranju delegata). Time to sami
definiemo metode za pristupanje dogaaju, nalaemo jeziku C# da ne
generie podrazumevanu logiku polja i metodu za pristupanje.
Uz eksplicitno definisane metode za pristupanje dogaajima, moete primeniti sloenije strategije za skladitenje pripadajueg delegata i pristup
njemu. To je korisno kada su metode za pristup dogaaju samo releji za
drugu klasu koja emituje taj dogaaj, ili kada se eksplicitno implementira interfejs koji deklarie dogaaj:
public interface IFoo { event EventHandler Ev; }
class Foo : IFoo
{
EventHandler ev;
event EventHandler IFoo.Ev
{
add { ev += value; } remove { ev -= value; }
}
}
Lambda izrazi
Lambda izraz je bezimena metoda napisana umesto instance delegata. Kompajler odmah konvertuje lambda izraz u jedno od sledeeg:
instancu delegata ili
stablo izraza, tipa Expression<TDelegate>, koje predstavlja kd
unutar lambda izraza u sekvencijalnom objektnom modelu.
To omoguava da se lambda izraz interpretira kasnije u vreme
izvravanja (taj proces opisujemo u poglavlju 8 knjige C# 5.0
za programere).
Lambda izrazi | 117
N apomena
Interno, kompajler pretvara lambda izraze ovog tipa tako to pie
privatnu metodu i u nju premeta kd datog izraza.
Kompajler obino moe iz konteksta da zakljui kog su tipa lambda parametri. Kada to nije sluaj, kompajleru moete navesti tipove
parametara:
118 | C# 5.0 Mali referentni prirunik
// 30
Console.WriteLine (natural());
Console.WriteLine (seed);
// 1
// 2
ivotni vek preuzetih promenljivih produava se na ivotni vek datog delegata. U narednom primeru, seme lokalne promenljive (seed)
obino bi nestalo iz opsega im se izvri delegat Natural. Meutim,
poto je promenljiva seed preuzeta, ivotni vek joj se produava do
kraja ivotnog veka delegata natural:
static Func<int> Natural()
{
int seed = 0;
return () => seed++;
// Vraa zatvarajui izraz
}
static void Main()
{
Func<int> natural = Natural();
Console.WriteLine (natural());
// 0
Console.WriteLine (natural());
// 1
}
Svaki zatvarajui izraz (prikazan podebljano) preuzima istu promenljivu, i. (To u stvari ima smisla kada imate u vidu da je i promenljiva ija vrednost ostaje ista izmeu iteracija petlje; ako elite, moete
ak eksplicitno izmeniti i u telu petlje.) Posledica je sledea: kada se
kasnije pokrenu delegati, svaki od njih vidi vrednost i u vreme pokre
tanja a to je 3. Ukoliko elimo da program ispie 012, reenje je da
iteracionu promenljivu dodelimo lokalnoj promenljivoj koja se prati unutar petlje:
120 | C# 5.0 Mali referentni prirunik
U pozorenje
Petlje foreach su ranije funkcionisale na isti nain, ali su se od
tada promenila pravila. Poevi od verzije C# 5.0, moete u lambda izrazu bezbedno zatvoriti iteracionu promenljivu petlje
foreach a da vam ne treba neka privremena promenljiva.
Anonimne metode
Anonimne metode su mogunost iz verzije C# 2.0 koju sada zamenjuju lambda izrazi. Anonimna metoda je kao lambda izraz, a razlikuje se po sledeem: tip njenih parametara se ne odreuje implicitno,
sintaksi izraza (anonimna metoda mora uvek da bude blok naredaba)
i sposobnosti da se prevede u stablo izraza.
Da biste napisali anonimnu metodu, dodajte rezervisanu re delegate praenu (opciono) deklaracijom parametra iza koje sledi telo metode. Na primer, ako je dat ovaj delegat:
delegate int Transformer (int i);
Ili samo:
Transformer sqr =
x => x * x;
Jedinstvena karakteristika anonimnih metoda jeste to to moete potpuno izostaviti deklaraciju parametara ak i ako je delegat oekuje.
To moe biti korisno pri deklarisanju dogaaja s podrazumevanom,
praznom procedurom za obradu dogaaja:
public event EventHandler Clicked = delegate { };
N apomena
Ovo je jednostavan primer koji ilustruje obradu izuzetaka. U
praksi se sa ovim konkretnim sluajem moemo bolje izboriti
tako to emo eksplicitno proveriti da li je delilac nula pre nego
to pozovemo Calc.
Obrada izuzetaka je relativno skupa jer su potrebne stotine ciklusa radnog takta.
Odredba catch
Odredba catch specificira koju vrstu izuzetka treba obraditi. To mora
biti ili System.Exception ili neka njegova potklasa. Obradom izuzetaka System.Exception obrauju se sve mogue greke. To je korisno u
sledeim sluajevima:
Kada se program moda moe oporaviti, bez obzira na konkretnu vrstu izuzetka.
Kada planirate da ponovo generiete izuzetak (moda nakon
to ste ga evidentirali).
Pri obradi izuzetka, izvrava se samo jedna odredba catch. Ako elite da u kd uvrstite i sigurnosnu mreu kako biste presretali i optije
izuzetke (kao to je System.Exception) morate prvo navesti procedure
za obradu specifinijih izuzetaka.
Izuzetak se moe presresti i bez zadavanja promenljive, ukoliko ne treba da pristupate njenim svojstvima:
catch (StackOverflowException)
{ ... }
// bez promenljive
tavie, moete izostaviti i promenljivu i tip (to znai da e biti obraeni svi izuzeci):
catch { ... }
Blok finally
Blok finally se uvek izvrava bez obzira na to da li je izuzetak generisan i da li se blok try izvrava do kraja. Blokovi finally se obino koriste za kd za ienje.
U ovom primeru zatvorili smo datoteku tako to smo pozvali metodu Dispose objekta StreamReader. Pozivanje metode Dispose objekta
unutar bloka finally, standardno je pravilo u celom .NET Frameworku i C# ga eksplicitno podrava pomou naredbe using.
Naredba using
Mnoge klase kapsuliraju neupravljane resurse, kao to su identifikatori datoteka (engl. file handles), identifikatori grafikih objekata (engl.
graphics handles), ili veze s bazama podataka. Te klase i mplementiraju
126 | C# 5.0 Mali referentni prirunik
interfejs System.IDisposable, koji definie samo jednu, besparametarsku metodu po imenu Dispose za brisanje tih resursa. Naredba using
obezbeuje elegantnu sintaksu za pozivanje metode Dispose za IDisposable objekat unutar bloka finally.
Sledei kd:
using (StreamReader reader = File.OpenText (file.txt))
{
...
}
Generisanje izuzetaka
Izuzetke moe generisati ili izvrno okruenje ili korisniki kd. Ovde
metoda Display generie izuzetak System.ArgumentNullException:
static void Display (string name)
{
if (name == null)
throw new ArgumentNullException (name);
Console.WriteLine (name);
}
N apomena
Kada bismo zamenili throw sa throw ex, primer bi i dalje funkcionisao, ali svojstvo StackTrace vie ne bi odraavalo prvobitnu greku.
Pri ponovnom generisanju drugaijeg izuzetka, u svojstvo InnerException moete upisati prvobitni izuzetak da bi se olakalo pronalaenje i otklanjanje greaka. Gotovo sve vrste izuzetaka imaju konstruktor za tu namenu (kao u naem primeru).
Znakovni niz koji predstavlja sve metode koje se pozivaju od mesta nastanka greke do bloka catch.
Message
(obino numeriki) argument prevelik ili premali. Na primer, generie se kada se negativan broj prosledi funkciji koja prihvata
samo pozitivne vrednosti.
System.InvalidOperationException
System.NotSupportedException
N apomena
Potreba za izuzetkom ArgumentException (i njegovim potklasama) eliminisana je zahvaljujui tzv. kodnim ugovorima (engl.
code contracts), to je opisano u poglavlju 13 knjige C# 5.0 za
programere.
Nabrajanje i iteratori
Nabrajanje
Enumerator je kursor za sekvencu vrednosti koje se mogu samo itati od poetka ka kraju sekvence; to je objekat koji implementira interfejs System.Collections.IEnumerator ili System.Collections.Generic.IEnumerator<T>.
Naredba foreach obavlja iteraciju nad nabrojivim (engl. enumerable)
objektom. Nabrojiv objekat je logiki oblik predstavljanja sekvence.
On sm nije kursor, ve objekat koji formira kursor za svoje vrednosti.
Nabrojiv objekat implementira interfejs IEnumerable/IEnumerable<T>
ili ima metodu GetEnumerator koja vraa enumerator.
Sintaksa nabrajanja izgleda ovako:
class Enumerator
// Obino implementira IEnumerator<T>
{
public IteratorVariableType Current { get {...} }
Evo kako se na visokom nivou iterira kroz znakove u rei beer pomou naredbe foreach:
foreach (char c in beer) Console.WriteLine (c);
Evo kako se na niskom nivou iterira kroz znakove u rei beer bez korienja naredbe foreach:
using (var enumerator = beer.GetEnumerator())
while (enumerator.MoveNext())
{
var element = enumerator.Current;
Console.WriteLine (element);
}
Ako enumerator implementira interfejs IDisposable, naredba foreach slui i kao naredba using, implicitno uklanjajui nabrojivi objekat.
Inicijalizatori kolekcija
Nabrojiv objekat moete instancirati i popuniti u jednom koraku. Na
primer:
using System.Collections.Generic;
...
List<int> list = new List<int> {1, 2, 3};
Iteratori
Dok je naredba foreach korisnik enumeratora, iterator je proizvoa
enumeratora. U ovom primeru koristimo iterator da bismo dobili niz
Fibonaijevih brojeva (u kome je svaki broj zbir prethodna dva):
using System;
using System.Collections.Generic;
class Test
{
static void Main()
{
foreach (int fib in Fibs(6))
Console.Write (fib + );
}
static IEnumerable<int> Fibs(int fibCount)
{
for (int i = 0, prevFib = 1, curFib = 1; i < fibCount; i++)
{
yield return prevFib;
int newFib = prevFib+curFib;
prevFib = curFib;
curFib = newFib;
}
}
}
REZULTAT: 1
Dok naredba return saoptava: Evo vrednosti koju ste traili da vratim iz ove metode, naredba yield return saoptava: Evo sledeeg
elementa koji ste traili da uitam iz ovog nabrajanja. Posle svake naredbe yield, kontrola se vraa pozivaocu, ali se stanje pozvanog odrava, tako da metoda moe nastaviti da se izvrava im pozivalac pree na sledei element. ivotni vek ovog stanja vezan je za enumerator,
i ono se moe osloboditi kada pozivalac zavri nabrajanje.
N apomena
Kompajler konvertuje metode iteratora u privatne klase koje implementiraju interfejs IEnumerable<T> i/ili IEnumerator<T>. Logika unutar bloka iteratora je obrnuta i raspodeljena izmeu
metode MoveNext i svojstva Current klase enumeratora koju generie kompajler i koja postaje maina s konanim brojem stanja. To znai sledee: kada pozovete metodu iteratora, samo instancirate klasu koju je generisao kompajler; nikakav va kd
se ne izvrava! Va kd se izvrava samo kada ponete nabrajanje nad rezultujuom sekvencom vrednosti, obino pomou naredbe foreach.
Semantika iteratora
Iterator je metoda, svojstvo ili indekser koji sadri jednu ili vie naredaba
yield. Iterator mora da vrati jedan od sledea etiri interfejsa (u suprotnom, kompajler e generisati greku):
System.Collections.IEnumerable
System.Collections.IEnumerator
System.Collections.Generic.IEnumerable<T>
System.Collections.Generic.IEnumerator<T>
U pozorenje
U bloku iteratora nije dozvoljena naredba return morate koristiti naredbu yield break.
Formiranje sekvenci
Iteratori su veoma fleksibilni. Primer s Fibonaijevim nizom proiriemo tako to emo klasi dodati sledeu metodu:
static IEnumerable<int> EvenNumbersOnly (
IEnumerable<int> sequence)
{
foreach (int x in sequence)
if ((x % 2) == 0)
yield return x;
}
}
izvravanje
2
sledei
Potroa
sledei
3
sledei
5
sledei
8
sledei
sledei
1
sledei
1
sledei
2
8
uitavanje podataka
yield podataka
string s = null;
.int i = null;
// OK - referentni tip
// Greka pri prevoenju - int ne moe da
// bude null.
Struktura Nullable<T>
T? se prevodi u System.Nullable<T>. Nullable<T> je lagana, nepromenljiva struktura sa samo dva polja koja predstavljaju Value i HasValue. Sutina strukture System.Nullable<T> veoma je jednostavna:
public struct Nullable<T> where T : struct
{
public T Value {get;}
public bool HasValue {get;}
public T GetValueOrDefault();
public T GetValueOrDefault (T defaultValue);
...
}
Kd:
int? i = null;
Console.WriteLine (i == null);
// True
prevodi se u:
Nullable<int> i = new Nullable<int>();
Console.WriteLine (! i.HasValue);
// True
Pri pokuaju da se uita Value kada je HasValue false, generie se InvalidOperationException. Metoda GetValueOrDefault() vraa Value
ako je HasValue true; u suprotnom, vraa new T() ili zadatu podrazumevanu vrednost.
Podrazumevana vrednost T? je null.
136 | C# 5.0 Mali referentni prirunik
// implicitna
// eksplicitna
Eksplicitna konverzija je direktno ekvivalentna pozivanju svojstva Value datog objekta koji prihvata null. Znai, generie se InvalidOperationException ukoliko je HasValue false.
// false
Kraa operatora
Struktura Nullable<T> ne definie operatore kao to su <, >, pa ak ni
==. Uprkos tome, sledei kd se prevodi i izvrava ispravno:
int? x = 5;
int? y = 10;
bool b = x < y;
// true
Ovo funkcionie zahvaljujui tome to kompajler pozajmljuje, tj.krade operator manje od od pripadajueg vrednosnog tipa. Semantiki, on prevodi prethodni poredbeni izraz u sledei:
bool b = (x.HasValue && y.HasValue)
? (x.Value < y.Value)
: false;
Drugim reima, ako i x i y imaju vrednosti, one se porede preko operatora manje od promenljive tipa int; u suprotnom, rezultat je false.
Kraa operatora znai da moete implicitno primenjivati T-ove operatore na T?. Moete definisati operatore za T? da biste obezbedili posebno ponaanje za null, ali je u velikoj veini sluajeva najbolje da se oslonite na to da e kompajler automatski primeniti logiku
za null umesto vas.
Kompajler primenjuje razliitu logiku za null, zavisno od kategorije operatora.
// true
// true
Dalje:
Ako jedan operand ima vrednost null, operandi nisu jednaki.
Ako su oba operanda razliita od null, porede se njihove vrednosti (Value).
int? c = x + y; // Prevod:
int? c = (x == null || y == null)
? null
: (int?) (x.Value + y.Value);
// c je null (pod uslovom da x ima vrednost 5 a y null)
Izuzetak je kada se operatori & i | primene na bool?, o emu e uskoro biti rei.
= false,
(n | n);
(n | f);
(n | t);
(n & n);
(n & f);
(n & t);
t = true;
// (null)
// (null)
// True
// (null)
// False
// (null)
// y je 5
int? a = null, b = 1, c = 2;
Console.Write (a ?? b ?? c); // 1
// (prva vrednost koja nije null)
Operator ?? ekvivalentan je pozivanju funkcije/metode GetValueOrDefault sa eksplicitno zadatom podrazumevanom vrednou, osim
to se izraz prosleen funkciji GetValueOrDefault nikada ne izraunava ukoliko promenljiva nije null.
Preklapanje operatora
Operatori se mogu preklapati (engl. overload) da bi se dobila prirodnija sintaksa za namenske tipove. Preklapanje operatora je najsvrsi
shodnije kada se koristi za implementiranje namenskih struktura
koje predstavljaju relativno proste tipove podataka. Na primer, namenski numeriki tip je odlian kandidat za preklapanje operatora.
Sledei simboliki operatori mogu se preklopiti:
+
++
--
&
==
!=
<
<<
>>
>
Operatorske funkcije
Operator se preklapa tako to se deklarie operatorska funkcija. Operatorska funkcija mora da bude statika, i bar jedan operand mora da
bude onog tipa u kojem je operatorska funkcija deklarisana.
U narednom primeru definiemo strukturu po imenu Note, koja predstavlja muziku notu, a zatim preklapamo operator +:
public struct Note
{
int value;
public Note (int semitonesFromA)
{ value = semitonesFromA; }
public static Note operator + (Note x, int semitones)
{
return new Note (x.value + semitones);
}
}
// eksplicitna konverzija
// implicitna konverzija
N apomena
Ovaj primer je pomalo nategnut: u stvarnoj situaciji, navedene
konverzije bi se moda bolje implementirale pomou metode
ToFrequency i (statike) metode FromFrequency.
Proirene metode
Proirene metode (engl. extension methods) omoguavaju da se postojei tip proiri novim metodama a da se ne menja definicija originalnog tipa. Proirena metoda je statika metoda statike klase, gde je
modifikator this primenjen na prvi parametar. Tip prvog parametra
bie onaj koji je proiren. Na primer:
Proirene metode | 143
Kada se poziv proirene metode kompajlira, prevodi se u poziv obine, statike metode:
Console.Write (StringHelper.IsCapitalized (Perth));
// S
Razjanjavanje dvosmislenosti
U imenskim prostorima
Da bi se moglo pristupiti proirenoj metodi, njen imenski prostor
mora biti vidljiv (obino uvezen pomou naredbe using).
Anonimni tipovi
Anonimni tip je jednostavna klasa napravljena u hodu, za smetaj
odreenog skupa vrednosti. Da biste definisali anonimni tip, zadajte rezervisanu re new praenu inicijalizatorom objekta koji odreuje
svojstva i vrednosti koje e tip sadrati. Na primer:
var dude = new { Name = Bob, Age = 1 };
Kompajler to reava tako to formira privatan ugneen tip sa svojstvima samo za itanje, za Name (tipa string) i Age (tipa int). Za opis
anonimnog tipa morate navesti rezervisanu re var, zato to ime tipa
generie kompajler.
Anonimni tipovi | 145
ekvivalentan je:
var dude = new { Name = Bob, Age = Age };
LINQ
LINQ, tj. Language Integrated Query, omoguava da piete strukturirane upite sa sigurnim tipovima za lokalne kolekcije objekata i udaljene izvore podataka.
LINQ omoguava da izvrite upit nad svakom kolekcijom koja implementira interfejs IEnumerable<>, bez obzira na to da li je re o nizu, listi, XML DOM-u, ili udaljenom izvoru podataka (kao to je tabela na
SQL Serveru). Prednosti LINQ-a su i provera tipova u vreme prevoenja i dinamiko sastavljanje upita.
N apomena
Dobar nain za eksperimentisanje sa LINQ-om jeste preuzimanje LINQPada sa lokacije http://www.linqpad.net. LINQPad
omoguava da interaktivno izvravate LINQ upite nad lokalnim
kolekcijama i SQL bazama podataka, a da ne morate nita da
podeavate; uz to, u LINQ su ugraeni brojni primeri.
Osnove LINQ-a
U LINQ-u, osnovne jedinice podataka su sekvence i elementi. Sekvenca je svaki objekat koji implementira generiki interfejs IEnumerable,
146 | C# 5.0 Mali referentni prirunik
Ovakvu sekvencu zovemo lokalna sekvenca zato to predstavlja lokalnu kolekciju objekata u memoriji.
Operator upita (engl. query operator) jeste metoda koja transformie sekvencu. Tipian operator upita prihvata ulaznu sekvencu i proizvodi
transformisanu izlaznu sekvencu. U klasi Enumerable, u imenskom prostoru System.Linq ima oko 40 operatora upita; svi su implementirani kao
statike proirene metode. Oni se zovu standardni operatori za upite.
N apomena
LINQ podrava i sekvence koje se mogu dinamiki proslediti iz
udaljenog izvora podataka kao to je SQL Server. Te sekvence dodatno implementiraju interfejs IQueryable<> i podrava ih odgovarajui skup standardnih operatora za upite u klasi Queryable.
Jednostavan upit
Upit je izraz koji transformie sekvence pomou jednog ili vie operatora za upite. Najjednostavniji upit se sastoji od jedne ulazne sekvence
i jednog operatora. Na primer, evo kako moemo primeniti operator
Where na jednostavan niz da bismo izdvojili imena dugaka najmanje etiri znaka:
string[] names = { Tom, Dick, Harry };
IEnumerable<string> filteredNames =
System.Linq.Enumerable.Where (
names, n => n.Length >= 4);
foreach (string n in filteredNames)
Console.Write (n + |);
// Dick|Harry|
IEnumerable<string> filteredNames =
names.Where (n => n.Length >= 4);
source je ulazna sekvenca; predicate je delegat koji se izvrava za svaki ulazni element. Metoda Where obuhvata sve elemente u izlaznoj se
kvenci za koje taj delegat vraa true. Interno, on je implementiran pomou iteratora evo njegovog izvornog koda:
foreach (TSource element in source)
if (predicate (element))
yield return element;
Projektovanje
Jo jedan od osnovnih operatora za upite jeste metoda Select. Ona
transformie (projektuje) svaki element iz ulazne sekvence pomou
zadatog lambda izraza:
string[] names = { Tom, Dick, Harry };
IEnumerable<string> upperNames =
names.Select (n => n.ToUpper());
foreach (string n in upperNames)
Console.Write (n + |); // TOM|DICK|HARRY|
Evo rezultata:
{ Name = Tom, Length = 3 }
{ Name = Dick, Length = 4 }
{ Name = Harry, Length = 5 }
//
//
//
//
10
6
8
9
LINQ | 149
Agregatni operatori
Agregatni operatori vraaju skalarnu vrednost obino numerikog
tipa. Najee se koriste agregatni operatori Count, Min, Max i Average:
int[] numbers = { 10, 9, 8, 7, 6 };
int count = numbers.Count();
// 5
int min = numbers.Min();
// 6
int max = numbers.Max();
// 10
double avg = numbers.Average();
// 8
Count prihvata opcioni predikat, koji pokazuje da li uvrstiti dati element. Sledei kd broji sve parne brojeve:
int evenNums = numbers.Count (n => n % 2 == 0); // 3
// 4
Kvantifikatori
Kvantifikatori vraaju logiku vrednost (bool). Kvantifikatori su Contains, Any, All i SequenceEquals (poredi dve sekvence):
int[] numbers = { 10, 9, 8, 7, 6 };
bool
bool
bool
bool
//
//
//
//
true
true
true
false
Operatori za skupove
Operatori za skupove (engl. set operators) prihvataju dve ulazne sekvence istog tipa. Concat nadovezuje jednu sekvencu na drugu; Union
radi isto to, ali uz uklanjanje duplikata:
// { 1, 2, 3, 3, 4, 5 }
// { 1, 2, 3, 4, 5 }
// { 3 }
// { 1, 2 }
// { 4, 5 }
Odloeno izvravanje
Vana osobina mnogih operatora za upite jeste to to se ne izvravaju
onda kad su napravljeni, ve kad su nabrojani (drugim reima, kada
se metoda MoveNext pozove za svoj enumerator). Razmotrite sledei upit:
var numbers = new List<int> { 1 };
numbers.Add (1);
IEnumerable<int> query = numbers.Select (n => n * 10);
numbers.Add (2);
// Kriom ubacuje jo jedan element
foreach (int n in query)
Console.Write (n + |);
// 10|20|
Dodatni broj koji smo ubacili u listu nakon sastavljanja upita obuhvaen je rezultatom, zato to se operacije filtriranja ili sortiranja
obavljaju tek kada se izvri naredba foreach. To se naziva odloeno
(engl. deferred ) ili lenjo (engl. lazy) izvravanje. Odloeno izvravanje razdvaja formiranje upita od njegovog izvravanja, omoguavajui
vam da sastavite upit u nekoliko koraka, kao i da izvrite upit nad bazom podataka a da klijentu ne prikazujete sve redove. Svi standardni
operatori za upite omoguavaju odloeno izvravanje, osim sledeih:
LINQ | 151
Operatori koji vraaju samo jedan element ili skalarnu vrednost (operatori koji vraaju elemente, agregatni operatori i
kvantifikatori)
Sledei operatori za konverzije:
ToArray, ToList, ToDictionary, ToLookup
Operatori za konverzije su zgodni, delimino i zato to spreavaju odloeno izvravanje. To moe biti korisno za zamrzavanje ili keiranje rezultata u odreenom trenutku, da bi se izbeglo ponovno izvravanje raunski zahtevnog upita ili upita iz udaljenog izvora, kao to
je LINQ upit za SQL tabelu. (Pratei efekat odloenog izvravanja jeste to to se upit ponovo izvrava ukoliko ga kasnije renumeriete.)
Sledei primer ilustruje upotrebu operatora ToList:
var numbers = new List<int>() { 1, 2 };
List<int> timesTen = numbers
.Select (n => n * 10)
.ToList();
// Odmah se izvrava u List<int>
numbers.Clear();
Console.WriteLine (timesTen.Count);
// I dalje 2
U pozorenje
Podupiti obezbeuju jo jedan nivo preusmeravanja. U podupitu je sve podlono odloenom izvravanju ukljuujui i metode agregacije i konverzije, zato to se i sm podupit izvrava iskljuivo odloeno, na zahtev. Pod pretpostavkom da je names niz
znakovnih nizova, podupit izgleda ovako:
names.Where (
n => n.Length ==
names.Min (n2 => n2.Length))
Opis
Odloeno
izvravanje?
Filtriranje
Da
Projektovanje
Da
Spajanje
Da
Promena redosleda
Da
Grupisanje
Da
Rad sa skupovima
Da
Elementi
Ne
Agregiranje
Kvantifikacija
Konverzija: uvoenje
Konverzija: izvoenje
Generisanje
Ne
Ne
Da
Ne
Da
LINQ | 153
Opis
Where
Take
Skip
TakeWhile
Preuzima elemente od ulazne sekvence sve dok dati predikat ne postane true
SkipWhile
Distinct
Ignorie elemente iz ulazne sekvence sve dok dati predikat ne postane true,
aonda preuzima ostale
Vraa kolekciju bez duplikata
Opis
Select
SelectMany
Opis
Primenjuje strategiju pretraivanja da bi upario elemente iz dve kolekcije,
dajui kao rezultat ravan skup
GroupJoin
Zip
Opis
Vraa date elemente sortirane po rastuem redosledu
Vraa date elemente sortirane po opadajuem redosledu
Vraa date elemente obrnutim redosledom
Opis
Grupie sekvencu u podsekvence
Opis
Concat
Union
Intersect
Except
Opis
Vraa prvi element sekvence, ili prvi element koji zadovoljava dati
predikat
Vraa poslednji element sekvence, ili poslednji element koji zadovoljava dati predikat
Ekvivalentno metodi First/FirstOrDefault, ali generie
izuzetak ukoliko ima vie od jednog poklapanja
Vraa element koji se nalazi na zadatoj poziciji
Vraa sekvencu s jednom vrednou, ija je vrednost null odnosno default(TSource) ako data sekvenca nema nijedan element
Opis
Vraa ukupan broj elemenata u ulaznoj sekvenci, ili broj elemenata koji
zadovoljavaju dati predikat
Vraa najmanji odnosno najvei element u sekvenci
Izraunava numeriki zbir odnosno prosenu vrednost svih elemenata
u sekvenci
Obavlja namensko agregiranje
LINQ | 155
Opis
Contains
Any
All
SequenceEqual Vraa true ako druga sekvenca ima identine elemente kao ulazna sekvenca
Opis
Konvertuje IEnumerable u IEnumerable<T>, odbacujui elemente pogrenog
tipa
Konvertuje IEnumerable u IEnumerable<T>, generiui izuzetak ukoliko ima
elemenata pogrenog tipa
Opis
Konvertuje IEnumerable<T> u T[]
Konvertuje IEnumerable<T> u List<T>
Konvertuje IEnumerable<T> u Dictionary<TKey,TValue>
Konvertuje IEnumerable<T> u ILookup<TKey,TElement>
Konvertuje nanie u IEnumerable<T>
Eksplicitno konvertuje ili konvertuje u IQueryable<T>
Opis
Pravi praznu sekvencu
Pravi sekvencu sa elementima koji se ponavljaju
Pravi sekvencu celih brojeva
Where, OrderBy i Select su standardni operatori za upite koji se preslikavaju u proirene metode klase Enumerable. Operator Where daje filtriranu verziju ulazne sekvence; OrderBy daje sortiranu verziju svoje
ulazne sekvence; Select daje sekvencu u kojoj se svaki ulazni element
transformie ili projektuje pomou datog lambda izraza (n.ToUpper(), u
ovom sluaju). Podaci teku sleva nadesno kroz lanac operatora, tako da
se prvo filtriraju, zatim sortiraju i, na kraju, projektuju. Krajnji rezultat
podsea na pokretnu proizvodnu traku, kao to je prikazano na slici 6.
n =>
n.Contains ("a")
n =>
n.Length
n =>
n.toUpper()
JAY
MARY
HARRY
Tom
Dick
Harry
Mary
Jay
Filter
Sorter
Projector
.Where()
.OrderBy
.Select
Operatori celim tokom obezbeuju odloeno izvravanje, pa nema nikakvog filtriranja, sortiranja ni projektovanja sve dok se ne uitaju svi
elementi za obradu u upitu.
LINQ | 157
Izrazi upita
Dosad smo pisali upite tako to smo pozivali proirene metode u klasi Enumerable. U ovoj knjizi to opisujemo kao fluentnu sintaksu. C#
nudi i specijalnu jeziku podrku za pisanje upita, a to su izrazi koji
ine upite, tj. izrazi upita (engl. query expressions). Evo prethodnog
upita napisanog u obliku izraza:
IEnumerable<string> query =
from n in names
where n.Contains (a)
orderby n.Length
select n.ToUpper();
Izraz upita uvek poinje odredbom from, a zavrava se odredbom select ili group. Pomou odredbe from deklarie se promenljiva sku
pa, engl. range variable (u ovom sluaju, n), koju moete smatrati promenljivom koja redom preuzima jedan po jedan element iz kolekcije
slino kao foreach. Slika 7 ilustruje kompletnu sintaksu.
N apomena
Ako poznajete sintaksu SQL-a, sintaksa izraza za LINQ upite
sa odredbom from na poetku i odredbom select na kraju
moda e vam delovati udno. U stvari, sintaksa izraza za upite
je loginija jer se navedene odredbe pojavljuju redosledom kojim
se izvravaju. To omoguava da vam Visual Studio pomogne pomou sistema IntelliSense dok kucate, a i pojednostavljuje pravila vidljivosti za podupite.
Kompajler obrauje izraze upita tako to ih prevodi u fluentnu sintaksu. On to radi prilino mehaniki slino kao to prevodi naredbe
foreach u pozive metoda GetEnumerator i MoveNext:
IEnumerable<string> query = names
.Where (n => n.Contains (a))
.OrderBy (n => n.Length)
.Select (n => n.ToUpper());
from
identifikator
ime tipa
in
enumerable-izraz
orderby
ascending
izraz
descending
identifikator
orderbyodredba
where
boolean-izraz
select
izraz
let identifikator
=izraz
groupodredba
fromodredba
joinodredba
join
unutranji
identifikator
in
unutranji
izraz
on
spoljni
klju
equals
into
SelectMany
nastavak
upita
into
identifikator
LINQ | 159
Rezervisana re let
Rezervisana re let uvodi novu promenljivu uz promenljivu skupa.
Na primer, pretpostavimo da elimo da izlistamo sva imena koja su,
ne raunajui samoglasnike, dua od dva znaka:
string[] names = { Tom,Dick,Harry,Mary,Jay };
IEnumerable<string> query =
from n in names
let vowelless = Regex.Replace (n, [aeiou], )
where vowelless.Length > 2
orderby vowelless
select n + - + vowelless;
Odredba let obavlja izraunavanja nad svakim elementom, ne menjajui originalni element. U naem upitu, odredbe koje slede (where, orderby i select) mogu da pristupe i promenljivoj n i promenljivoj vowelless.
Jedan upit moe da sadri proizvoljan broj odredaba let, i one mogu biti
proizvoljno rasporeene s dodatnim odredbama where i join.
Kompajler prevodi rezervisanu re let tako to projektuje u privremen anonimni tip koji sadri i originalne i transformisane elemente:
IEnumerable<string> query = names
.Select (n => new
{
n = n,
vowelless = Regex.Replace (n, [aeiou], )
}
)
.Where (temp0 => (temp0.vowelless.Length > 2))
.OrderBy (temp0 => temp0.vowelless)
.Select (temp0 => ((temp0.n + - ) + temp0.vowelless))
LINQ | 161
Nastavljanje upita
Ukoliko elite da dodate odredbe iza odredbe select ili group, morate
upotrebiti rezervisanu re into kako biste nastavili upit. Na primer:
from c in The quick brown tiger.Split()
select c.ToUpper() into upper
where upper.StartsWith (T)select upper
// REZULTAT: THE, TIGER
Iza odredbe into, promenljiva skupa koja joj prethodi nalazi se van
opsega vidljivosti.
Upite s rezervisanom reju into, kompajler samo prevodi u dui lanac operatora:
The quick brown tiger.Split()
.Select (c => c.ToUpper())
.Where (upper => upper.StartsWith (T))
Kada u upitu ima vie od jedne odredbe from, kompajler poziva Select
Many:
162 | C# 5.0 Mali referentni prirunik
LINQ | 163
Spajanje
LINQ ima tri operatora za spajanje, a najvaniji su Join i GroupJoin
koji spajaju elemente na osnovu pretraivanja po kljuevima. Join i
GroupJoin podravaju samo podskup funkcionalnosti koje dobijate
pomou vie generatora/SelectMany, ali su pogodniji za lokalne upite poto koriste strategiju pretraivanja zasnovanu na he-tabelama
umesto da izvravaju ugneene petlje.
(Sa upitima tipa LINQ to SQL i Entity Framework, operatori za spajanje
nisu u prednosti nad vie generatora.)
Evo rezultata:
Tom bought a House
Dick bought a Boat
Dick bought a Car
Harry bought a Holiday
S lokalnim sekvencama, operatori Join i GroupJoin efikasnije obrauju velike kolekcije od SelectMany zato to prvo uitaju unutranju
sekvencu u heiranu tabelu kljueva. Meutim, kad je re o upitima
u baze podataka, isti rezultat moete podjednako efikasno dobiti na
sledei nain:
from c in customers
from p in purchases
where c.ID == p.CustomerID
select c.Name + bought a + p.Product;
Operator GroupJoin
GroupJoin obavlja isto to i Join, ali umesto ravnog daje hijerarhij-
LINQ | 165
N apomena
Odredba into se prevodi u GroupJoin samo kada se pojavljuje
neposredno iza odredbe join. Ukoliko se nalazi iza odredbe select ili group, oznaava nastavak upita. Ova dva naina primene
rezervisane rei into prilino su razliiti, mada imaju jednu zajedniku osobinu: oba uvode novu promenljivu upita.
Isti rezultat bismo mogli dobiti (ali manje efikasno, za lokalne upite)
projektovanjem u anoniman tip koji ukljuuje podupit:
from c in customers
select new
{
CustName = c.Name,
custPurchases =
purchases.Where (p => c.ID == p.CustomerID)
}
Operator Zip
Zip je najjednostavniji operator za spajanje. On nabraja dve sekvence element po element (kao patent-zatvara), vraajui sekvencu zasnovanu na primeni neke funkcije na svaki par elemenata. Na primer:
int[] numbers = { 3, 5, 7 };
string[] words = { three, five, seven, ignored };
IEnumerable<string> zip =
numbers.Zip (words, (n, w) => n + = + w);
Viak elemenata u bilo kojoj od dve ulazne sekvence ignorie se. Upiti u baze podataka ne podravaju Zip.
Sortiranje
Rezervisana re orderby sortira sekvencu. Moete zadati proizvoljan
broj izraza po kojima e se sekvenca sortirati:
string[] names = { Tom,Dick,Harry,Mary,Jay };
IEnumerable<string> query = from n in names
orderby n.Length, n
select n;
Kompajler prevodi prvi orderby izraz u poziv funkcije OrderBy, a dalje izraze u poziv funkcije ThenBy:
LINQ | 167
To se prevodi u:
.OrderByDescending (n => n.Length).ThenBy (n => n)
N apomena
Operatori za sortiranje vraaju proireni oblik interfejsa IEnumerable<T>, zvan IOrderedEnumerable<T>. Taj interfejs definie dodatnu funkcionalnost potrebnu operatoru ThenBy.
Grupisanje
GroupBy organizuje ravnu ulaznu sekvencu u sekvencu koju ini vie
Enumerable.GroupBy radi tako to uitava ulazne elemente u privremeni renik lista, pa svi elementi sa istim kljuem zavravaju u istoj podlisti. Zatim formira sekvencu iji su elementi grupe. Grupa je sekvenca koja ima klju (svojstvo Key):
public interface IGrouping <TKey,TElement>
: IEnumerable<TElement>, IEnumerable
{
// Klju vai za podsekvencu kao celinu
TKey Key { get; }
}
Elementi svake grupe standardno su netransformisani ulazni elementi, osim kada zadate argument elementSelector. Sledei kd projektuje svaki ulazni element u velika slova:
from name in names
group name.ToUpper() by name.Length
to se prevodi u:
names.GroupBy (
name => name.Length,
name => name.ToUpper() )
LINQ | 169
Nastavljanje upita esto se koristi u upitima tipa group by. Sledei upit
filtrira grupe u kojima postoje tano dva poklapanja:
from name in names
group name.ToUpper() by name.Length into grouping
where grouping.Count() == 2
select grouping
N apomena
Odredba where iza group by ekvivalentna je odredbi HAVING u
SQL-u. Primenjuje se na svaku podsekvencu ili na celu grupu, a
ne na pojedinane elemente.
Pravila za kompatibilnost elemenata slede ona za operator is u jeziku C#. Evo interne implementacije operatora Cast:
public static IEnumerable<TSource> Cast <TSource>
(IEnumerable source)
{
foreach (object element in source)
yield return (TSource)element;
}
Ovo se prevodi u:
from x in classicList.Cast <int>() ...
Dinamiko povezivanje
Dinamiko povezivanje odlae povezivanje (engl. binding) proces razreavanja tipova, lanova i operacija od trenutka prevoenja do trenutka izvravanja. Dinamiko povezivanje je uvedeno u verziju C#
4.0, i korisno je kada u vreme prevoenja vi znate da postoji odreena
funkcija, lan ili operacija, ali kompajler ne zna. To se obino deava
kada radite i sa dinamikim jezicima (kao to je IronPython) i COM, i
u sluajevima kada biste inae moda koristili refleksiju.
Dinamiki tip se deklarie pomou kontekstualne rezervisane rei
dynamic:
dynamic d = GetSomeObject();
d.Quack();
Dinamiki tip saoptava kompajleru da se opusti. Oekujemo da u vreme izvravanja d ima metodu Quack ali to ne moemo da dokaemo
statiki. Poto je d dinamiki tip, kompajler odlae povezivanje metode
Quack za d do trenutka izvravanja. Da bismo razumeli ta to znai, moramo razlikovati statiko povezivanje i dinamiko povezivanje.
Kada pozovemo metodu Quack dobijamo greku pri prevoenju, a razlog je sledei: mada vrednost sauvana u d moe da sadri metodu
po imenu Quack, kompajler to ne moe da zna poto je jedina informacija koju ima tip promenljive u ovom sluaju, object. Ali, sada emo
promeniti statiki tip promenljive d u dynamic:
dynamic d = ...
d.Quack();
obavljalo kada bi kompajler znao tip dinamikog objekta u trenutku izvravanja. Pomenute dve alternative zovu se namensko poveziva
nje (engl. custom binding) i jeziko povezivanje (engl. language binding).
Namensko povezivanje
Namensko povezivanje se obavlja kada dinamiki objekat implementira interfejs IDynamicMetaObjectProvider (IDMOP). Mada moete
implementirati IDMOP na tipove koje piete na jeziku C# i to je korisno ee ete koristiti IDMOP objekat iz dinamikog jezika implementiranog u .NET za Dynamic Language Runtime (DLR), kao to
su IronPython ili IronRuby. Objekti iz tih jezika implicitno implementiraju IDMOP kao sredstvo za direktno odreivanje znaenja operacija koje se obavljaju nad njima. Evo jednostavnog primera:
using System;
using System.Dynamic;
public class Test
{
static void Main()
{
dynamic d = new Duck();
d.Quack();
// Pozvana je metoda Quack
d.Waddle();
// Pozvana je metoda Waddle
}
}
public class Duck : DynamicObject
{
public override bool TryInvokeMember (
InvokeMemberBinder binder, object[] args,
out object result)
{
Console.WriteLine (binder.Name + was called);
result = null;
return true;
}
}
Klasa Duck u stvari nema metodu Quack. Umesto nje, ona koristi namensko povezivanje da bi presrela i protumaila sve pozive te metode.
Detaljnije razmatranje namenskog povezivanja dato je u poglavlju 20
knjige C# 5.0 za programere.
Jeziko povezivanje
Do jezikog povezivanja dolazi kada dinamiki objekat ne implementira interfejs IDynamicMetaObjectProvider. Ono je korisno kada
radite sa loe definisanim tipovima ili ogranienjima svojstvenim
.NET-ovom sistemu tipova. Pri korienju numerikih tipova, tipian
problem je to to oni nemaju zajedniki interfejs. Ve smo videli da se
metode mogu povezati dinamiki; isto vai i za operatore:
static dynamic Mean (dynamic x, dynamic y)
{
return (x + y) / 2;
}
static void Main()
{
int x = 3, y = 4;
Console.WriteLine (Mean (x, y));
}
Prednost je oigledna ne morate duplirati kd posebno za svaki numeriki tip. Meutim, gubite bezbednost statikih tipova, rizikujui da se generiu izuzeci pri izvravanju a ne greke pri prevoenju.
N apomena
Dinamiko povezivanje zanemaruje statiku bezbednost tipova,
ali ne i bezbednost tipova pri izvravanju. Za razliku od refleksije, dinamikim povezivanjem ne moete zaobii pravila pristupanja elementima tipova.
Jeziko povezivanje u vreme izvravanja projektovano je tako da se ponaa najslinije mogue statikom povezivanju kada bi tipovi dinamikih
objekata u vreme izvravanja bili poznati u vreme prevoenja. U naem
prethodnom primeru, program bi se ponaao isto i da smo eksplicitno
napisali kd da Mean radi s tipom int. Najznaajniji izuzetak u slinosti
statikog i dinamikog povezivanja odnosi se na proirene metode, koje
razmatramo u odeljku Funkcije koje se ne mogu pozivati, na strani 179.
N apomena
Dinamiko povezivanje loe utie i na performanse. Meutim,
zahvaljujui DLR-ovim mehanizmima keiranja, optimizuju se
ponovljeni pozivi istih dinamikih izraza to vam omoguava
da efikasno pozivate dinamike izraze u petlji. Zahvaljujui toj
optimizaciji, uobiajeno optereenje za jednostavan dinamiki
izraz na dananjem hardveru manje je od 100 ns.
Izuzetak RuntimeBinderException
Ako neki lan ne uspe da se povee, generie se RuntimeBinderException, koji moete smatrati grekom pri prevoenju u vreme izvravanja:
dynamic d = 5;
d.Hello();
// generie RuntimeBinderException
Strukturno, nema razlike izmeu reference na objekat i dinamike reference. Dinamika referenca samo omoguava da se nad objektom na
koji upuuje obavljaju dinamike operacije. Tip object moete konvertovati u dynamic da biste obavili bilo koju dinamiku operaciju na
tipu object:
object o = new System.Text.StringBuilder();
dynamic d = o;
d.Append (hello);
Console.WriteLine (o);
// hello
Dinamike konverzije
Tip dynamic moe se implicitno konvertovati u druge tipove i iz njih.
Da bi konverzija uspela, tip dinamikog objekta u vreme izvravanja
mora biti u stanju da se implicitno konvertuje u odredini statiki tip.
Sledei primer generie izuzetak RuntimeBinderException poto se int
ne moe implicitno konvertovati u short:
int i = 7;
dynamic d = i;
long l = d;
short j = d;
Evo i primera:
dynamic
var y =
int i =
int j =
x = hello;
hello;
x;
y;
//
//
//
//
Dinamiki izrazi
Dinamiki se mogu pozivati polja, svojstva, metode, dogaaji, konstruktori, indekseri, operatori i konverzije.
Nije dozvoljeno da se upotrebi rezultat dinamikog izraza povratnog
tipa void kao to je sluaj i sa izrazom statikog tipa. Razlika je to
to se greka javlja u vreme izvravanja.
Izrazi s dinamikim operandima obino su i sami dinamiki, poto se
efekat izostanka informacije o tipu prenosi dalje:
dynamic x = 2;
var y = x * 3;
Postoji nekoliko oiglednih izuzetaka od ovog pravila. Prvo, eksplicitnim konvertovanjem dinamikog izraza u statiki tip dobija se statiki izraz. Drugo, pozivanje konstruktora uvek rezultuje statikim izrazima ak i kada se konstruktor poziva s dinamikim argumentima.
Uz to, postoji i nekoliko graninih sluajeva gde je izraz koji sadri
dinamiki argument statian, meu kojima su prosleivanje indeksa
nizu i izrazi koji generiu delegate.
// x je prijemnik
Meutim, dinamiko povezivanje nije ogranieno na prijemnike: argumenti metode se takoe mogu dinamiki povezati. Funkcija s dinamikim argumentima poziva se da bi se razreavanje preklapanja odloilo od trenutka prevoenja na trenutak izvravanja:
class Program
{
static void Foo (int x) { Console.WriteLine (1); }
static void Foo (string x) { Console.WriteLine (2); }
static void Main()
{
dynamic x = 5;
dynamic y = watermelon;
Foo (x);
Foo (y);
// 1
// 2
}
}
void
void
void
void
void
X(object
X(object
X(string
X(string
Main()
x,
x,
x,
x,
object
string
object
string
y)
y)
y)
y)
{Console.Write(oo);}
{Console.Write(os);}
{Console.Write(so);}
{Console.Write(ss);}
object o = hello;
dynamic d = goodbye;
X (o, d);
// os
Poziv metode X(o,d) povezan je dinamiki zato to je jedan njen argument, d, tipa dynamic. Ali, poto je argument o statiki poznat, povezivanje bez obzira na to to se odvija dinamiki iskoristie tu injenicu. U ovom sluaju, razreavanje preklapanja e izabrati drugu
implementaciju metode X zbog statikog tipa o i tipa d koji je poznat
tek u trenutku izvravanja. Drugim reima, kompajler je onoliko statiki koliko uopte moe biti.
Atributi
Ve vam je poznat pojam dodeljivanja atributa elementima programskog koda pomou modifikatora, kao to su virtual ili ref. Ti konstrukti su ugraeni u jezik. Atributi su proiriv mehanizam za dodavanje namenskih informacija elementima koda (sklopovima, tipovima,
lanovima, povratnim vrednostima i parametrima). Ta proirivost je
korisna za servise koji su duboko integrisani u sistem tipova, a da im
ne trebaju specijalne rezervisane rei ili konstrukti jezika C#.
Dobar scenario za atribute predstavlja serijalizacija proces konvertovanja proizvoljnih objekata u odreen format ili iz njega. U ovom
scenariju, pomou atributa polja moe se zadati prevoenje izmeu
naina predstavljanja tog polja u jeziku C# i u izabranom formatu.
Klase za atribute
Atribut definie klasa koja nasleuje (direktno ili indirektno) apstraktnu klasu System.Attribute. Da biste atribut prikljuili elementu koda, zadajte ime tipa atributa u uglastim zagradama, ispred elementa koda. Na primer, evo kako ete atributom ObsoleteAttribute
obeleiti klasu Foo:
[ObsoleteAttribute]
public class Foo {...}
Kompajler prepoznaje ovaj atribut i prikazae upozorenje ukoliko se referencira tip ili lan oznaen kao zastareo. Po konvenciji, imena svih tipova
atributa zavravaju se reju Attribute. C# to prepoznaje i omoguava vam
da izostavite taj sufiks kada dodajete atribut:
[Obsolete]
public class Foo {...}
Odredita atributa
Implicitno, odredite atributa je element koda kome atribut neposredno prethodi, to je obino tip ili lan tipa. Meutim, atribute moete dodati i sklopu. Da biste to uradili, morate eksplicitno zadati odredite atributa.
Evo primera upotrebe atributa CLSCompliant za zadavanje usklaenosti celog bloka sa specifikacijom CLS (Common Language Specification):
[assembly:CLSCompliant(true)]
Atributi | 181
[Test(20)]
public void Method2() { ... }
[Test(20, FailureMessage=Debugging Time!)]
public void Method3() { ... }
}
naciju konstrukata) na koji moe da se primeni namenski atribut. Nabrajanje AttributeTargets ima lanove kao to su Class, Method, Parameter i Constructor (kao i All, koji kombinuje sva odredita).
Atributi | 183
Evo rezultata:
Method1 will be tested; reps=1; msg=
Method2 will be tested; reps=20; msg=
Method3 will be tested; reps=20; msg=Debugging Time!
Console.WriteLine (lineNumber);
}
}
N apomena
Vienitni rad, paralelni rad i asinhrono programiranje opsene
su teme. Posveena su im dva poglavlja u knjizi C# 5.0 za progra
mere, kao i razmatranje na adresi http://albahari.com/threading.
Task<int> ComplexCalculationAsync()
{
return Task.Run (() => ComplexCalculation());
}
Ovaj kd saoptava operaciji: Kada zavri, neka se izvri zadati delegat. U naem nastavku, prvo se poziva metoda GetResult koja vraa rezultat izraunavanja. (Ili, ako operacija nije uspela [generisan je
izuzetak], pozivanjem metode GetResult ponovo se generie taj izuzetak.) Na nastavak tada ispisuje rezultat pomou naredbe Console.WriteLine.
N apomena
Kompajler takoe generie kd da bi optimizovao situacije u kojima se operacije zavravaju sinhrono (odmah). Asinhrona operacija se zavrava odmah najee onda kada implementira unutranji mehanizam za keiranje a rezultat je ve u keu.
N apomena
Modifikator async slian je modifikatoru unsafe po tome to ne
utie na potpis metode niti na javne metapodatke; utie samo na
ono to se deava unutar date metode.
N apomena
CLR-ova implementacija metode OnCompleted ekalice (nastavka), omoguava da se nastavci standardno prosleuju kroz tekui
sinhronizacioni kontekst, ako takav postoji. U praksi, to znai da u
sluajevima sa korisnikim interfejsima sloenih klijenata (WPF,
Metro, Silverlight i Windows Forms), ako zadate await u niti korisnikog interfejsa, va kd e nastaviti da se izvrava u istoj toj niti.
To pojednostavljuje probleme bezbednosti niti.
Izraz na koji se primenjuje modifikator await obino je posao; meutim, kompajler e biti zadovoljan svakim objektom s metodom Get
Awaiter koji vraa objekat za ekanje koji implementira interfejs
INotifyCompletion.OnCompleted i s metodom GetResult ispravnog
tipa (i logikim svojstvom IsCompleted pomou kojeg se ispituje sinhroni zavretak).
Rezultat naeg await izraza je tipa int; razlog je to to je izraz koji smo
ekali bio tipa Task<int> (ija metoda GetAwaiter().GetResult() vraa tip int).
Task.Delay je statika metoda koja vraa posao koji se zavrava u zadatom broju milisekundi. Sinhroni ekvivalent metode Task.Delay jeste Thread.Sleep.
Task je negenerika osnovna varijanta klase Task<TResult> i funkcionalno joj je ekvivalentna, osim to nema rezultat.
Nakon prvog izvravanja metode ComplexCalculationAsync, tok izvravanja se vraa pozivaocu zbog izraza await. Kada se metoda zavri
(ili ne uspe), izvravanje se nastavlja od mesta gde je bilo prekinuto,
sa ouvanim vrednostima promenljivih i brojaa u petljama. Kompajler to postie tako to prevodi takav kd u mainu s konanim brojem
stanja (engl. state machine), kao i u sluaju iteratora.
Bez rezervisane rei await, runa upotreba nastavaka bi znaila da
morate napisati neto ekvivalentno maini s konanim brojem stanja.
To je oduvek bio razlog zbog kojeg je asinhrono programiranje teko.
Obratite panju na to da posao ne vraamo eksplicitno u telo metode. Kompajler generie posao koji obavetava o zavretku metode ili
u sluaju neobraenog izuzetka. To olakava izradu lanaca asinhronih poziva:
async Task Go()
{
await PrintAnswerToLife();
Console.WriteLine (Done);
}
(A poto metoda Go vraa objekat tipa Task, Go je samu po sebi mogue ekati.) Kompajler proiruje asinhrone funkcije koje vraaju poslove u kd koji (indirektno) koristi objekat klase TaskCompletionSource
da bi formirao posao koji zatim izvrava do kraja ili prekida.
N apomena
TaskCompletionSource je CLR tip koji vam omoguava da pravite poslove kojima upravljate runo, obeleavajui ih kao zavrene s rezultatom ili prekinute zbog izuzetka. Za razliku od Task.
Run, TaskCompletionSource ne blokira nit tokom trajanja operacije. Koristi se i za pisanje I/O metoda koje vraaju poslove (kao
to je Task.Delay).
Rad metode GetAnswerToLife moemo prikazati tako to emo je pozvati iz metode PrintAnswerToLife (koja se poziva iz metode Go):
async Task Go()
{
await PrintAnswerToLife();
Console.WriteLine (Done);
}
async Task PrintAnswerToLife()
{
int answer = await GetAnswerToLife();
Console.WriteLine (answer);
}
async Task<int> GetAnswerToLife()
{
await Task.Delay (5000);
int answer = 21 * 2;
return answer;
}
Asinhrone funkcije ine asinhrono programiranje slinim sinhronom programiranju. Evo sinhronog ekvivalenta nae eme poziva, u
kome se pozivanjem metode Go() dobija isti rezultat, nakon blokade
od pet sekundi:
void Go()
{
PrintAnswerToLife();
Console.WriteLine (Done);
}
void PrintAnswerToLife()
{
int answer = GetAnswerToLife();
Console.WriteLine (answer);
}
int GetAnswerToLife()
{
Thread.Sleep (5000);
int answer = 21 * 2;
return answer;
}
Paralelizam
Upravo smo prikazali najee korien ablon, a to je await s funkcijama koje vraaju kontrolu pozivajuem poslu odmah nakon pozivanja. Rezultat toga je sekvencijalni tok programa koji je logiki slian
sinhronom ekvivalentu.
Pozivanje asinhrone metode bez ekanja da se ona zavri, omoguava
da se kd koji sledi izvrava paralelno. Na primer, u sledeem kodu,
metoda PrintAnswerToLife izvrava se dvaput, paralelno:
var task1 = PrintAnswerToLife();
var task2 = PrintAnswerToLife();
await task1; await task2;
Poto nakon pozivanja ekamo na zavretak obe operacije, okonavamo paralelizam u toj taki (i ponovo generiemo eventualne izuzetke generisane u tim metodama). Klasa Task obezbeuje statiku
Asinhrone funkcije (C# 5.0) | 193
metodu po imenu WhenAll da bi se isti rezultat ostvario malo efikasnije. WhenAll vraa zadatak koji se zavrava kada se zavre svi poslovi koje ste prosledili toj metodi:
await Task.WhenAll (PrintAnswerToLife(),
PrintAnswerToLife());
Nebezbedan kd i pokazivai
C# podrava direktan rad s memorijom putem pokazivaa unutar
blokova koda oznaenih kao nebezbedni i prevoenja koda s opcijom
/unsafe kompajlera. Pokazivaki tipovi su korisni, pre svega, za inter
operabilnost sa API-jima na jeziku C, ali se mogu koristiti i za direkt
no pristupanje memoriji izvan upravljanog hipa ili takama koje su
kritine za performanse.
Osnove pokazivaa
Za svaki vrednosni ili pokazivaki tip V postoji odgovarajui pokazivaki tip V*. Instanca pokazivaa uva adresu promenljive. Pokazivaki tipovi se mogu (nebezbedno) konvertovati u bilo koji drugi pokazivaki tip. Evo glavnih operatora za rad s pokazivaima:
Operator
&
*
->
Znaenje
Operator adrese vraa pokaziva na adresu promenljive.
Operator dereferenciranja vraa promenljivu koja se nalazi na adresi pokazivaa.
Operator pokaziva lana je sintaksna preica, u kojoj je x->y ekvivalentno (*x).y.
Nebezbedan kd
Ako oznaite tip, lan tipa ili blok naredaba pomou rezervisane rei
unsafe, moete koristiti pokazivake tipove i obavljati operacije u stilu jezika C++ nad memorijom unutar njegovog opsega. Evo primera
u kome se pokazivai koriste za brzu obradu bit-mape:
unsafe void BlueFilter (int[,] bitmap)
{
int length = bitmap.Length;
fixed (int* b = bitmap)
{
int* p = b;
for (int i = 0; i < length; i++)
*p++ &= 0xFF;
}
}
Naredba fixed
Naredba fixed je potrebna da bi se fiksirao objekat koji se obrauje,
kao to je bit-mapa u prethodnom primeru. Tokom izvravanja programa, mnogim objektima se dodeljuje i oduzima memorija sa hipa.
Da bi se izbeglo nepotrebno zauzimanje ili fragmentiranje memorije, skuplja smea premeta objekte unaokolo. Odravanje pokazivaa na objekat je uzaludno ako se njegova adresa moe promeniti dok
196 | C# 5.0 Mali referentni prirunik
se on referencira, pa naredba fixed nalae skupljau smea da zakuca taj objekat i da ga ne pomera. Poto to moe loe uticati na efikasnost izvrnog okruenja, fiksirane blokove treba koristiti samo kratko, i ne treba im dodeljivati memoriju sa hipa.
Unutar naredbe fixed moete dobiti pokaziva na vrednosni tip, niz
vrednosnih tipova, ili znakovni niz. U sluaju nizova i znakovnih nizova, pokaziva e u stvari pokazivati na prvi element, koji je vrednosnog tipa.
Kada su vrednosni tipovi deklarisani u istom redu s referentnim tipovima, mora se fiksirati referentni tip:
class Test
{
int x;
unsafe static void Main()
{
Test test = new Test();
fixed (int* p = &test.x)
// Fiksira test
{
*p = 9;
}
System.Console.WriteLine (test.x);
}
}
System.Console.WriteLine (test.x);
}
}
Nizovi
Rezervisana re stackalloc
Memorija se moe eksplicitno dodeliti u obliku bloka na steku pomou rezervisane rei stackalloc. Poto je dodeljena na steku, ivotni vek
joj je ogranien na izvravanje metode, ba kao i u sluaju svake druge lokalne promenljive. Blok moe da koristi operator [] za indeksiranje memorije:
int* a = stackalloc int [10];
for (int i = 0; i < 10; ++i)
Console.WriteLine (a[i]);
// Ispisuje sirovu memoriju
void*
Prazan pokaziva (void*) ne pretpostavlja nita o tipu pridruenog
podatka i koristan je za funkcije koje rade direktno s memorijom.
Svaki pokazivaki tip moe se implicitno konvertovati u void*. void*
se ne moe dereferencirati, niti se nad praznim pokazivaima mogu
obavljati aritmetike operacije. Na primer:
unsafe static void Main()
{
short[] a = {1,1,2,3,5,8,13,21,34,55};
fixed (short* p = a)
{
// sizeof vraa veliinu vrednosnih tipova u bajtovima
Zap (p, a.Length * sizeof (short));
}
foreach (short x in a)
System.Console.WriteLine (x); // Ispisuje sve nule
}
unsafe static void Zap (void* memory, int byteCount)
{
byte* b = (byte*) memory;
for (int i = 0; i < byteCount; i++)
*b++ = 0;
}
Pretprocesorske direktive
Pretprocesorske direktive pruaju prevodi dodatne informacije o pojedinim delovima koda. Najee pretprocesorske direktive su uslovne
direktive, koje obezbeuju nain za ukljuivanje ili izostavljanje delova koda pri prevoenju. Na primer:
#define DEBUG
class MyClass
{
int x;
void Foo()
{
# if DEBUG
Console.WriteLine (Testing: x = {0}, x);
# endif }
...
}
Meutim, imajte u vidu da ne sastavljate obian C# izraz, i da simboli s kojima radite nemaju apsolutno nikakve veze s promenljivama
ni statikim ni drugim.
Simboli #error i #warning spreavaju nehotinu zloupotrebu uslovnih direktiva tako to nalau kompajleru da generie upozorenje ili
greku ukoliko mu se prosledi nepoeljan skup simbola.
Evo kompletne liste pretprocesorskih direktiva:
Pretprocesorska direktiva
Akcija
#define simbol
Definie simbol
#undef simbol
Pretprocesorska direktiva
Akcija
Izvrava kd do sledee direktive #endif
#elif simbol[operator simbol2] Kombinuje granu #else i test #if
#endif
Zavrava uslovne direktive
tekst upozorenja koje se pojavljuje kao rezultat
#warning tekst
prevoenja
tekst poruke o greci koja se pojavljuje kao
#error tekst
rezultat prevoenja
number je broj reda u izvornom kodu; file je
ime datoteke koje e se pojaviti u rezultatu; hid#line [number[file] | hidden] den nalae modulima za pronalaenje i otklanjanje greaka da preskoe kd od ove take do naredne direktive #line
#region name
Oznaava poetak celine u kodu
#endregion
Oznaava kraj celine
#pragma warning
Videti sledei odeljak
#else
Izostavljanjem broja iz direktive #pragma warning, deaktivirate odnosno ponovo aktivirate sve upozoravajue kodove.
Ukoliko dosledno primenjujete ovu direktivu, kd moete prevesti uz
opciju /warnaserror ona nalae kompajleru da eventualna zaostala
upozorenja tretira kao greke.
XML dokumentacija
Dokumentacioni komentar je blok ugraenog XML-a kojim se dokumentuje neki tip ili lan. Dokumentacioni komentar se pie neposredno ispred deklaracije tipa ili lana, i poinje s tri kose crte:
/// <summary>Otkazuje izvravanje upita.</summary>
public void Cancel() { ... }
Oznaava kratak opis (engl. tool tip) koji IntelliSense treba da prikae za dati tip ili lan. To je obino jedan izraz ili reenica.
<remarks>
<remarks>...</remarks>
Dodatni tekst kojim se opisuje dati tip ili lan. Preuzimaju ga generatori dokumentacije i kombinuju u opirniji opis tipa ili lana.
<param>
<param name=name>...</param>
Navodi izuzetak koji moe da generie neka metoda (cref ukazuje na tip izuzetka).
<permission>
<permission [cref=type]>...</permission>
<example>
<example>...</example>
Oznaava primer (koji koriste generatori dokumentacije). Obino sadri i opisni tekst i izvorni kd (izvorni kd je najee unutar oznaka <c> ili <code>).
<c>
<c>...</c>
Oznaava odlomak koda. Ova oznaka se obino koristi unutar bloka <example>.
<see>
<see cref=member>...</see>
Umee referencu na drugi tip ili lan, u istom redu. Generatori HTML dokumentacije obino pretvaraju ovu oznaku u hipervezu. Kompajler daje upozorenje ukoliko je ime tipa ili lana neispravno.
<seealso>
<seealso cref=member>...</seealso>
Referencira drugi tip ili lan. Generatori dokumentacije ovo obino upisuju u poseban odeljak See Also (Videti i) na dnu strane.
<paramref>
<paramref name=name/>
<item>
<term>...</term>
<description>...</description>
</item>
</list>
Kombinuje spoljnu XML datoteku koja sadri dokumentaciju. Atribut path oznaava XPath upit za odreeni element u toj
datoteci.
O autorima
Dozef (Joseph) Albahari je autor knjiga C# 4.0 in a Nutshell, LINQ
Pocket Reference i C# 4.0 Pocket Reference. Bavi se razvojem obimnih
poslovnih aplikacija due od 15 godina, i autor je LINQPad-a popularne alatke za izradu LINQ upita namenjenih bazama podataka. Trenutno radi honorarno, kao konsultant. Vie informacija o njemu nai
ete na adresi http://albahari.net/.
Ben Albahari je osniva veb lokacije TakeOnIt.com. Pet godina je
upravljao razvojem programa u Microsoftu, gde je radio na nekoliko
projekata, ukljuujui .NET Compact Framework i ADO.NET. Ben
je suosniva kompanije Genamics, koja pravi alatke za C# i J++ programere, kao i softver za analizu DNA i proteinskih sekvenci. Koautor
je knjige C# Essentials, prve knjige o jeziku C# koju je objavila izdavaka kua OReilly, kao i prethodnih izdanja knjige C# in a Nutshell.
O autorima | 207