Algus
Hello World
Tere tulemast meie interaktiivsesse veebiraamatusse! Miks interaktiivsesse? Aga seepärast, et meie raamatus saab koodinäidiseid koha peal katsetada.
Proovi nüüd seda programmi, vajutades koodijupi järel paremal olevat nuppu Käivita. All olevas mustas aknas küsitakse sinult nime ja antakse seejärel vastuseks tervitus.
public class Program
{
public static void Main()
{
Console.WriteLine("Palun kirjuta siia oma nimi ja vajuta Enter:");
Console.WriteLine("Tere " + Console.ReadLine() + "!");
}
}
public class Program
{
public static void Main()
{
Console.WriteLine("Palun kirjuta siia oma nimi ja vajuta Enter:");
Console.WriteLine("Tere " + Console.ReadLine() + "!");
}
}Vaatame siis lähemalt, mis siin toimus.
Käsureaprogramm
Me kasutame selles veebiraamatus käsureaprogrammi, mis kirjutab oma väljundi pulti (console) ja loeb ka inimese sisestatud info samast puldist. See on programmeerimiskeele õppimiseks kiire ja vahetu lahendus, sest ei pea alustama kasutajaliidese loomisest.
Objektid
C# on objektorienteeritud keel, mis tähendab, et kõik käsud on alati mingite objektide sees. Meie näidises on objekt (class) Program.
Mõnedes rakendustes (nagu WPF ja Silverlight) peab määrama, mis on käivitatav objekt, aga teistes (nagu meie käsureaprogramm) otsitakse see automaatselt.
Meetodid
Objektidel on tavaliselt meetodid. Kui vaadelda objekti kui elektripliiti, siis meetodid on sellel paiknevad nupud. Iga nupp ehk meetod teeb midagi. Meetodid võivad vajada sisendparameetreid ja nad võivad tagastada midagi väljundis.
Meetodi sisendparameetrid kirjutatakse meetodi nime järele sulgude vahele. Meie objektil Program on üks meetod Main ja see, nagu näed, ei vaja mingeid sisendparameetreid.
Meetod võib pärast töö lõppu ka midagi tagastada. Tagastatav kirjutatakse enne meetodi nime. Meie meetod tagastab void, mis tähendab "mitte midagi".
Käsureaprogrammil peab olema meetod Main() ja kui see meetod oma töö lõpetab, lõpeb ka programmi töö.
Programmi sisu
Programmi sisuks on kaks rida. Esimene neist palub sisestada kasutajal tema nimi ja vajutada Enter. Selle jaoks kasutatakse pulti:
Console.WriteLine() kirjutab pulti sulgude vahel oleva info.
Teine koodirida loeb inimese sisetatud teksti (mille inimene peab lõpetama Enteriga), paneb selle vastuse sisse ja kirjutab vastuse pulti.
Console.ReadLine() laseb inimesel sisestada ühe rea teksti ja tagastab sisestatud teksti.
Kokkuvõte
Proovi allolevas aknas koodi muuta (näiteks muutes jutumärkide vahel olevat teksti) ja käivitada. Sa näed, et muudatused avaldavad mõju.
Algus
Objektid
Kogu C#-programm on klassides (class). Klass on sisuliselt üks ühik tarkvara, mille eksemplar ehk objekt võib sisaldada andmeid ja tööriistu.
Objektid hoiavad oma andmeid väljades (field).
Väli võib olla avalik (public), misjuhul saab selle ligi iga objekti kasutaja, või isiklik (private), misjuhul saab seda kasutada ainult objekt ise. Kui seda direktiivi pole antud, on väli vaikimisi isiklik.
Igal väljal peab olema tüüp, mis on kirjutatud tema ette, ja nimi.
Alltoodud näites on klass, millel on üksainus väli tüübiga string, mis tähendab teksti.
public class Program
{
public static void Main()
{
Console.WriteLine(Objekt.Nimi);
}
}
class Objekt
{
public static string Nimi = "Mingi nimi";
}
public class Program
{
public static void Main()
{
Console.WriteLine(Objekt.Nimi);
}
}
class Objekt
{
public static string Nimi = "Mingi nimi";
}Pole raske ära arvata, mis programmi käivitades pulti kirjutades. Aga proovi siiski.
Kui olid tähelepanelik, siis nägid, et välja definitsioonis on veel üks sõna, static. See tähendab, et väli on kättesaadav klassi definitsioonist. Klass on defineeritud ühe korra, aga sellest võib toota ükskõik kui palju eksemplare (instance).
Staatiline väli on ühine kõigis eksemplarides, kuid dünaamiline väli (ilma direktiivita static) on igal eksemplaril oma.
Vaata järgmist näidet, kus objektist luuakse kaks eksemplari. Väli Nimi on nüüd dünaamiline ja selle väärtus on kummalgi eksemplaril erinev.
public class Program
{
public static void Main()
{
Objekt o1 = new Objekt();
o1.Nimi = "Esimene";
Objekt o2 = new Objekt();
o2.Nimi = "Teine";
Console.WriteLine(o1.Nimi);
Console.WriteLine(o2.Nimi);
}
}
class Objekt
{
public string Nimi;
}
public class Program
{
public static void Main()
{
Objekt o1 = new Objekt();
o1.Nimi = "Esimene";
Objekt o2 = new Objekt();
o2.Nimi = "Teine";
Console.WriteLine(o1.Nimi);
Console.WriteLine(o2.Nimi);
}
}
class Objekt
{
public string Nimi;
}Objekti eksemplari loomiseks tuleb kasutada käsklust new (uus). Nagu nägid, on meie näites kaks muutujat, mille tüüp on Objekt. Muutuja väärtuseks saabki objekti eksemplar. Muutujatest järgmises artiklis.
Proovi nüüd käivitada järgmist näidet:
public class Program
{
public static void Main()
{
Console.WriteLine(Objekt.Nimi);
}
}
class Objekt
{
public string Nimi;
}
public class Program
{
public static void Main()
{
Console.WriteLine(Objekt.Nimi);
}
}
class Objekt
{
public string Nimi;
}Miks see annab veateate? Sellepärast, et me proovime kasutada objekti välja, aga ei loo objekti eksemplari. Kuna tegemist ei ole staatilise väljaga, ei ole võimalik väljale ka ligi pääseda.
Järgmine variant on aga toimiv:
Console.WriteLine(new Objekt().Nimi);
public class Program
{
public static void Main()
{
Console.WriteLine(new Objekt().Nimi);
}
}
class Objekt
{
public string Nimi;
}Me loome objekti eksemplari, küsime mis on tema Nimi (see on tühi), ja rohkem me seda eksemplari ei vaja.
Algus
Muutujad
Muutuja on tähis, mis sisaldab mingi objekti eksemplari (loodetavasti lugesid eelmist peatükki).
C# on range keel, mis tähendab, et igal muutujal on kindel tüüp. Range keel võib tunduda lõtvade keeltega (nagu näiteks PHP ja JavaScript) võrreldes ebamugav, aga rangus programmi kirjutamisel aitab ära hoida rumalaid vigu. Seega pole C# rangus mitte puudus, vaid nimelt projekteeritud omapära, millel on omad plussid.
Eelmises osas käsitlesime objekti väljasid, mis on oma olemuselt samad mis muutujad, aga muutujad erinevad selle poolest, et need on ajutised ja kohalikud. Need ei ole ligipääsetavad väljastpoolt.
Vaata järgmist näidet:
string nimi = "Mati";
Console.WriteLine(nimi);
public class Program
{
public static void Main()
{
string nimi = "Mati";
Console.WriteLine(nimi);
}
}Me defineerime muutuja nimi ja anname talle kohe ka väärtuse. Seejärel me väljastame muutuja väärtuse.
Aga järgmine näide ebaõnnestub:
string nimi;
Console.WriteLine(nimi);
public class Program
{
public static void Main()
{
string nimi;
Console.WriteLine(nimi);
}
}Ebaõnnestumine on tingitud sellest, et muutuja on küll defineeritud, kuid sellel pole väärtust. Asi on selles, et string on objekt, mis vajab eksemplari loomist. Muutuja defineerimine näitab küll koha, kus seda eksemplari hoidma hakatakse, aga ei loo eksemplari ennast. Ingliskeelne veateade on "Use of unassigned local variable".
Kui mõned keeled (nagu JavaScript) lubavad muutujatele väärtuse andmata jätta, siis C# seda ei luba. Igal objektil on ka vaikeväärtus (default), mis tähendab tühja muutujat. Kui string on väärtusega null, siis see tähendab, et muutuja on väärtustatud, aga objekti eksemplari pole loodud. Selline olek on muutujate puhul täiesti lubatav.
Järgmine näide annab muutujale tühiväärtuse null ja proovib seejärel tagastada teksti pikkuse:
string nimi = null;
Console.WriteLine(nimi.Length);
public class Program
{
public static void Main()
{
string nimi = null;
Console.WriteLine(nimi.Length);
}
}Programm tekitab vea "Object reference not set to an instance of an object", mis tähendab "Viide objektile ei viita eksemplarile".
Järgmine näide on aga täiesti okei:
string nimi = null;
Console.WriteLine(nimi);
public class Program
{
public static void Main()
{
string nimi = null;
Console.WriteLine(nimi);
}
}Muutuja on tühi, aga ta ei ole ilma väärtuseta. Tema väärtus on "tühi" ehk null. Seda väljundisse kirjutades ei kirjutata muidugi midagi, aga tegemist on legaalse operatsiooniga.
Muutujate tüübid
Kuna igaüks võib objekte lõpmatult defineerida ja süsteemis on tuhandeid eeldefineeritud objekte, ei saa ükski programmeerija kõiki tüüpe kunagi päris selgeks. Aga mõned põhilised on siin:
bool kahendmuutuja, millel võib olla väärtuseks true või false
string Tekst, mis on alati Unicode-kodeeringus ehk võib sisaldada täpitähti, kirillitsat ja nii edasi
char Unicode-kodeeringus täht (nendest koosneb string)
byte Üks bait (8 bitti)
int (ehk Int32) 4-baidine (ehk 32-bitine) täisarv
long (ehk Int64) 8-baidine (ehk 64-bitine) täisarv
double 8-baidine reaalarv (ujuva komakohaga arv)
DateTime Kuupäev ja kellaaeg
TimeSpan Ajavahemik
Kõik need tüübid on oma olemuselt objektid, neil on nii staatilised kui ka dünaamilised meetodid. Igal tüübil on oma tühiväärtus, mis näiteks stringil on null, arvudel on 0, aga DateTime'il on DateTime.MinValue ja TimeSpanil on TimeSpan.Zero.
Algus
Tehted
Muutujatega saab teha tehteid, kasutades tehtemärke (inglise keeles operator). Igale objektile saab defineerida suvalisi tehteid, aga praegu vaatame standardseid tehteid — ja neid on rohkemgi kui siin käsitleme.
Muutujale väärtuse andmiseks kasutatakse võrdusmärki. See on kõige lihtsam tehe.
string nimi = "Juku";
Console.WriteLine(nimi);
public class Program
{
public static void Main()
{
string nimi = "Juku";
Console.WriteLine(nimi);
}
}Liitmistehe
Teine levinud tehe on liitmine, milleks kasutatakse plussmärki. Liita saab näiteks arve ja ajavahemikke, aga ka stringe. Stringiga saab liita mida iganes, misjärel teisendatakse liidetav alati stringiks.
Proovi järgmisi näiteid:
Console.WriteLine(1 + 1);
public class Program
{
public static void Main()
{
Console.WriteLine(1 + 1);
}
}
Console.WriteLine("Tulemus on " + 1 + 1);
public class Program
{
public static void Main()
{
Console.WriteLine("Tulemus on " + 1 + 1);
}
}
Console.WriteLine("Tulemus on " + (1 + 1));
public class Program
{
public static void Main()
{
Console.WriteLine("Tulemus on " + (1 + 1));
}
}Vaata, mis on erinevus eelviimase ja viimase näite puhul. Tulemus on igatahes erinev. Asi on selles, et tehteid tehakse teatud järjekorras, ja liitmisel vasakult paremale. Eelviimase näite puhul liidetakse stringile arv (ja tulemus on string), millele liidetakse veel üks arv. Teise näite puhul kasutatakse sulge, mis tähendab, et sulgudes olev tehe teostatakse esiteks (kahe arvu vahel) ja liidetakse seejärel stringile.
Liitmistehe ++ liidab arvule väärtuse "1" ja i++ on sarnane sellega kui kirjutada i = i + 1. Üks erinevus siiski on. Nimelt võib ++ olla enne või pärast muutujat. Kui see on enne muutujat, liidetakse 1 enne ja seejärel tagastatakse muutuja väärtus, kui see on pärast, liidetakse 1 muutujale pärast selle väärtuse tagastamist. Võrdle neid kahte näidet, need annavad erineva tulemuse:
int i = 0;
Console.WriteLine(i++);
Console.WriteLine(i);
public class Program
{
public static void Main()
{
int i = 0;
Console.WriteLine(i++);
Console.WriteLine(i);
}
}
int i = 0;
Console.WriteLine(++i);
Console.WriteLine(i);
public class Program
{
public static void Main()
{
int i = 0;
Console.WriteLine(++i);
Console.WriteLine(i);
}
}Liita saab ka "+=" tehet kasutades. Seejuures liidetakse ühest erinev arv. Näiteks i++ annab sama tulemuse mis i += 1, aga võib teha ka i += 100.
Tehted arvudega
Liitmistehte vastand on lahutamistehe, mille kohta kehtivad samad reeglid, ainult et lahutamistehet ei saa kasutada stringi puhul.
Arvusid saab ka korrutada (*) ja jagada (/).
Tehted seoses kahendväärtustega
Kahendväärtus on tüübiga bool ja siin on 2 liiki tehteid.
Tehted, mis tagastavad kahendväärtuse
Need tehted tagastavad kahendväärtuse: "==" (võrdne), ">" (suurem kui), "<" (väiksem kui), ">=" (suurem või võrdne) ja "<=" (väiksem või võrdne).
Tsekka seda näidet:
Console.WriteLine(5 > 4);
public class Program
{
public static void Main()
{
Console.WriteLine(5 > 4);
}
}Tehted kahendväärtustega
Need tehted on võimalikud kahe kahendväärtuse vahel: "&&" (ja) ning "||" (või).
Mida tagastab see näide?
Console.WriteLine(5 > 4 && 5 < 4);
public class Program
{
public static void Main()
{
Console.WriteLine(5 > 4 && 5 < 4);
}
}Ja mida tagastab see?
Console.WriteLine(5 > 4 || 5 < 4);
public class Program
{
public static void Main()
{
Console.WriteLine(5 > 4 || 5 < 4);
}
}Kui saad aru, miks, oled asjast õigesti aru saanud.
Tehted bittidega
C# tunneb tehtemärke "&" (bitwise AND), "|" (bitwise OR) ja "^" (bitwise XOR), samuti "<<" (shift left) ja ">>" (shift right). Need on samad mis muudes programmeerimiskeeltes ja mõeldud edasijõudnutele.
Algus
Meetodid
Kui mõnes programmeerimiskeeles on eristatud funktsioone ja protseduure (kus esimesed tagastavad väärtuse ja teised mitte), siis C# jaoks on kõik meetodid. Meetod, mis ei tagasta mitte midagi, tagastab tegelikult tüübi void ehk mitte midagi.
Meetodi definitsioon sisaldab alati 1) meetodi väljundi tüüpi; 2) meetodi nime; 3) sulgusid meetodi nime järel.
Väljund
Meetodil saab olla ainult üks väljund. See võib olla mistahes objektitüüpi ja selle tüüp kirjutatakse vahetult enne meetodi nime. Alltoodud näites tagastab meetod GetInt täisarvu (int):
class Objekt
{
public static int GetInt()
{
return 1;
}
}
public class Program
{
public static void Main()
{
Console.WriteLine(Objekt.GetInt());
}
class Objekt
{
public static int GetInt()
{
return 1;
}
}
}Väljundi tagastamiseks meetodi sees on ainult üks moodus: käsk return, millele järgneb tagastatav väärtus. Kui meetodi väljundiks on void, kirjutatakse lihtsalt return; millele ei järgne mingit väärtust. Kui meetodi väljundiks ei ole void, on return kohustuslik.
Parameetrid
Sisendparameetreid võib olla suvaline arv ja need kirjeldatakse sulgude vahel. Kui sisendparameetreid ei ole, ongi ainult tühjad sulud.
Allpool on meetod, mis suurendab sisendparameetri väärtust 1 võrra:
public static int GetInt(int i)
{
return i + 1;
}
public class Program
{
public static void Main()
{
Console.WriteLine(Objekt.GetInt(1));
}
class Objekt
{
public static int GetInt(int i)
{
return i + 1;
}
}
}Juhul, kui meetod sisendparameetri väärtust muudab, ei kajastu see muudatus väljaspool meetodit. Käivita alltoodud näide:
public class Program
{
public static void Main()
{
int i = 1;
Objekt.Do(i);
Console.WriteLine(i);
}
class Objekt
{
public static void Do(int i)
{
i += 100;
}
}
}
public class Program
{
public static void Main()
{
int i = 1;
Objekt.Do(i);
Console.WriteLine(i);
}
class Objekt
{
public static void Do(int i)
{
i += 100;
}
}
}Sisendparameeter võib olla ref-tüüpi, mis tähendab, et meetodile edastatakse selle viide (reference), lubades muuta muutuja originaalväärtust. Selleks kirjutatakse käsklus ref sisendparameetri ette nii meetodi definitsioonis kui ka seda välja kutsudes (et tagada, et programmeerija saab täpselt aru, mida teeb).
Käivita nüüd see kood ja vaata, kuidas on selle käitumine erinev eelmisest:
public class Program
{
public static void Main()
{
int i = 1;
Objekt.Do(ref i);
Console.WriteLine(i);
}
class Objekt
{
public static void Do(ref int i)
{
i += 100;
}
}
}
public class Program
{
public static void Main()
{
int i = 1;
Objekt.Do(ref i);
Console.WriteLine(i);
}
class Objekt
{
public static void Do(ref int i)
{
i += 100;
}
}
}Sisendparameeter võib olla ka out-tüüpi, mis tähendab tegelikult, et tegemist on väljundparameetriga. Sellest on kasu juhtumiks, kui meetod peab tagastama rohkem kui ühe väljundi. Parameeter, mis on out-tüüpi, ei hooli muutuja algväärtusest:
public class Program
{
public static void Main()
{
int i;
Objekt.Do(out i);
Console.WriteLine(i);
}
class Objekt
{
public static void Do(out int i)
{
i = 100;
}
}
}
public class Program
{
public static void Main()
{
int i;
Objekt.Do(out i);
Console.WriteLine(i);
}
class Objekt
{
public static void Do(out int i)
{
i = 100;
}
}
}Vaikeväärtus
Sisendparameetril võib olla vaikeväärtus. Sel juhul võib meetodit välja kutsudes parameetri andmata jätta.
Alltoodud näites sobivad mõlemad meetodi kasutamise variandid, sest vaikeväärtuseks on 0:
public class Program
{
public static void Main()
{
Do();
Do(0);
}
static void Do(int i = 0)
{
Console.WriteLine(i);
}
}
public class Program
{
public static void Main()
{
Do();
Do(0);
}
static void Do(int i = 0)
{
Console.WriteLine(i);
}
}Mitu parameetrit
Kui parameetreid on mitu, eraldatakse need üksteisest komaga:
public class Program
{
public static void Main()
{
Do();
Do(0);
Do(0, 1);
}
static void Do(int i = 0, int j = 0)
{
Console.WriteLine(i + j);
}
}
public class Program
{
public static void Main()
{
Do();
Do(0);
Do(0, 1);
}
static void Do(int i = 0, int j = 0)
{
Console.WriteLine(i + j);
}
}Meetodit saab defineerida ka nii, et selle parameetreid on teadmata hulk.
Sel juhul peavad kõik parameetrid olema ühte tüüpi ja need jõuavad meetodisse massiivina (massiividest edaspidi).
Proovi seda näidet, mis võimaldab parameetrina anda suvalise hulga täisarve:
public class Program
{
public static void Main()
{
Do();
Do(0);
Do(0, 1);
}
static void Do(params int[] ii)
{
Console.WriteLine(ii.Length);
}
}
public class Program
{
public static void Main()
{
Do();
Do(0);
Do(0, 1);
}
static void Do(params int[] ii)
{
Console.WriteLine(ii.Length);
}
}See pole kaugeltki kõik, aga alustuseks piisab.
Algus
Omadused
Omadus (property) on oma olemuselt sarnane väljaga (field), aga omadus võimaldab kirjutada väärtuse lugemisse ja kirjutamisse lisakoodi. Lisakood on omaduse sees, ja väljastpoolt (objekti kasutajale) käitub omadus samamoodi nagu väli.
Lihtsaim viis omadust defineerida on selline:
class Objekt
{
public int Omadus { get; set; }
}
public class Program
{
public static void Main()
{
Objekt o1 = new Objekt();
o1.Omadus = 1;
Console.WriteLine(o1.Omadus);
}
class Objekt
{
public int Omadus { get; set; }
}
}See ongi sisuliselt sama mis väli, sest { get; set; } loob sisemiselt ühe välja, kuhu salvestatakse ja kust loetakse omaduse Omadus väärtus.
Aga get ja set võivad sisaldada ka koodi. Selleks teeme sisemise välja ja avaldame omaduse. Omadust kasutame selleks, et välja sisse salvestatavat infot enne salvestamist kontrollida:
class Objekt
{
private int omadus;
public int Omadus
{
get
{
return omadus;
}
set
{
if (value < 1)
omadus = 1;
else if (value > 9)
omadus = 9;
else
omadus = value;
}
}
}
public class Program
{
public static void Main()
{
Objekt o1 = new Objekt();
o1.Omadus = 0;
Console.WriteLine(o1.Omadus);
}
class Objekt
{
private int omadus;
public int Omadus
{
get
{
return omadus;
}
set
{
if (value < 1)
omadus = 1;
else if (value > 9)
omadus = 9;
else
omadus = value;
}
}
}
}Nagu näete, sisaldab väli omadus (väikese tähega) tegelikku väärtust. Kuna see on isiklik väli, ei saa objekti väljastpoolt seda kätte. Selle lugemiseks kasutatakse Omadust, millel on get {}, mis tagastab välja väärtuse sellisena nagu ta on.
Samas kontrollib set {} Omadusele väärtuse omistamisel, kas väärtus jääb vahemikku 1..9 ja kirjutab selle siis välja sisse. Nagu nägite, kasutatakse siin eeldefineeritud muutujat value, mis sisaldab set-üksusesse saabuvat väärtust (ja see on loomulikult sama tüüpi mis omadus ise).
Seda kõike annaks sama hästi saavutada meetoditega int GetOmadus() ja void SetOmadus(int value), aga omaduse defineerimise kaudu on asi lihtsalt natuke ilusam, kuna väärtuse omistamisel saab kasutada tehtemärgina võrdusmärki ja nii lugemisel kui ka kirjutamisel on nimetus sama. Teine pluss on omaduste kasutamisel veel: programmi algjärgus ei ole ehk sul plaanis väljale mingeid kontrollimehanisme teha ja sa teed välja lihtsalt avalikuks (public). Hiljem võid selle omaduseks ümber teha ning välja kasutajad ei pea midagi muutma.
Omadust saab teha ka ainult loetavaks või ainult kirjutatavaks, kui jätad ära vastavalt set {} või get {} osa.
Ja muidugi saab omadustki teha isiklikuks (private) või avalikuks (public).
Staatiline omadus
Kui omaduse ees on direktiiv static, on see objekti definitsiooni omadus, millele saab ligi ilma objekti eksemplari loomata. See on siis ühine ehk globaalne kõigile, ükskõik kui palju eksemplare objektist tehtud ka poleks.
public static void Main()
{
Console.WriteLine(Objekt.Omadus);
}
class Objekt
{
public static int Omadus
{
get
{
return 1;
}
}
}
public class Program
{
public static void Main()
{
Console.WriteLine(Objekt.Omadus);
}
class Objekt
{
public static int Omadus
{
get
{
return 1;
}
}
}
}Ülaltoodud koodinäidises on objektil staatiline omadus, mis on ainult lugemiseks.
Algus
Nimeruum
Nimeruum (namespace) on sisuliselt klasside grupp. Kui vanemates programmeerimiskeeltes (Delphi, PHP) on globaalsed muutujad ja globaalsed funktsioonid, siis C# on puhtalt objektorienteeritud. See tähendab, et kogu süsteemikood on klassides.
Muidugi on ka C#-s defineeritud terve ports kinnissõnu nagu for, while, int ja string, aga ei ole selliseid funktsioone nagu näiteks inttostr().
.NETi teegid sisaldavad tuhandeid klasse. Lisaks on maailmas saada tuhandeid lisateeke. Selleks, et nende klassinimede kattumine ei tekitaks probleemi, on välja mõeldud nimeruumid. Kõigi teekide klassid on omakorda nimeruumide sees ja nende kasutamine käib näiteks nii:
System.String s = "tere";
Esiteks tuleb nimeruum, seejärel klass. Nii nimeruumi kui ka klassi nimi võib sisaldada punkti. Klassi puhul läheb punkti vaja siis, kui ühe klassi sees on defineeritud teine.
public class Väline
{
public class Sisene
{
}
}
public class Program
{
public static void Main()
{
Väline.Sisene k = new Väline.Sisene();
}
}
public class Väline
{
public class Sisene
{
}
}
public class Program
{
public static void Main()
{
Väline.Sisene k = new Väline.Sisene();
}
}Muidugi on kõik meie koodinäidised samuti mingi nimeruumi sees, ainult et seda kõike tehakse lava taga. Nimeruumi defineeritakse nii:
namespace Nimeruum
{
public class Program
{
}
}
Nimeruum võib olla samamoodi teise nimeruumi sees. Ka sel juhul kasutatakse punkti. Kui käivitad selle koodinäidise, avaldab see, mis nimeruumi sees meie konsool on, ja näitab selle järel punktiga nimeruumi Mati:
namespace Mati
{
public class Program
{
public void Main()
{
Console.WriteLine(GetType().FullName);
}
}
}
namespace Mati
{
public class Program
{
public void Main()
{
Console.WriteLine(GetType().FullName);
}
}
}Selleks, et klassi nimele ei peaks nimeruumi alati ette kirjutama, kasutatakse direktiivi using, mis lisab koodifaili algusse kasutatavad nimeruumid. Meie konsoolis lisatakse need automaatselt ja neid muuta ei saa.
Näiteks selle asemel, et peaks kogu aeg kasutama klassi System.Text.Encoding, võib panna faili ülaossa rea using System.Text; ja seejärel saab kirjutada lihtsalt Encoding.
Kui erinevates nimeruumides on kattuvaid klassinimesid (nagu esineb ka .NETi teekides) ja mõlemad nimeruumid on lisatud using-direktiiviga, tuleb klassi kasutades defineerida ka nimeruum.
Näiteks eksisteerib klass System.IO.Path ja klass System.Windows.Shapes.Path. Kui on tehtud using System.IO ja using System.Windows.Shapes, ei jäägi muud üle kui kirjutada klassi Path nime ette alati ka nimeruum.
Üks nimeruum võib olla defineeritud mitmes kohas, isegi mitmes erinevas teegis, ja .NETi teekides tehakse seda trikki pidevalt. Uue teegi lisamisel lisandub nimeruumi automaatselt uusi klasse.
Kui nimeruum on nuhtluseks
Üks kõige suurem nuhtlus C#-keelt tundma õppides ongi see nimeruumide värk. Mitte et see raskesti mõistetav oleks, aga veebis olevates koodinäidistes ei ole tavaliselt mainitud, mis nimeruumiga on tegu. Näed klassi nime, aga kompileerida ei saa, sest sellist klassi ei leita. Visual Studio ei ole piisavalt nutikas, et osata otsida viidatud teekidest automaatselt ise nimeruume, milles soovitud klass leidub, ja neid siis veateate asemel pakkuda.
Siin tuleb abiks järgmine trikk: olles klassi peal, millele ei leidu nimeruumi, vajuta Visual Studios klahve Alt + Shift + F12, ja sulle otsitakse üles kõik kohad, kus see sõna nimeruumides ja klassides esineb. Nii leidub ka õige nimeruum, mida using-direktiiviga faili algusse lisada.
Algus
Üldistamine
Üldistamine (generics) võimaldab kirjutada tüübiülest koodi, mis samas on kasutatav tüübikindlana. Selle asemel, et kasutada object-tüüpi (millest kõik teised päritud on), võimaldab üldistamine kompileerida koodi tüübikontrolliga.
Vanas süsteemis tuli näiteks pinust lugedes andmed alati objectiks ja tagasi teisendada. Esiteks kulub sellele aega. Nii käis see vanasti:
Stack stack = new Stack();
stack.Push(1);
int number = (int)stack.Pop();
Teine ja tõsisem probleem on tüübiteisenduse ebaturvalisus, sest selline asi oleks kompileeritav, kuid annaks käivitamisel vea:
Stack stack = new Stack();
stack.Push("kits");
int number = (int)stack.Pop();
Neid probleeme saaks vältida isetehtud spetsiaalsete klassidega, mis aga nõuavad mõttetult aega ja kulutusi.
Niisiis üldistamine
Üldistamisel lisatakse klassi või meetodi definitsioonile nurksulgudes <T> info tüüpi tähistava muutujaga. Klassi või meetodit kasutades tuleb selle asemele panna tüübitähis. See objekt luuakse juba õigele tüübile ja mingeid teisendamisi pole vaja:
Stack stack = new Stack();
stack.Push(1);
int number = stack.Pop();
public class Program
{
public static void Main()
{
Stack<int> stack = new Stack<int>();
stack.Push(1);
int number = stack.Pop();
}
}Asja ilu on selles, et klassi defineerides ei pea üldse teadma, mis tüübi jaoks seda hiljem kasutatakse. Sama hästi võib asemel olla või miks mitte ka mõni ise defineeritud klass.
Kus vajalik?
Võib-olla ei tundu see võimalus esmapilgul kuigi vajalik, kuid eelkõige leiab see kasutamist just andmehulkadega töötamisel. See võimaldab luua kõiksugu loendite ja pinude klasse, millele pole üldse oluline andmete tegelik tüüp.
Seepärast ongi Silverlightist täiesti välja jäetud vanad System.Collections nimeruumi klassid – sest neid pole kellelegi vaja – ja selle asemel on nimeruumis System.Collections.Generic olemas kõik tarvilik, nagu Comparer, Dictionary, HashSet, List, Queue ning Stack. Ja mõned veel, millest tuleb juttu eraldi peatükis.
Näiteks vana süsteemi pärandisse kuuluv klass Convert sisaldab tohutul hulgal meetodeid nagu näiteks ToBoolean(string value), ToByte(string value) jne, nii et kaetud on kõik sisendite ja väljundite võimalused. Seda üldistamise abil tehes võik kirjutada lihtsalt ühe meetodi To<T>(string value).
Vaatleme näitena punkti definitsiooni, kus koordinaatide andmevormi võib kasutaja ise otsustada:
public class Punkt<T>
{
public T X, Y;
}
public static void Main()
{
Punkt<double> pd = new Punkt<double>{ X = 1.5, Y = 2.3};
Punkt<long> pl = new Punkt<long>{ X = 0x2345AF2, Y = 0xBC329870};
}
public class Program
{
public class Punkt<T>
{
public T X, Y;
}
public static void Main()
{
Punkt<double> pd = new Punkt<double>{ X = 1.5, Y = 2.3};
Punkt<long> pl = new Punkt<long>{ X = 0x2345AF2, Y = 0xBC329870};
}
}Kui tüübi muutuja T on klassi definitsioonis, ei pea seda enam lisama meetodite, väljade ja omaduste juures. Ülaltoodud näites saab seda kasutada välja tüübina.
Dictionary<TKey, TValue>
See on tabel, mis koosneb nime ja väärtuse paaridest. Iga nime kohta on võimalik üks väärtus. See on eriti mugav klass ja hea näide üldistamise kasust, kuna nii nime kui väärtusena võib kasutada mistahes tüüpe:
Dictionary<string, int> tabel = new Dictionary<string, int>();
tabel["Mati"] = 5;
tabel["Kati"] = 6;
public class Program
{
public static void Main()
{
Dictionary<string, int> tabel = new Dictionary<string, int>();
tabel["Mati"] = 5;
tabel["Kati"] = 6;
}
}Kui nostalgia vaevab, siis loomulikult on üldistatud klasse võimalik kasutada ka vana ideoloogia järgi: List<object> või Dictionary<object, object> töötab ka. Aga ei soovitaks.
Algus
Peitdeklareerimine
C# on tüübikindel keel, see tähendab, et muutujatel on kindel tüüp. Esialgu tähendas see seda, et kõiki muutujaid ja argumente deklareerides tuli anda ka nende tüüp:
int i = 5;
Tüübikindlus võib JavaScripti või PHP-ga harjunud inimesele tüütu tunduda, kuid teisest küljest takistab see tegemast midagi valesti ja kaitseb ebakvaliteetse koodi eest. Teine aspekt on jõudlus, sest automaatne tüübiteisendus nõuab paratamatult ressursse. Tüübikindel programmeerimine võimaldab olla distsiplineeritum.
Mõned programmeerijad on harjutanud end panema muutuja nimesse alati tema tüübi: dNumber on double ja iNumber on int. C# puhul pole sel mõtet, sest muutuja tüüp on deklaratsioonist nagunii näha.
On siiski olukordi, kus ei ole mõtet tüüpi deklaratsiooni välja kirjutada, sest see oleks mõttetu kordamine. Vaata näiteks seda deklaratsiooni:
System.Collections.Generic.List<System.Collections.Generic.List<int>> list = new System.Collections.Generic.List<System.Collections.Generic.List<int>>();
public class Program
{
public static void Main()
{
System.Collections.Generic.List<System.Collections.Generic.List<int>> list =
new System.Collections.Generic.List<System.Collections.Generic.List<int>>();
Console.WriteLine(list.GetType());
}
}Siin tuleb appi var, mis deklareerib tüübikindla muutuja, sätestamata deklaratsioonis tema tüüpi. Muutuja tüüp selgub tema väärtusest:
var list = new System.Collections.Generic.List<System.Collections.Generic.List<int>>();
list = "tekst";
public class Program
{
public static void Main()
{
var list = new System.Collections.Generic.List<System.Collections.Generic.List<int>>();
list = "tekst";
}
}See ei tähenda, et muutujale võiks anda teist tüüpi väärtuse ja siis muutuja tüüp muutub. Ei, muutuja jääb selleks, kes ta on. Ehk et eeltoodud näide ei kompileeru.
Sellist deklareerimist nimetatakse peitdeklareerimiseks (implicit declaration), sest tüüpi ei ole otse näidatud. Deklaratsioon, milles muutuja tüüp on deklaratsioonis öeldud, on otsedeklaratsioon (explicit declaration).
Anonüümne tüüp
Anonüümne tüüp on tüüp nagu iga teinegi, ainult et tal puudub nimi. Seda tüüpi muutuja luuakse ilma seda eelnevalt deklareerimata. Kuna tal puudub nimi, ei ole võimalik muutuja tüüpi deklareerida.
var m = new { Nimi = "Part", Kaal = 3.5 };
Console.WriteLine(m.Nimi + ": " + m.Kaal + " kg");
public class Program
{
public static void Main()
{
var m = new { Nimi = "Part", Kaal = 3.5 };
Console.WriteLine(m.Nimi + ": " + m.Kaal + " kg");
}
}Kompilaator teab, mis tüübiga on tegemist, ja ka sina tead, aga sellele pole nime antud. Siin ei olegi muud võimalust kui kasutada muutuja deklareerimiseks tüübi asemel var.
Argumendid ja var
var-iga ei saa deklareerida meetodi argumente, mis on ka loogiline. Ehk et
string ToString(var m)
pole võimalik, kuna kompilaator ei saa kuskilt teada, mis tüüpi see m on. Samuti pole võimalik defineerida var-tüüpi omadusi.
Tüübi määramine konstandile
Kui tüüpi pole defineeritud, on arvude juures sellegi poolest oluline, mis tüüpi ta on. Täisarv on vaikimisi int, komakohaga arv on vaikimisi double. Kui arvul on taga M, tekitatakse decimal, kui L, siis long:
var i = 1;
Console.WriteLine(i.GetType());
var d = 1.0;
Console.WriteLine(d.GetType());
var m = 1M;
Console.WriteLine(m.GetType());
var l = 1L;
Console.WriteLine(l.GetType());
public class Program
{
public static void Main()
{
var i = 1;
Console.WriteLine(i.GetType());
var d = 1.0;
Console.WriteLine(d.GetType());
var m = 1M;
Console.WriteLine(m.GetType());
var l = 1L;
Console.WriteLine(l.GetType());
}
}Sama tulemus oleks kui kasutada tüübiteisendust, näiteks var d = (double)1, aga sel juhul loodaks tegelikult int, mis teisendatakse hiljem double-tüüpi (ressursi kulu).
Aga Object?
Absoluutselt kõik tüübid C#-keeles on päritud tüübist object. Alltoodu töötab ilusti:
static void Print(object o)
{
Console.WriteLine(o.GetType());
}
public static void Main()
{
Print(1);
Print(1.0);
var m = 1M;
Print(m);
}
public class Program
{
static void Print(object o)
{
Console.WriteLine(o.GetType());
}
public static void Main()
{
Print(1);
Print(1.0);
var m = 1M;
Print(m);
}
}Miks seda ei võiks siis kasutada? Kuigi object sobib igale poole, kaob sellega tüübikontroll ehk tüübikindlus. Keeles, kus tüübikindlus on standardiks, oleks selline lähenemine ohtlik (suurem võimalus eksida). Tehtud vead ei avalduks kompileerimisel, küll aga ilmneksid kasutamisel (aga seda me ju ei taha).
Tüübi object kasutamine on aga vahel lihtsam ja vahel omal kohal. Näiteks on see kohane meetodite puhul, mis on defineeritud objectis (ja on seega olemas absoluutselt kõigil tüüpidel), nagu ToString() ja GetType(), nagu ülaltoodud näites näha.
Algus
Tekst
C# on moodne keel ja sellesse on sisse ehitatud tekstide Unicode-tugi. See tähendab, et täpitähed on loomulikud igal pool, kaasa arvatud programmis endas. Tsekka seda näidet, mis kompileerub ja töötab:
var käru = 1;
var зверь = 2;
var 粤粵 = 3;
Console.WriteLine(käru + зверь + 粤粵);
public class Program
{
public static void Main()
{
var käru = 1;
var зверь = 2;
var 粤粵 = 3;
Console.WriteLine(käru + зверь + 粤粵);
}
}See ei tähenda, et nii peaks kirjutama, aga see tähendab kindlasti, et sellised muutujate nimed nagu ymberm66t on ajast ja arust.
Kuna tegemist on Unicode'iga, tähendab see seda, et C#-keele char on hoopis teine asi kui C-keele char. C#-keeles on väga selge vahe teksti ja baitide vahel, samas kui C-keeles on char lihtsalt üks bait. Selleks, et tekstist baite teha ja vastupidi, tulevad C#-keeles mängu kodeeringud.
Kodeering
Kodeeringut ei ole vaja, kui käsitled string- ja char-tüüpi muutujaid (string ehk sõne on tekst, samas kui char on tähemärk). Kodeeringut läheb vaja teksti kirjutamisel ja lugemisel baitidest (voogudest, failidest).
Kodeeringuid on palju erinevaid ja olulisemad neist on klassis System.Text.Encoding.
Eesti keele jaoks oleks soovitatav kasutada kodeeringut UTF8, mis kulutab mittetäpitähtedele 1 baidi. Ja kuna täpitähti tekstis väga palju ei ole, ei kasva faili suurus märkimisväärselt.
Vaata seda näidet, mis teeb kolmest eri keeles tekstist baidid ja väljastab nende pikkuse:
Console.WriteLine(Encoding.UTF8.GetBytes("käru").Length);
Console.WriteLine(Encoding.UTF8.GetBytes("зверь").Length);
Console.WriteLine(Encoding.UTF8.GetBytes("粤粵").Length);
public class Program
{
public static void Main()
{
Console.WriteLine(Encoding.UTF8.GetBytes("käru").Length);
Console.WriteLine(Encoding.UTF8.GetBytes("зверь").Length);
Console.WriteLine(Encoding.UTF8.GetBytes("粤粵").Length);
}
}Näeme, et eestikeelne tekst võtab 5 baiti (3 mittetäpitähte pluss 2 baiti täpitähele). Venekeelne tekst võtab 10 baiti (2 baiti igale tähele) ja hiinakeelne võtab 6 baiti (3 baiti igale hieroglüüfile).
Teine näide kasutab kodeeringut Unicode (UTF16), mis võtab ühtlaselt palju ruumi (2 baiti) kõigi tähemärkide jaoks:
Console.WriteLine(Encoding.Unicode.GetBytes("käru").Length);
Console.WriteLine(Encoding.Unicode.GetBytes("зверь").Length);
Console.WriteLine(Encoding.Unicode.GetBytes("粤粵").Length);
public class Program
{
public static void Main()
{
Console.WriteLine(Encoding.Unicode.GetBytes("käru").Length);
Console.WriteLine(Encoding.Unicode.GetBytes("зверь").Length);
Console.WriteLine(Encoding.Unicode.GetBytes("粤粵").Length);
}
}Kui selle käivitad, siis näed, et eestikeelne võtab 8 baiti, venekeelne 10 baiti ja hiinakeelne 4 baiti. Eestikeelsele tekstile pole see järelikult otstarbekas (sest meil niipalju täpitähti ei ole), venekeelsel pole mingit vahet, aga hiinakeelsele on mõttekas (sest UTF8-kodeeringus on igale hieroglüüfile vaja lisabaiti). Siit moraal: vali kodeering vastavalt kasutatud keelele. UTF-kodeeringud on lollikindel valik, sest oskavad edastada kõiki tähemärke.
Kodeeritud baitide lugemine käib sama loogikaga nagu kirjutamine. Sa pead ise teadma, mis kodeeringus fail on, automaatikat pole tehtud.
byte[] bb = Encoding.Unicode.GetBytes("зверь");
Console.WriteLine(Encoding.Unicode.GetString(bb, 0, bb.Length));
public class Program
{
public static void Main()
{
byte[] bb = Encoding.Unicode.GetBytes("зверь");
Console.WriteLine(Encoding.Unicode.GetString(bb, 0, bb.Length));
}
}UTF8-kodeeringus teksti on võimelised sööma ka veebibrauserid. Selleks kirjuta päisesse Content-Type väärtus text/html; charset=utf-8.
Kodeering on võimalik kaasa anda kõigis tekstist ja tekstiks konverteerivates meetodites. Näiteks baidivoogu kirjutamiseks on klass System.IO.StreamWriter. Tekstifaili lugemiseks on meetod System.IO.File.ReadAllText. Kõik need võtavad valikulise argumendina ka kodeeringu. Tasub olla tähelepanelik ja sisestada õige kodeering.
Konstandid
Tekstikonstantide sisestamiseks programmi on järgmised võimalused:
Tähemärk on alati ühekordsete jutumärkide vahel 'a'. Erimärgid antakse stiilis '\t' (tab).
Tekst ehk sõne antakse topeltjutumärkide vahel: "tekst". Erimärgid samamoodi tagurpidi kaldkriipsudega. Näiteks reavahe sisestatakse nii: "esimene rida\r\nteine rida" ja tagurpidi kaldkriips on "\\".
Erimärkideta teksti konstant algab sümboliga @ ja see ei saa sisaldada tagurpidi kaldkriipsuga erimärke. See on kasulik näiteks failitee kirjutamisel (kuna tagurpidi kaldkriipsud ongi tagurpidi kaldkriipsud): @"C:\Windows\System32\drivers\ets\hosts". Samuti võimaldab see sisestada reavahesid:
@"esimene rida
teine rida"
Teksti muutmine
String ehk sõne ei ole pärast koostamist tähemärgi kaupa muudetav. Alltoodud koodinäidises on teine rida vigane.
string s = "зверь";
s[1] = 'v';
Console.WriteLine(s[1]);
public class Program
{
public static void Main()
{
string s = "зверь";
s[1] = 'v';
Console.WriteLine(s[1]);
}
}Selleks, et sõnele ühe tähe kaupa ligi pääseda, tuleb sellest teha tähemassiiv (char[]):
char[] s = "зверь".ToArray();
s[1] = 'v';
Console.WriteLine(s[1]);
public class Program
{
public static void Main()
{
char[] s = "зверь".ToArray();
s[1] = 'v';
Console.WriteLine(s[1]);
}
}Sõne võimaldab teksti lisamist tavalise liitmistehtega:
string s = "Tere";
s += " hommikust";
Console.WriteLine(s);
public class Program
{
public static void Main()
{
string s = "Tere";
s += " hommikust";
Console.WriteLine(s);
}
}Nagu enamuses keeltest, on ka C#-s sõnele teksti lisamine teatud määral ebaefektiivne. Suuremate protseduuride jaoks on tehtud StringBuilder, millel on meetodid Append ja AppendLine.
Proovi seda koodi, mis kasutab teksti lisamiseks kahte erinevat moodust ja mõõdab aega:
DateTime start = DateTime.Now;
string s = "Tere";
for (int i = 0; i < 10000; i++)
s += " hommikust";
Console.WriteLine((DateTime.Now - start).TotalMilliseconds);
start = DateTime.Now;
StringBuilder sb = new StringBuilder("Tere");
for (int i = 0; i < 10000; i++)
sb.Append(" hommikust");
Console.WriteLine((DateTime.Now - start).TotalMilliseconds);
public class Program
{
public static void Main()
{
DateTime start = DateTime.Now;
string s = "Tere";
for (int i = 0; i < 10000; i++)
s += " hommikust";
Console.WriteLine((DateTime.Now - start).TotalMilliseconds);
start = DateTime.Now;
StringBuilder sb = new StringBuilder("Tere");
for (int i = 0; i < 10000; i++)
sb.Append(" hommikust");
Console.WriteLine((DateTime.Now - start).TotalMilliseconds);
}
}Esimesele variandile kulub lubamatult palju aega (minu arvutis natuke üle sekundi). Teine variant on megakiire. Tuleb arvestada, et mõningane aeg kulub StringBuilderi loomisele ja pärast sellest teksti kättesaamisele, seega on kuskil mingi mõistlik piir, millest alates selle kasutamine ära tasub.
Algus
Tühiväärtus
Ingliskeelne termin null viitab tühiväärtusele, samas kui eestikeelne null on inglise keeles zero. Mis vahe neil on? Süsteemi sees ei tarvitsegi alati vahet olla, näiteks viit võib olla täisarv, mis viitab mäluaadressile, ja kui see on 0, siis ta ei viita millelegi. Samas, tühi muutuja ei ole sama mis omistamata (unassigned) muutuja. Vaatame neid ühekaupa.
Omistamata muutuja
Alltoodud näide annab kompileerimisel veateate "Use of unassigned local variable". Miks? Seepärast, et muutuja on defineeritud, kuid talle pole omistatud ühtegi väärtust ega ka mitte öeldud, et ta on tühi.
public static void Main()
{
string s;
Console.WriteLine(s);
}
public class Program
{
public static void Main()
{
string s;
Console.WriteLine(s);
}
}C# jätab muutuja spetsiaalselt omistamata ja ei luba seda kasutada, et vältida programmeerimisvigu. Muutuja on omistamata, kui ta on defineeritud meetodi või omaduse sees. Klassi väljad saavad automaatselt omistatud vaikeväärtusega.
Vaikeväärtus
Igal objektil on mingi vaikeväärtus. Objekte on kahte sorti: need, millel võib olla tühiväärtus ja need, millel tühiväärtust ei saa olla. struct- ja enum-tüüpi objektid ei saa olla tühiväärtusega, samas kui class-tüüpi objektidel saab olla tühiväärtus. Mingi vaikeväärtus on siiski kõigil.
Vaata alltoodud näidet. Me defineerime ühe classi ja ühe structi. Mõlemad on klassi Program väljad, seega omistatakse neile vaikeväärtus. Esimese vaikeväärtus on null (tühi), teisel ei ole tühiväärtus võimalik:
public class Program
{
class Klass
{
}
struct Strukt
{
}
static Klass klass;
static Strukt strukt;
public static void Main()
{
Console.WriteLine(klass);
Console.WriteLine(strukt);
}
}
public class Program
{
class Klass
{
}
struct Strukt
{
}
static Klass klass;
static Strukt strukt;
public static void Main()
{
Console.WriteLine(klass);
Console.WriteLine(strukt);
}
}Tühiväärtuseta on näiteks int, double, DateTime, TimeSpan. Nende vaikeväärtused on vastavalt 0, 0.0, DateTime.MinValue ja TimeSpan.Zero.
Mis füüsiline vahe neil siis tegelikkuses on? Vahe on selles, et muutujad, millel võib olla tühiväärtus, on tegelikult viidad mäluaadressile (reference), kuhu salvestatakse info. Tühiväärtus tähendab, et viit on olemas, aga viit ei viita millelegi (mälu pole hõivatud).
Objektid, millel tühiväärtust ei saa olla, sisaldavad väärtust iseendas (value), viitamata millelegi. Mis eelis sellel on? Ruumi kokkuhoid. Kui meil on täisarv, siis selle kasutamine üle viida tähendaks kahekordset mälukasutust, samas kui väärtuse võiks salvestada ju otse muutujasse.
Tühiväärtus
string-tüüpi muutuja võib olla 1) tühiväärtusega või 2) ilma ühegi tähemärgita tekst.
Esimene tähendab, et objekti pole loodud. Muutujale on väärtus omistatud, kuid see väärtus on tühi (null).
Teine tähendab, et objekt on loodud, kuid teksti pikkus on 0.
string a = null;
string b = "";
Console.WriteLine(a);
Console.WriteLine(b);
Console.WriteLine(a.Length);
Console.WriteLine(b.Length);
public class Program
{
public static void Main()
{
string a = null;
string b = "";
Console.WriteLine(a);
Console.WriteLine(b);
Console.WriteLine(a.Length);
Console.WriteLine(b.Length);
}
}Ülaltoodud näites on a tühiväärtusega, samas kui b on ühegi tähemärgita tekst. Seega, b.Length väljastab väärtuseks 0, aga a.Length tekitab vea "Object reference not set to an instance of an object" (objekti viide ei viita eksemplarile).
stringil ongi selle jaoks olemas omadus IsNullOrEmpty, mis testib mõlemat juhtumit (ülaltoodud näites tagastaks nii string.IsNullOrEmpty(a) kui ka string.IsNullOrEmpty(b) true).
Tühiväärtustatav muutuja
C# võimaldab luua tühiväärtustatavaid muutujaid sellistest tüüpidest, mis muidu seda ei võimalda. Kui int-muutuja deklareerida int?, võib see sisaldada ka väärtust null, ja selle salvestamine käib üle viida. Tühiväärtuse korral ei viita viit millelegi, aga näiteks väärtuse 0 korral viitab viit mälupesale, milles on väärtus 0. Kuna see võtab kaks korda rohkem mälumahtu, on see õigustatud ainult vajadusel.
Tühiväärtustatavat muutujat ei lubata turvakaalutlustel sellisena otse kasutada. Sul tuleb kasutada omadust Value, kontrollides eelnevalt, kas väärtus on olemas (i != null), või siis kasutada muutujat operaatoriga ??, mis annab väärtuse puudumisel vaikeväärtuse (i ?? 0).
Vaata seda näidet:
int? i = null;
if (i == null)
Console.WriteLine("null");
else
Console.WriteLine(i.Value);
Console.WriteLine(i ?? 0);
public class Program
{
public static void Main()
{
int? i = null;
if (i == null)
Console.WriteLine("null");
else
Console.WriteLine(i.Value);
Console.WriteLine(i ?? 0);
}
}Tühiväärtustatava muutujaga, millel on tühiväärtus, opereerimisel tagastatakse tühiväärtus (sest viita ei eksisteeri ja tehe ebaõnnestub). Proovi seda:
int? i = null;
i += 1;
if (i == null)
Console.WriteLine("null");
else
Console.WriteLine(i.Value);
public class Program
{
public static void Main()
{
int? i = null;
i += 1;
if (i == null)
Console.WriteLine("null");
else
Console.WriteLine(i.Value);
}
}Tühiväärtustatavat muutujat ei saa otse teisendada. Seetõttu järgmine näide ebaõnnestub:
int? i = null;
int j = (int)i;
public class Program
{
public static void Main()
{
int? i = null;
int j = (int)i;
Console.WriteLine(j);
}
}Teisendada tuleb nii, et antud on vaikeväärtus juhuks, kui tegemist on tühiväärtusega:
int? i = null;
int j = i ?? 0;
public class Program
{
public static void Main()
{
int? i = null;
int j = i ?? 0;
Console.WriteLine(j);
}
}See kehtib ka näiteks juhul, kui sul on muutuja bool?. Sa ei saa kasutada teda samamoodi nagu ilma küsimärgita kahendmuutujat, sest ta pole otse teisendatav. Seega, alljärgnev näide ei kompileeru:
bool? b = true;
if (b)
Console.WriteLine("jah");
public class Program
{
public static void Main()
{
bool? b = true;
if (b)
Console.WriteLine("jah");
}
}Sa pead tegema seda nii:
if (b == true)
Console.WriteLine("jah");
public class Program
{
public static void Main()
{
bool? b = true;
if (b == true)
Console.WriteLine("jah");
}
}Muide, operaator ?? toimib ka viitade puhul:
string s = null;
Console.WriteLine(s ?? "tühi");
public class Program
{
public static void Main()
{
string s = null;
Console.WriteLine(s ?? "tühi");
}
}Kokkuvõte
Tühiväärtuse olemust on väga oluline mõista. See on sarnane SQL-andmebaasi NULL-väärtusega.
Tühiväärtustatavate muutujate kasutamine ei ole kuigi levinud, sest kulutab ressursse, kuid mõnel juhul on see võimalus hea, sest annab muutujale ühe lisaoleku. Näiteks CheckBoxil võib IsChecked (bool?) olla true, false või null (osaliselt tsekitud). Täisarvude korral võib vaja olla, et legaalne on kogu skaala, kaasa arvatud 0, ja samas on vaja ka tühiväärtust.
Algus
Dünaamilised muutujad
Kas see muudab kõik, mis C# seni on olnud? Nimelt on C# olnud tüübikindel keel tüübikindlate muutujatega, mis tähendab, et muutujale tuleb deklareerida tüüp ja pärast deklareerimist seda muuta ei saa.
Muutuja, mis on deklareeritud tüübiga dynamic, muudab automaatselt oma tüüpi just nagu JavaScripti muutuja. Kusjuures kompilaator lubab selle peal kasutada suvalisi meetodeid, kontrollimata, kas need eksisteerivad.
Tsekka seda näidet:
dynamic i = 1;
Console.WriteLine(i.GetType());
i = "Väärtus on " + i;
Console.WriteLine(i.GetType());
public class Program
{
public static void Main()
{
dynamic i = 1;
Console.WriteLine(i.GetType());
i = "Väärtus on " + i;
Console.WriteLine(i.GetType());
}
}Kas võikski edaspidi deklareerida kõik muutujad kui dynamic ja nautida vabadust? Igasuguse vabadusega kaasneb vastutus. Antud juhul tähendab see seda, et kompilaator enam ei vastuta, programmeerija vastutab oma lolluste eest ise. Vaata järgmist lollust, mille eest kompilaator vastutaks, kui tüüp ei oleks dynamic:
dynamic i = 1;
Console.WriteLine(i.Substring(1));
public class Program
{
public static void Main()
{
dynamic i = 1;
Console.WriteLine(i.Substring(1));
}
}Nüüd aga, ülaltoodud näite puhul, saab veateate mitte programmeerija, vaid kasutaja. Mis tähendab, et klient võtab ühendust klienditoega, nad uurivad ja katsetavad seda asja pool tundi, koostavad vea esiletoomise kirjelduse, saadavad arendusosakonda, mis määrab selle programmeerijale, kes uurib asja ja parandab vea. Kogu asi läheb firmale maksma umbes 300 eurot pluss kliendi rahulolu langus. Kui muutuja oleks algusest peale olnud int, oleks programmeerija saanud kompileerimisel vea, otsinud vea üles ja parandanud selle 5 minutiga. Noh, see on must stsenaarium, aga paraku elus neid musti esineb pidevalt.
Kus siis see dynamic kasutatav oleks? Eelkõige sellistes kohtades, kus suheldakse keeltega, mis on ise dünaamilised. Näiteks COM, JavaScript ja HTML DOM. Sa tead, et asi on seal, aga C# ei tea. Noh, tee siis dünaamiline muutuja ja tarbi rõõmuga. Vaatame näidet. Esiteks, näide sellest, et C# ei tea, et asi on seal, ja ei luba kompileerida:
var document = System.Windows.Browser.HtmlPage.Document;
document.title = "Tere hommikust";
public class Test: Grid
{
public Test()
{
Loaded += (sender, e) => (Parent as ChildWindow).Close();
var document = System.Windows.Browser.HtmlPage.Document;
document.title = "Tere hommikust";
}
}See võib olla frustreeriv, onju. Aga sul pole tegelikult vaja teha muud kui panna selle muutuja tüübiks dynamic, ja kompilaator ei vingu enam. Ja asi toimib, sest documentil on tõesti omadus title. Ja me saame seda pealkirja Silverlightist ilma suuremate trikkideta muuta:
dynamic document = System.Windows.Browser.HtmlPage.Document;
document.title = "Tere hommikust";
public class Test: Grid
{
public Test()
{
Loaded += (sender, e) => (Parent as ChildWindow).Close();
dynamic document = System.Windows.Browser.HtmlPage.Document;
document.title = "Tere hommikust";
}
}Kokkuvõtteks võiks ütelda, et tegelikult dynamic ei muuda C# olemust ja ei tohiks muuta ka programmeerijate käitumist. Aga see avab ukse kohtades, kus seni pidi peaga vastu seina jooksma ja imetrikke tegema. Nii et dynamic on väga mõnus ja vajalik vidin.
Algus
Laiendmeetodid
Laiendmeetod (extension method) on olemasoleva klassi võimaluste laiendamiseks loodud meetod, mis käitub samamoodi nagu originaalmeetodid. Ega see vist väga arusaadav ei olnud.
Võtame näite: klass String on süsteemiklass ja seda muuta ei saa. Aga oletame, et sul oleks vaja läbi oma programmi kasutada funktsiooni, mis teisendab selle reaalarvuks double ja tagastab vaikeväärtuse, kui see ei õnnestu. Seejuures peaks funktsioon suutma süüa komakoha tähistajana nii punkti kui ka koma. Seda oleks lihtne teha mingi klassi meetodiga:
public static double StringToDouble(string s, double def = 0)
{
double result = def;
double.TryParse
(
s.Replace(',', '.'),
System.Globalization.NumberStyles.Float,
new System.Globalization.NumberFormatInfo { NumberDecimalSeparator = "." },
out result
);
return result;
}
public static void Main()
{
Console.WriteLine(StringToDouble("1,1"));
}
public class Program
{
public static double StringToDouble(string s, double def = 0)
{
double result = def;
double.TryParse
(
s.Replace(',', '.'),
System.Globalization.NumberStyles.Float,
new System.Globalization.NumberFormatInfo { NumberDecimalSeparator = "." },
out result
);
return result;
}
public static void Main()
{
Console.WriteLine(StringToDouble("1,1"));
}
}Aga siin on üks probleem: see lahendus ei ole väga objektorienteeritud, sest see ei ole seotud kummagi klassiga: ei string ega double ei puutu asjasse. Palju loogilisem oleks teha näiteks string.ToDouble() või double.FromString(). Siin tulebki appi laiendmeetod, mis võimaldab esimest varianti.
Vaata seda:
public static class Program
{
public static double ToDouble(this string s, double def = 0)
{
double result = def;
double.TryParse
(
s.Replace(',', '.'),
System.Globalization.NumberStyles.Float,
new System.Globalization.NumberFormatInfo { NumberDecimalSeparator = "." },
out result
);
return result;
}
public static void Main()
{
Console.WriteLine("1,1".ToDouble());
}
}
public static class Program
{
public static double ToDouble(this string s, double def = 0)
{
double result = def;
double.TryParse
(
s.Replace(',', '.'),
System.Globalization.NumberStyles.Float,
new System.Globalization.NumberFormatInfo { NumberDecimalSeparator = "." },
out result
);
return result;
}
public static void Main()
{
Console.WriteLine("1,1".ToDouble());
}
}Pane tähele järgmisi nõudeid:
- Klass, milles laiendmeetod on defineeritud, peab olema static. Klass võib olla täiesti suvaline ja defineerida laiendmeetodeid mitmetele klassidele.
- Laiendmeetod ise peab olema static.
- Klass, millele laiendmeetod lisatakse, on meetodi esimene parameeter ja selle ees on this. Pane tähele seda osa:
ToDouble(this string s,
Aga kas saaks teha staatilist laiendmeetodit, see tähendab double.FromString()? Ei, paraku mitte. Laiendmeetod on juba staatiline ja võtab esimese parameetrina objekti eksemplari, mille juurde ta kuulub. Sellist laiendmeetodit, mis käituks kui staatiline meetod (ei vajaks objekti eksemplari), keel (vähemalt praegu) ei võimalda.
Arusaadavalt tekitab selline konstruktsioon vea, sest z on tühiväärtus (null).
string z = null;
Console.WriteLine(z.ToDouble());
public static class Program
{
public static double ToDouble(this string s, double def = 0)
{
double result = def;
double.TryParse
(
s.Replace(',', '.'),
System.Globalization.NumberStyles.Float,
new System.Globalization.NumberFormatInfo { NumberDecimalSeparator = "." },
out result
);
return result;
}
public static void Main()
{
string z = null;
Console.WriteLine(z.ToDouble());
}
}Aga tore on, et sellist viga saab vältida, kuna sisemiselt on tegemist staatilise meetodiga. Kui sellesse meetodisse panna tühiväärtuse kontroll, on probleem lahendatud ja asi toimib, isegi kui z = null:
public static double ToDouble(this string s, double def = 0)
{
double result = def;
if (string.IsNullOrEmpty(s))
return result;
double.TryParse
(
s.Replace(',', '.'),
System.Globalization.NumberStyles.Float,
new System.Globalization.NumberFormatInfo { NumberDecimalSeparator = "." },
out result
);
return result;
}
public static void Main()
{
string z = null;
Console.WriteLine(z.ToDouble());
}
public static class Program
{
public static double ToDouble(this string s, double def = 0)
{
double result = def;
if (string.IsNullOrEmpty(s))
return result;
double.TryParse
(
s.Replace(',', '.'),
System.Globalization.NumberStyles.Float,
new System.Globalization.NumberFormatInfo { NumberDecimalSeparator = "." },
out result
);
return result;
}
public static void Main()
{
string z = null;
Console.WriteLine(z.ToDouble());
}
}See on põnev, sest näitab, et laiendmeetod on isegi võimekam kui originaalmeetod.
Põhivõimalused
Suhtlus arvutiga
Enamik programme vajavad andmeid kasutajalt - muul juhul ei teaks ju arvuti, mida meil vaja on. Kui just programmi ainsaks ülesandeks polegi kellaaja teatamine, sest sellisel juhul tõesti piisab vaid programmi enese käivitamisest.
Sisendite võimalused sõltuvad programmi kasutuskohast. Veebis näiteks saame pruukida veebilehel töötavaid graafikakomponente - nuppe, tekstivälju, rippmenüüsid jne. Windows Formide ehk arvutis iseseisvalt (mitte veebi kaudu) töötavate graafiliste rakenduste puhul on tavalised graafikakomponendid suhteliselt sarnased veebis nähtavatega. Vaid mõnevõrra vabamalt pääseb oma komponente juurde tegema ning mitmekülgsemaid võimalusi kasutama. Tekstiakna rakenduste juures piirdub suhtlus arvutiga loetava ja trükitava tekstiga. Lihtsaim dialoogi pidav programm näeb välja järgmine:
public class Sisend
{
public static void Main(string[] arg)
{
Console.WriteLine("Palun eesnimi:");
string eesnimi = Console.ReadLine();
Console.WriteLine("Tere, " + eesnimi);
}
}
public class Sisend
{
public static void Main(string[] arg)
{
Console.WriteLine("Palun eesnimi:");
string eesnimi = Console.ReadLine();
Console.WriteLine("Tere, " + eesnimi);
}
}Esmane "Palun eesnimi" trükitakse välja sarnaselt nagu lihtsaimaski tervitavas programmis. Edasine Console.ReadLine() jääb kasutajalt sisestust ootama. Kõik, mis kasutaja kuni reavahetuseni kirjutab, püütakse kokku üheks tekstiks ning selle saab arvutisse meelde jätta. Märksõnaks ehk muutuja nimeks sai "eesnimi" ning andmetüübiks "string", mis inimkeeli tähendab teksti. Järgmisel real trükitakse tulemus välja. Nõnda sõltub programmi vastus küsimise peale sisestatavast nimest.
Põhivõimalused
Arvutamine
Arvutamine teadupärast arvuti põhitöö - vähemalt arvutustehnika algaastatel. Et siin lahkesti kasutaja antud arve liita/lahutada saaks, tuleb kõigepealt hoolitseda, et need ka arvuti jaoks arvud ja mitte sümbolite jadad oleksid. Kõigepealt annab ReadLine kätte numbriliste sümbolitega teksti. Ning käsklus int.Parse muudab selle arvutuste jaoks kõlbulikuks. Tüüp int (sõnast integer) tähistab täisarvu. Kui on vaja komakohtadega ümber käia, siis sobib selleks tüüp double. Teise arvu puhul on andmete lugemine ning arvuks muundamine ühte käsklusesse kokku pandud. Nii võib ka.
Väljatrüki juures näete kolme looksulgudesse paigutatud arvu. Nõnda on võimalik andmeid trükkides algul määrata ära trükkimise kohad ning alles pärast loetellu kirjutada tegelikud väärtused. Juhul, kui väärtuste arvutamine on pikk (näiteks arv1 * arv2), aitab see programmikoodi pilti selgemana hoida. Muul juhul tuleks hulk pluss- ja jutumärke väljatrüki juurde. Jutumärgid tekstide eristamiseks ning plussmärgid üksikute osade kokku liitmiseks. Samuti on sellisest asukohanumbritega paigutamisest kasu juhul, kui rakendust tõlgitakse. Keele lauseehituste tõttu võib sõnade järjestus lauses muutuda. Selliselt looksulgude vahel olevate arvudega mängides aga saab lihtsamalt tõlkida ilma, et peaks selleks programmikoodis märgatavaid muutusi tegema.
public class Arvutus
{
public static void Main()
{
Console.WriteLine("Esimene arv:");
string tekst1 = Console.ReadLine();
int arv1 = int.Parse(tekst1);
Console.WriteLine("Teine arv:");
int arv2 = int.Parse(Console.ReadLine());
Console.WriteLine("Arvude {0} ja {1} korrutis on {2}", arv1, arv2, arv1 * arv2);
}
}
public class Arvutus
{
public static void Main()
{
Console.WriteLine("Esimene arv:");
string tekst1 = Console.ReadLine();
int arv1 = int.Parse(tekst1);
Console.WriteLine("Teine arv:");
int arv2 = int.Parse(Console.ReadLine());
Console.WriteLine("Arvude {0} ja {1} korrutis on {2}", arv1, arv2, arv1 * arv2);
}
}Ülesandeid
- Küsi kahe inimese nimed ning teata, et täna on nad pinginaabrid
- Küsi ristkülikukujulise toa seinte pikkused ning arvuta põranda pindala
- Leia 30% hinnasoodustusega hinna põhjal alghind
Põhivõimalused
Valikud
Ehk võimalus otsustamiseks, kui on vaja, et programm käituks kord üht-, kord teistmoodi. Allpoololev näide koos väljundiga võiks näidata, kuidas tingimuslause abil tehtud valik toimib.
public class Valik1
{
public static void Main()
{
Console.WriteLine("Palun nimi:");
string eesnimi = Console.ReadLine();
if (eesnimi == "Mari")
Console.WriteLine("Tule homme minu juurde!");
else
Console.WriteLine("Mind pole homme kodus.");
}
}
public class Valik1
{
public static void Main()
{
Console.WriteLine("Palun nimi:");
string eesnimi = Console.ReadLine();
if (eesnimi == "Mari")
Console.WriteLine("Tule homme minu juurde!");
else
Console.WriteLine("Mind pole homme kodus.");
}
}Nagu näha, Jukut külla ei kutsutud. C# juures, nii nagu selle aluseks oleva C-keele puhul kasutatakse võrdlemise juures kahte võrdusmärki. Üks võrdusmärk on omistamine ehk kopeerimine. Arvude puhul saab kasutada ka võrdlusi < ja > ehk suurem kui ja väiksem kui. Näiteks:
if (vanus > 14)
Console.WriteLine("Tuleb osta täispilet");
public class Valik1
{
public static void Main()
{
Console.WriteLine("Palun vanus:");
int vanus = int.Parse(Console.ReadLine());
if (vanus > 14)
Console.WriteLine("Tuleb osta täispilet");
}
}Samuti kehtivad võrdlused >= ja <= ehk suurem või võrdne ning väiksem või võrdne. Kui uurida, kas arv jääb soovitud vahemikku, tuleb järjest panna kaks võrdlust. Näiteks:
if (vanus > 6 && vanus <= 14)
Console.WriteLine("Sinu jaoks on lapsepilet");
public class Valik1
{
public static void Main()
{
Console.WriteLine("Palun vanus:");
int vanus = int.Parse(Console.ReadLine());
if (vanus > 6 && vanus <= 14)
Console.WriteLine("Sinu jaoks on lapsepilet");
}
}Kaks &-märki tähendab, et kogu tingimus on tõene ainult siis, kui mõlemad võrdlused on tõesed. Teistpidi saab ka:
if (vanus < 7 || vanus > 14)
Console.WriteLine("Sulle lapsepilet ei sobi");
public class Valik1
{
public static void Main()
{
Console.WriteLine("Palun vanus:");
int vanus = int.Parse(Console.ReadLine());
if (vanus < 7 || vanus > 14)
Console.WriteLine("Sulle lapsepilet ei sobi");
}
}&&-märke saab lugeda sõnaga "ja", || märke sõnaga "või". Ehk siis: kui vanus on alla seitsme või suurem neljateistkümnest, sel juhul lapsepilet ei sobi.
Kommentaarid
Vahel on kasulik enesele ja teistele selgituseks programmi sisse kommentaare lisada. Teksti sisse kirjutades pannakse kommentaari ette kaks kaldkriipsu. Selliselt kirjutatud teksti pole kompilaatori jaoks olemas. Enesel on aga vahel päris hea meenutada, mida mõne lausega kirja panna taheti.
double summa = kogus * pirnihind; //korrutatakse ühe pirni hinnaga
Kommentaar võib olla ka üle mitme rea. Sellisel juhul pannakse kommentaari algusesse /* ning lõppu */ Siin on näiteks programmi väljund sarnaselt kaldkriips-tärni ning tärn-kaldkriipsu vahele paigutatud - et ka ilma käivitamata võib juba näha, mis konkreetsel juhul tehti.
Lisaks märkmete jätmisele on kommenteerimine ka heaks võimaluseks programmi testimisel ja vigade otsimisel. Kui kuidagi viga üles ei leia, siis saab algul kahtlase lause või lausete ploki vahel tervikuna välja kommenteerida. Seejärel veenduda, et programm ilma selle osata ilusti töötab. Ning edasi võib asuda juba kindlama tundega sellest väljakommenteeritud plokist viga otsima teades, et ta kindlasti seal peab olema. Omakorda saab lauseid ükshaaval võtta kommenteeritud osast välja ja lõpuks nõnda kõik ilusti tööle saada. Aga nüüd näide ise:
public class Program
{
public static void Main()
{
double pirnihind = 1.70;
Console.WriteLine("Mitu pirni ostad?");
double kogus = double.Parse(Console.ReadLine());
double summa = kogus * pirnihind; //korrutatakse ühe pirni hinnaga
Console.WriteLine("Kas kilekotti ka soovid? (jah/ei)");
string vastus = Console.ReadLine();
if (vastus == "jah")
summa = summa + 0.70;
Console.WriteLine("Kogusumma: " + summa);
}
}
Kui soovida üksikutest osadest summat kokku arvutada nagu ülal näites, siis on küllalt tavaline, et iga küsimuse peal otsustatakse, kas ja mida selle juures teha. Siin nagu näha - juhul kui kilekott ostetakse lisaks, siis pannakse hinnale 70 senti juurde.
summa = summa + 0.70
tähendab just seda.
Ülesandeid
- Küsi temperatuur ning teata, kas see on üle kaheksateistkümne kraadi (soovitav toasoojus talvel).
- Küsi inimese pikkus ning teata, kas ta on lühike, keskmine või pikk (piirid pane ise paika)
- Küsi inimeselt pikkus ja sugu ning teata, kas ta on lühike, keskmine või pikk (mitu tingimusplokki võib olla üksteise sees).
- Küsi inimeselt poes eraldi kas ta soovib osta piima, saia, leiba. Löö hinnad kokku ning teata, mis kõik ostetud kraam maksma läheb.
Põhivõimalused
Kordused
Arvutist on enamjaolt kasu siis, kui ta meie eest mõned sellised tööd ära teeb, kus enesel tarvis ükshaaval ja üksluiselt sama toimetust ette võtta. Ühest hinnast paarikümne protsendi maha arvutamisega saab ka käsitsi mõne ajaga hakkama. Kui aga hindu on mitukümmend, siis on päris hea meel, kui arvuti selle töö meie eest ära teeb. Järgnevalt näide, kuidas arvuti vastu tulevale viiele matkajale tere ütleb. Täisarvuline muutuja nimega nr näitab, mitmenda matkaja juures parajasti ollakse. Käskluse while juurde kuuluvat plokki saab korrata. Plokk läbitakse üha uuesti juhul, kui ümarsulgudes olev tingimus on tõene. Et kordusi soovitud arv saaks, on juba programmeerija hoolitseda. Selleks on siin igal korral pärast tervitust käsklus nr = nr + 1, ehk siis suurendatakse matkaja järjekorranumbrit. Kui see suurendamine kogemata tegemata jääks, siis jääkski arvuti igavesti esimest matkajat teretama (proovi järele). Nüüd aga töötav korduste näide:
int nr = 1;
while (nr <= 5)
{
Console.WriteLine("Tere, {0}. matkaja!", nr);
nr = nr + 1;
}
public class Program
{
public static void Main(string[] arg)
{
int nr = 1;
while (nr <= 5)
{
Console.WriteLine("Tere, {0}. matkaja!", nr);
nr = nr + 1;
}
}
}Heal lapsel mitu nime. Ehk ka korduste kirja panekuks on mitu moodust välja mõeldud. Algul näidatud while-tsükkel on kõige universaalsem, selle abil on võimalik iga liiki kordusi kokku panna. Sama tulemuse aga saab mõnevõrra lühemalt kirja panna for-i abil. Levinud kordusskeemi jaoks on välja mõeldud omaette käsklus, kus algul luuakse muutuja ja antakse talle algväärtus; seejärel kontrollitakse, kas järgnevat plokki on vaja täita; lõpuks võetakse ette toiming andmete ettevalmistamiseks uue ploki jaoks. nr++ on sama, mis nr = nr + 1 - ehk siis suurendatakse muutuja väärtust ühe võrra. Näha programminäide:
for (int nr = 1; nr <= 5; nr++)
Console.WriteLine("Tere, {0}. matkaja!", nr);
public class Program
{
public static void Main()
{
for (int nr = 1; nr <= 5; nr++)
Console.WriteLine("Tere, {0}. matkaja!", nr);
}
}Järelkontroll
Nii for-i kui while-i puhul kontrollitakse alati ploki algul, kas seda on vaja täita. Ning kui matkajate arv poleks mitte 5, vaid hoopis 0, siis sellisel juhul ei täideta plokki ainsatki korda, ehk kõik tered jäävad ütlemata. Mõnikord on aga teada, et plokk tuleb kindlasti läbida. Lihtsalt pole teada, kas sama teed tuleb ka teist või kolmandat korda käia.
Tüüpiline näide selle juures on sisestuskontroll. Kui esimene kord toiming õnnestus, pole vaja kasutajalt andmeid uuesti pärida. Juhtus aga äpardus, siis tuleb senikaua jätkata, kuni kõik korras on. Siin küsitakse jälle tunninäitu. Sattus see arusaadavase vahemikku, minnakse rahus uue väärtusega edasi. Kui aga midagi ebaõnnestus, siis on arvuti väga järjekindel uuesti ja uuesti küsima lootuses, et kunagi ka midagi temale sobilikku jagatakse.
public class Program
{
public static void Main()
{
int tund;
do
{
Console.WriteLine("Sisesta tund vahemikus 0-23");
tund = int.Parse(Console.ReadLine());
} while (tund < 0 || tund > 23);
Console.WriteLine("Tubli, sisestasid {0}.", tund);
}
}
Ülesandeid
- Trüki arvude ruudud ühest kahekümneni
- Küsi kasutajalt viis arvu ning väljasta nende summa
- Ütle kasutajale "Osta elevant ära!". Senikaua korda küsimust, kuni kasutaja lõpuks ise kirjutab "elevant".
Põhivõimalused
Korrutustabel
... ehk näide, kuidas eelnevalt vaadatud tarkused ühe programmi sisse kokku panna ning mis selle peale ka midagi tarvilikku teeb.
Algul on näha, kuidas otse programmi käivitamise juures ka mõned andmed sinna kätte anda. Et kui kirjutan
4 5
siis saadakse sellest aru, et soovin korrutustabelit nelja rea ja viie veeruga. Kõik sisendisse kirjutatud sõnad (ka üksik number on arvuti jaoks sõna) pannakse sinna argumentide massiivi ehk jadasse, kust neid järjekorranumbri järgi kätte saab. Andmetüüp string[] tähendabki, et tegemist on stringide ehk sõnade ehk tekstide massiiviga. Kirjutades massiivi järgi .Length, saab teada, mitu elementi selles massiivis on — mis praegusel juhul on võrdne lisatud sõnade arvuga käsureal. Kõik sõnad saab ka ükshaaval järjekorranumbri järgi kätte. Arvestama peab ainult, et sõnu hakatakse lugema numbrist 0. Nii et kui eeldatakse, et tegemist on kahe parameetriga, siis nende kättesaamiseks peame ette andma numbrid 0 ja 1.
Nagu tingimusest on näha: juhul kui argumente pole täpselt kaks, siis kasutatakse vaikimisi ridade ja veergude arvu ning joonistatakse korrutustabel suurusega 10 korda 10.
Tabeli trükkimiseks on kaks for-tsüklit paigutatud üksteise sisse. Selles pole midagi imelikku - iga rea juures trükitakse kõik veerud esimesest kuni viimaseni. Ning selleks, et erinevate numbrite arvuga arvud meie tabelit sassi ei lööks, on väljatrüki juurde vorminguks kirjutatud {0, 5}. Ainsat Console.Write argumenti (järjekorranumbriga 0) trükitakse nõnda, et ta võtaks alati viis kohta.
public class Program
{
public static void Main()
{
Console.WriteLine("Kirjuta ridade ja veergude arv (näiteks 4 5)");
string[] argumendid = Console.ReadLine().Split(' ');
int ridadearv = 10, veergudearv = 10;
if (argumendid.Length == 2)
{
ridadearv = int.Parse(argumendid[0]);
veergudearv = int.Parse(argumendid[1]);
}
for (int rida = 1; rida <= ridadearv; rida++)
{
for (int veerg = 1; veerg <= veergudearv; veerg++)
Console.Write("{0, 5}", rida * veerg); //5 kohta
Console.WriteLine();
}
}
}
public class Program
{
public static void Main()
{
Console.WriteLine("Kirjuta ridade ja veergude arv (näiteks 4 5)");
string[] argumendid = Console.ReadLine().Split(' ');
int ridadearv = 10, veergudearv = 10;
if (argumendid.Length == 2)
{
ridadearv = int.Parse(argumendid[0]);
veergudearv = int.Parse(argumendid[1]);
}
for (int rida = 1; rida <= ridadearv; rida++)
{
for (int veerg = 1; veerg <= veergudearv; veerg++)
Console.Write("{0, 5}", rida * veerg); //5 kohta
Console.WriteLine();
}
}
}Põhivõimalused
Alamprogramm
Nii nagu algul kirjas, nii ka siin tasub meeles pidada, et programmid armastavad paisuda ja paisuda. Seepärast tuleb leida mooduseid, kuidas üha suuremaks kasvavas koodihulgas orienteeruda. Alamprogramm on esmane ja hea vahend koodi sisse uppumise vältimiseks. Lisaks võimaldab ta terviklikke tegevusi eraldi ning mitu korda välja kutsuda. Samuti on ühe alamprogrammi tööd küllalt hea testida. Järgnevalt võimalikult lihtne näide, kuidas omaette tegevuse saab alamprogrammiks välja tuua. Siin on selliseks tegevuseks korrutamine. Luuakse käsklus nimega Korruta, talle antakse ette kaks täisarvu nimedega arv1 ja arv2 ning välja oodatakse sealt ka tulema täisarv.
public class Program
{
static int Korruta(int arv1, int arv2)
{
return arv1 * arv2;
}
public static void Main()
{
int a = 4;
int b = 6;
Console.WriteLine("{0} korda {1} on {2}", a, b, Korruta(a, b));
Console.WriteLine(Korruta(3, 5));
}
}
public class Program
{
static int Korruta(int arv1, int arv2)
{
return arv1 * arv2;
}
public static void Main()
{
int a = 4;
int b = 6;
Console.WriteLine("{0} korda {1} on {2}", a, b, Korruta(a, b));
Console.WriteLine(Korruta(3, 5));
}
}Ülesandeid
- Koosta alamprogramm kahe arvu keskmise leidmiseks
- Koosta alamprogramm etteantud arvu tärnide väljatrükiks. Katseta.
- Küsi inimeselt kolm arvu. Iga arvu puhul joonista vastav kogus tärne ekraanile
Põhivõimalused
Massiivid
Kuna arvuti on mõeldud suure hulga andmetega ümber käimiseks, siis on programmeerimiskeelte juurde mõeldud ka vahendid nende andmehulkadega toimetamiseks.
Kõige lihtsam ja levinum neist on massiiv. Iga elemendi poole saab tema järjekorranumbri abil pöörduda. Algul tuleb määrata, millisest tüübist andmeid massiivi pannakse ning mitu kohta elementide jaoks massiivis on. Järgnevas näites tehakse massiiv kolme täisarvu hoidmiseks. Kusjuures nagu C-programmeerimiskeele sugulastele kombeks on, hakatakse elemente lugema nullist. Nii et kolme massiivielemendi puhul on nende järjekorranumbrid 0, 1 ja 2. Tahtes väärtusi sisse kirjutada või massiivist lugeda, tuleb selleks kirja panna massiivi nimi (praeguse juhul m) ning selle taha kandiliste sulgude sisse järjekorranumber, millise elemendiga suhelda tahetakse.
public class Program
{
public static void Main()
{
int[] m = new int[3];
m[0] = 40;
m[1] = 48;
m[2] = 33;
Console.WriteLine(m[1]);
}
}
public class Program
{
public static void Main()
{
int[] m = new int[3];
m[0] = 40;
m[1] = 48;
m[2] = 33;
Console.WriteLine(m[1]);
}
}Massiivile saab algväärtusi anda ka lihtsamalt:
int[] m = { 40, 48, 33 };
public class Program
{
public static void Main()
{
int[] m = { 40, 48, 33 };
Console.WriteLine(m[1]);
}
}Tsükkel andmete kasutamiseks
Massiivi kõikide elementidega kiiresti suhtlemisel aitab tsükkel. Siin näide, kuidas arvutatakse massiivi elementidest summa. Algul võetakse üks abimuutuja 0-ks ning siis liidetakse kõikide massiivi elementide väärtused sellele muutujale juurde. Avaldis summa += m[i] on pikalt lahti kirjutatuna summa = summa + m[i] ning tähendab just olemasolevale väärtusele otsa liitmist. for-tsükli juures kõigepealt võetakse loendur (sageli kasutatakse tähte i) algul 0-ks, sest nullist hakatakse massiivi elemente lugema. Jätkamistingimuses kontrollitakse, et on veel läbi käimata elemente ehk loendur on väiksem kui massiivi elementide arv (massiivinimi.Length). Pärast iga sammu suurendatakse loendurit (i++). Nõnda ongi summa käes.
int[] m = { 40, 48, 33 };
int summa = 0;
for (int i = 0; i < m.Length; i++)
summa += m[i];
Console.WriteLine(summa);
Massiiv ja alamprogramm
Nagu ennist kirjutatud, saab eraldiseisva toimingu kergesti omaette alamprogrammi tuua. Siin on nõnda eraldatud summa leidmine.
Massiive saab alamprogrammile samuti ette anda nagu tavalisi muutujaid. Lihtsalt andmetüübi taga on kirjas massiivi tunnusena kandilised sulud.
public class Program
{
static int LeiaSumma(int[] mas)
{
int summa = 0;
for (int i = 0; i < mas.Length; i++)
summa += mas[i];
return summa;
}
public static void Main(string[] arg)
{
int[] m = { 40, 48, 33 };
int vastus = LeiaSumma(m);
Console.WriteLine(vastus);
}
}
Algväärtustamine, järjestamine
Kui massiivi elementide väärtused on kohe massiivi loomise ajal teada, siis saab nad loogelistes sulgudes komadega eraldatult kohe sisse kirjutada. Nii saab andmed lihtsalt lühemalt kirja panna.
Kui elemendid on lihtsa võrdlemise teel järjestatavad nagu näiteks täisarvud, siis piisab nende rittaseadmiseks klassi Array käsust Sort.
int[] m = { 40, 48, 33 };
Array.Sort(m);
for (int i = 0; i < m.Length; i++)
Console.WriteLine(m[i]);
Osutid ja koopiad
Kui ühe hariliku täisarvulise muutuja väärtus omistada teisele, siis mõlemas muutujas on koopia samast väärtusest ning toimingud ühe muutujaga teise väärtust ei mõjuta. Massiividega ning tulevikus ka muude objektidega tuleb tähelepanelikum olla. Kui üks massiiv omistada teisele, siis tegelikult kopeeritakse vaid massiivi osuti, mõlema muutuja kaudu pääsetakse ligi tegelikult samadele andmetele.
Nagu järgnevas näites: massiivid m2 ja m näitavad samadele andmetele. Kui ühe muutuja kaudu andmeid muuta, siis muutuvad ka teise muutuja kaudu nähtavad andmed nagu väljatrüki juures paistab. Algselt on massiivi m ja m2 elemendid 40, 48, 33. Pärast massiivi m elemendi number 1 muutmist 32ks, on ka massiivi m2 elemendid muutunud - väärtusteks 40, 32, 33. Nõnda on suurte andmemassiivide juures teise muutuja tegemine andmete juurde pääsemiseks arvuti jaoks kerge ülesanne. Samas aga peab vaatama, et vajalikke andmeid kogemata ettevaatamatult ei muudaks.
int[] m = { 40, 48, 33 };
int[] m2 = m; //Viide samale massiivile
Console.WriteLine(m[1]);
m[1] = 32;
Console.WriteLine(m[1]);
Console.WriteLine(m2[1]);
Kui soovida, et kaks algsetest andmetest pärit massiivi on üksteisest sõltumatud, siis tuleb teha algsest massiivist koopia (kloon).
int[] m = { 40, 48, 33 };
int[] m2 = (int[])m.Clone(); //Andmete koopia
m[1] = 32;
Console.WriteLine(m[1]);
Console.WriteLine(m2[1]);
Pärast kloonimist muutused massiiviga m enam massiivi m2 väärtusi ei mõjuta.
Soovides massiivi tühjendada, aitab klassi Array käsklus Clear, mis täisarvude puhul kirjutab etteantud vahemikus (ehk praegusel juhul kogupikkuses, 0 ja Length-1 vahel) täisarvude puhul väärtusteks nullid.
Array.Clear(m2, 0, m2.Length);
Massiivist andmete otsimiseks sobib käsklus IndexOf. Soovitud elemendi leidumise korral väljastatakse selle elemendi järjekorranumber. Otsitava puudumisel aga -1.
public class Program
{
static void Trüki(int[] mas)
{
for (int i = 0; i < mas.Length; i++)
Console.WriteLine(mas[i]);
Console.WriteLine();
}
public static void Main(string[] arg)
{
int[] m = { 40, 48, 33 };
int[] m2 = m; //Viide samale massiivile
Trüki(m2);
m[1] = 32;
Trüki(m2);
int[] m3 = (int[])m.Clone(); //Andmete koopia
m[1] = 20;
Trüki(m3);
Array.Clear(m3, 0, m3.Length); //Tühjendus
Trüki(m3);
Console.WriteLine(Array.IndexOf(m, 33));
Console.WriteLine(Array.IndexOf(m, 17)); //puuduv element
}
}
Massiiv alamprogrammi parameetrina
Massiivimuutuja omistamisel tekib võimalus kahe muutuja kaudu samadele andmetele ligi pääseda. See võimaldab luua alamprogramme, mis massiivi elementidega midagi peale hakkavad.
Eelnevalt vaadeldud käsklus Sort tõstab massiivis elemendid kasvavasse järjekorda. Siin on näha omatehtud alamprogramm KorrutaKahega, mis massiivi kõikide elementide väärtused kahekordseks suurendab.
public class Program
{
static void Trüki(int[] mas)
{
for (int i = 0; i < mas.Length; i++)
Console.WriteLine(mas[i]);
Console.WriteLine();
}
static void KorrutaKahega(int[] mas)
{
for (int i = 0; i < mas.Length; i++)
mas[i] = mas[i] * 2;
}
public static void Main()
{
int[] m = { 40, 48, 33 };
KorrutaKahega(m);
Trüki(m);
}
}
foreach-tsükkel
Kui on vaja kogumi kõik elemendid läbi käia samas järjestuses nagu need esinevad, aitab all näites paistev foreach-tsükkel. Selle abi saab näiteks summa arvutamise juures pruukida.
int[] m = { 40, 48, 33 };
foreach (int arv in m)
Console.WriteLine(arv);
Mitmemõõtmeline massiiv
Massiivis võib mõõtmeid olla märgatavalt rohkem kui üks. Kahemõõtmelist massiivi saab ette kujutada tabelina, milles on read ja veerud. Kolmemõõtmelise massiivi elemendid oleksid nagu tükid kuubis, mille asukoha saab määrata pikkuse, laiuse ja kõrguse kaudu. Harva läheb vaja enam kui kolme mõõdet - siis on sageli juba otstarbekam ja arusaadavam oma andmestruktuur ehitada.
Kasutamine toimub aga nii, nagu allpool näites näha. Massiivi elementidega saab ümber käia nagu tavaliste muutujatega. foreach-tsükliga saab soovi korral läbi käia ka mitmemõõtmelise massiivi kõik elemendid.
public class Program
{
public static void Main()
{
int[,] m =
{
{40, 48, 33},
{17, 23, 36}
};
Console.WriteLine(m[0, 1]); //48
Console.WriteLine("Mõõdete arv: " + m.Rank);
Console.WriteLine("Ridade arv: " + m.GetLength(0));
Console.WriteLine("Veergude arv: " + m.GetLength(1));
//elemente mõõtmes nr. 1
int summa = 0;
foreach (int arv in m)
summa += arv;
Console.WriteLine("Summa: " + summa);
}
}
Ülesandeid
- Küsi kasutaja käest viis arvu ning väljasta need tagurpidises järjekorras.
- Loo alamprogramm massiivi väärtuste aritmeetilise keskmise leidmiseks. Katseta.
- Loo alamprogramm, mis suurendab kõiki massiivi elemente ühe võrra. Katseta.
- Sorteeri massiiv ning väljasta selle keskmine element.
- Koosta kahemõõtmeline massiiv ning täida korrutustabeli väärtustega. Küsi massiivist kontrollimiseks väärtusi.
Põhivõimalused
Tekst
Teksti koostamise, analüüsimise ja muutmisega puutuvad kokku enamik arvutiprogramme. Järgnevalt mõned näited, mille põhjal saab enamiku vajalikke tekstiga seotud toimetusi korda.
Teksti pikkuse saab kätte muutujast Length. Loetakse kokku kõik tekstis leiduvad sümbolid, kaasa arvatud tühikud.
Tekstist lõigu eraldamiseks sobib Substring. Esimese parameetrina olev arv näitab, mitmendast tähest hakatakse andmeid võtma, teine näitab, mitu tähte võetakse. String ehk sõne algab tähega number 0. Nii lugedes ongi sõna "tuli" algustäht järjekorranumbriga 5.
IndexOf võimaldab tekstis tähte või sõna leida. Leidmise korral väljastatakse leitud jupi algustähe järjekorranumber. Otsitava puudumise tulemusena väljastatakse -1.
public class Program
{
public static void Main()
{
string s = "Juku tuli kooli";
Console.WriteLine("Pikkus: " + s.Length);
Console.WriteLine(s.Substring(5, 4));
Console.WriteLine("'tuli' kohal " + s.IndexOf("tuli"));
}
}
public class Program
{
public static void Main()
{
string s = "Juku tuli kooli";
Console.WriteLine("Pikkus: " + s.Length);
Console.WriteLine(s.Substring(5, 4));
Console.WriteLine("'tuli' kohal " + s.IndexOf("tuli"));
}
}Muutmine
Teksti muutmiseks sobivad käsud Insert ja Remove. Esimene lisab soovitud kohale juurde etteantud teksti. Lausest "Juku tuli kooli" sai üheksandale kohale sõna "vara" lisamisel lause "Juku tuli vara kooli". Remove võimaldab sobivast kohast tähti välja võtta. Tehniliselt vaadates käsud Insert ja Remove ei muuda algses muutujas olevat teksti, vaid luuakse uus tekstiplokk mälus, mille poole on võimalik muutuja kaudu pöörduda. Muutujas s on endiselt "Juku tuli kooli". Vaid s2 ja s3 on uute väärtustega.
Tekstiga ümber käimiseks on ka mitukümmend muud käsklust, millega lähemat tutvust teha saab veebiaadressilt http://msdn2.microsoft.com/en-us/library/system.string_methods.aspx
Levinumatest on siin näha StartsWith alguse kontrolliks, IndexOf teksti otsinguks ja Replace asendamiseks. Aja jooksul läheb aga vaja tõenäoliselt meetodit Compare järjestuse kindlaks tegemisel, EndsWith lõpu kontrollimisel, Trim tühikute eemaldamisel, ToUpper ja ToLower suur- ja väiketähtedeks muutmisel ning ToCharArray juhul, kui soovitakse tervikliku teksti asemel tähemassiivi kasutada (näiteks tähtede massiliseks ümbertõstmiseks).
public class Program
{
public static void Main()
{
string s = "Juku tuli kooli";
Console.WriteLine("Pikkus: " + s.Length);
Console.WriteLine(s.Substring(5, 4));
Console.WriteLine("'tuli' kohal " + s.IndexOf("tuli"));
string s2 = s.Insert(9, " vara");
Console.WriteLine(s2);
string s3 = s.Remove(5, 5); //Kuuendast alates viis tähte
Console.WriteLine(s3);
if (s.StartsWith("Juku"))
Console.WriteLine("Algab Jukuga");
if (s.IndexOf("kala") == -1)
Console.WriteLine("Siin ei ole kala");
Console.WriteLine(s.Replace("tuli", "jooksis"));
}
}
Tükeldamine
Pika teksti osadeks jaotamiseks on mitmetes keeltes olemas vastavad käsud ja objektid. Nii ka siin. Käsuga Split võib olemasoleva teksti määratud sümbolite koha pealt juppideks lõigata. Kõikidest üksikutest tükkidest moodustatakse massiiv. Siin näiteks on eraldajaks koma, mis tähemassiivi üksiku elemendina ette antakse. Aga nagu aimata võib, saab massiivi lisada ka rohkem eraldajaid.
Kui on põhjust massiiv uuesti üheks pikaks tekstiks kokku panna, siis selle tarbeks leiab käskluse Join.
public class Program
{
public static void Main()
{
string s = "Tallinn,Tartu,Narva";
string[] linnad = s.Split(',');
foreach (string linn in linnad)
Console.WriteLine(linn);
Console.WriteLine(String.Join("; ", linnad));
}
}
Ülesandeid
- Trüki inimese nime eelviimane täht
- Teata, kas sisestatud nimi algab A-ga
- Trüki sisestatud nimi välja suurtähtedega
- Teata, kas lauses leidub sõna "ja"
- Asenda olemasolu korral "ja" sõnaga "ning" ja teata asendusest
- Trüki välja lause kõige pikem sõna
Põhivõimalused
Juhuarv
Kui soovida, et arvuti samade algandmete abil erinevalt käituks, tulevad appi juhuarvud. Nende abil saab kasutajale ette anda juhusliku tervituse, muuta soovi järgi pildi värvi, või näiteks kontrollida loodud funktsiooni toimimist mitmesuguste väärtuste juures. Kõigi nende erinevate väljundite aluseks on arvuti poolt loodud juhuarvud. Neid aitab saada klassi Random eksemplar. Reaalarvu saamiseks on käsklus NextDouble. Kui soovida mõnda muud vahemikku kui nullist üheni, tuleb saadud arv lihtsalt soovitud suurusega läbi korrutada. Ühtlase jaotuse asemele normaal- või mõne muu jaotuse saamiseks tuleb mõnevõrra enam vaeva näha — juhul kui see peaks tarvilikuks osutuma.
Täisarv luuakse käsuga Next, andes ette ülempiiri, soovi korral ka alampiiri. Ning sobiva nime, anekdoodi või terviku saamiseks tuleb olemasolevad valitavad objektid paigutada massiivi, leida juhuarv massiivi elementide arvu piires ning võibki sobiva väärtuse välja võtta.
public class Program
{
public static void Main()
{
Random r = new Random();
Console.WriteLine(r.NextDouble()); //Nullist üheni
Console.WriteLine(r.Next(20)); //Täisarv alla 20
Console.WriteLine(r.Next(50, 100)); //Viiekümnest sajani
string[] nimed = { "Juku", "Kati", "Mati" };
Console.WriteLine(nimed[r.Next(nimed.Length)]); //Juhuslik nimi
}
}
public class Program
{
public static void Main()
{
Random r = new Random();
Console.WriteLine(r.NextDouble()); //Nullist üheni
Console.WriteLine(r.Next(20)); //Täisarv alla 20
Console.WriteLine(r.Next(50, 100)); //Viiekümnest sajani
string[] nimed = { "Juku", "Kati", "Mati" };
Console.WriteLine(nimed[r.Next(nimed.Length)]); //Juhuslik nimi
}
}Ülesandeid
- Trüki juhuslike teguritega korrutamisülesanne
- Kontrolli, kas kasutaja pakutud vastus oli õige
- Sõltuvalt vastuse õigsusest lase arvutil pakkuda olemasolevate hulgast valitud kiitev või julgustav kommentaar.
Põhivõimalused
Omaloodud andmestruktuur
Standardandmetüüpe on .NET raamistikus kätte saada palju. Klasside arvu loetakse tuhandetes. Sellegipoolest juhtub oma rakenduste puhul olukordi, kus tuleb toimetada andmetega, mille hoidmiseks mugavat moodust pole olemas. Või siis on keegi kusagil selle küll loonud, aga lihtsalt ei leia üles. Harilike muutujate ja massiivide abil saab küll kõike arvutis ettekujutatavat hoida. Vahel aga on mugavam, kui pidevalt korduvate sarnaste andmete hoidmiseks luuakse eraldi andmetüüp. Siis on teada, et kokku kuuluvad andmed püsivad kindlalt ühes kohas koos ning pole nii suurt muret, et näiteks kahe firma andmed omavahel segamini võiksid minna.
Järgnevas näites kirjeldatakse selliseks omaette andmestruktuuriks punkt tasandil, kaks täisarvulist muutujat asukohti määramas.
struct Punkt
{
public int x;
public int y;
}
Kui edaspidi programmis kirjutatakse
Punkt p1
siis on teada, et p1-nimelisel eksemplaril on olemas x ja y väärtused ning neid on võimalik vaadata ja muuta. struct-iga kirjeldatud andmestruktuuri põhjal loodud muutuja omistamisel teisele sama tüüpi muutujale kopeeritakse väärtused ilusti ära. Nii nagu ühe täisarvulise muutuja väärtuse omistamisel teisele tekib sellest arvust koopia uude mälupiirkonda, nii ka struktuurina loodud Punkti omistamisel teisele Punktile on mälus kaks eraldi ja sõltumatut piirkonda, mõlemal x-i ja y-i teise x-i ja y-iga samasugused. Õieti ei peakski sellist kopeerimist eraldi rõhutama — tundub ju nõnda omistamisel kopeerimine täiesti loomulik. Märkus on toodud lihtsalt tähelepanu äratamiseks. Kui tulevikus ei kasutata andmetüübi loomisel mitte sõna struct, vaid sõna class, siis käitutakse omistamisel mõnevõrra teisiti.
Nüüd aga programmi töö kohta seletus.
Luuakse muutuja nimega p1. Tal on mäluväljad nii x-i kui y-i jaoks:
Väljadele antakse väärtused:
Luuakse muutuja nimega p2. Ka temal on mäluväljad nii x-i kui y-i jaoks. Muutuja p1 x-i väärtus kopeeritakse muutuja p2 x-i väärtuseks ning samuti ka y-iga. Praeguseks on siis mälus kahte kohta kirjutatud kolm ning kahte kohta viis:
Edasi muudetakse esimese punkti x-koordinaat kaheks. Teise oma aga jääb endiselt kolmeks:
Et kui nüüd teise punkti koordinaadid välja trükkida, siis tulevad sealt rõõmsasti 3 ja 5.
Console.WriteLine(p2.x + " " + p2.y);
Töötav kood tervikuna:
struct Punkt
{
public int x;
public int y;
}
public class Program
{
public static void Main()
{
Punkt p1;
p1.x=3;
p1.y=5;
Punkt p2=p1; //Väärtused kopeeritakse
p1.x=2;
Console.WriteLine(p2.x + " " + p2.y);
}
}
struct Punkt
{
public int x;
public int y;
}
public class Program
{
public static void Main()
{
Punkt p1;
p1.x=3;
p1.y=5;
Punkt p2=p1; //Väärtused kopeeritakse
p1.x=2;
Console.WriteLine(p2.x + " " + p2.y);
}
}Punktimassiiv
Oma andmetüüpi on enamasti põhjust luua juhul, kui seda tüüpi andmeid on rohkem kui paar-kolm eksemplari. Suurema hulga samatüübiliste andmete jaoks kasutatakse sageli massiivi. Nii ka omaloodud andmetüübi puhul.
Punkt[] pd = new Punkt[10];
teeb punktidest kümme eksemplari järjenumbritega nullist üheksani. Ning ilusti järjenumbri järgi elemendi poole pöördudes saab sinna nii väärtusi panna kui küsida. Tsükli abil pannakse iga elemendi y väärtuseks tema järjekorranumbri ruut. Ning väljatrükil
Console.WriteLine(pd[4].y);
trükitakse rõõmsasti ekraanile 16:
struct Punkt
{
public int x;
public int y;
}
public class Program
{
public static void Main()
{
Punkt[] pd = new Punkt[10]; //mälu kohe olemas
for (int i = 0; i < 10; i++)
{
pd[i].x = i;
pd[i].y = i * i;
}
Console.WriteLine(pd[4].y);
}
}
struct Punkt
{
public int x;
public int y;
}
public class Program
{
public static void Main()
{
Punkt[] pd = new Punkt[10]; //mälu kohe olemas
for (int i = 0; i < 10; i++)
{
pd[i].x = i;
pd[i].y = i * i;
}
Console.WriteLine(pd[4].y);
}
}Ülesandeid
- Koosta struktuur riidelappide andmete hoidmiseks: pikkus, laius, toon
- Katseta loodud andmetüüpi paari eksemplariga.
- Loo lappidest väike massiiv, algväärtusta juhuarvude abil.
- Trüki välja lappide andmed, mille mõlemad küljepikkused on vähemalt 10 cm.
Objektorienteeritus
Tutvustus
struct-lausega loodud kirjed on mõeldud põhiliselt andmete hoidmiseks ning vajadusel üksikute andmete (nt. sünniaasta abil vanuse) välja arvutamiseks. Toimingud andmetega jäävad enamjaolt kirjest väljapool paikneva programmi ülesandeks. Objektide puhul aga püütakse enamik konkreetse objektitüübiga seotud toiminguid ühise kesta sisse koondada. Piir kirjete ja objektide vahel on mõnevõrra hägune ning mõnes keeles (nt. Java) polegi kirjete jaoks eraldi keelekäsklust olemas. Samas on siiski hea eri lähenemised lahus hoida.
Traditsioonilise objektorienteeritud programmeerimise juures pole eksemplari muutujatele sugugi võimalik otse väljastpoolt ligi saada. Samuti on vaid mõned alamprogrammid teistele objektidele vabalt kasutavad, küllalt palju vahendeid võib olla loodud vaid objekti enese toimimise tarbeks. Selline kapseldamine aitab suuremate programmide puhul järge pidada, et vaid ühe teemaga seotud ning teemadevahelised lõigud üksteist segama ei hakkaks. Lisaks väliste käskude vähemast arvust tulevale lihtsusele lubab muutujate ja alamprogrammide varjamine teiste objektide eest hiljem oma objekti sisemist ülesehitust muuta ilma, et muu programmi osa sellest häiritud saaks. Selline muutmisvõimalus on aga hästi tänuväärne olukorras, kus tegijaid on palju, ja samu objekte kasutatakse tulevikus teisteski programmides.
Järgnevas näites on punkt loodud klassina ehk objektitüübina. Koordinaadid x ja y on privaatse ligipääsuga, neid saab kasutada vaid sama klassi meetodites. Väärtuste küsimiseks on eraldi käsklused GetX ja GetY. Esimese hooga võib tunduda selline väärtuse küsimise peitmine imelik. Aga kui tulevikus näiteks soovitakse teha statistikat, et mitu korda on küsitud x-i ja mitu korda y-i väärtust, siis alamprogrammiga andmete küsimise puhul saab selle loenduse kergesti paika panna. Muutuja puhul aga ei ole nõnda tavaprogrammeerimise vahenditega võimalik. Kuna siin on lubatud punkti asukoha määramine vaid eksemplari loomisel, siis kord antud väärtusi enam muuta ei saa, sest Punkti juures pole lihtsalt vastavat käsklust. Klassiga samanimelist väljastustüübita käsklust nimetatakse konstruktoriks. See käivitatakse vaid üks kord - eksemplari loomisel - ning sinna sisse pannakse tavaliselt algväärtustamisega seotud toimingud. Kui näiteks algväärtustamisel seada sisse piirang, et koordinaatide väärtused peavad jääma nulli ja saja vahele, siis pole võimalik muus piirkonnas punkti luua. Sedasi õnnestub objektina luua küllalt keerukaid süsteeme, mis oskavad "iseenese eest hoolitseda" ning millel saab lihtsalt käsklusi ehk meetodeid välja kutsuda.
class Punkt
{
private int x;
private int y;
public Punkt(int ux, int uy)
{
x = ux;
y = uy;
}
public int GetX()
{
return x;
}
public int GetY()
{
return y;
}
public double KaugusNullist()
{
return Math.Sqrt(x * x + y * y);
}
}
public class Program
{
public static void Main()
{
Punkt p1 = new Punkt(3, 4);
Console.WriteLine(p1.GetX());
Console.WriteLine(p1.KaugusNullist());
}
}
class Punkt
{
private int x;
private int y;
public Punkt(int ux, int uy)
{
x = ux;
y = uy;
}
public int GetX()
{
return x;
}
public int GetY()
{
return y;
}
public double KaugusNullist()
{
return Math.Sqrt(x * x + y * y);
}
}
public class Program
{
public static void Main()
{
Punkt p1 = new Punkt(3, 4);
Console.WriteLine(p1.GetX());
Console.WriteLine(p1.KaugusNullist());
}
}Ülesandeid
- Koosta klass riidelapi andmete hoidmiseks: pikkus, laius, toon
- Lisa käsklus lapi andmete väljatrükiks
- Lisa käsklus lapi pindala arvutamiseks
- Lisa meetod (alamprogramm) lapi poolitamiseks: pikem külg tehakse poole lühemaks.
- Poolitamise meetod lisaks algse lapi poolitamisele väljastab ka uue samasuguse algsest poole väiksema eksemplari.
- Lisa teine poolitusmeetod, kus saab määrata, mitme protsendi peale lõigatakse pikem külg
Objektorienteeritus
Klassimuutuja
Klassi juurde korjatakse võimalusel kokku kõik vastavat tüüpi objektidega tehtavad toimingud ja andmed. Enamasti kuuluvad andmed isendite ehk eksemplaride juurde, kuid mitte alati. Näiteks tüüpiliselt — loodud punktide arvu loendur on küll punktidega sisuliselt seotud, kuid pole sugugi ühe konkreetse punkti omadus. Punktide arv on olemas ka juhul, kui ühtegi reaalset punkti eksemplari pole veel loodud. Sellisel juhul on punktide arv lihtsalt null. Samuti on punktide arv olemas juhul, kui punkte on loodud palju. Lihtsalt sel juhul on loenduri väärtus suurem. Sellise klassi ja mitte isendiga seotud muutuja ette pannakse sõna static.
static int loendur = 0;
Teine asi on punkti järjekorranumber — see on iga punkti juures kindel arv, mis antakse punktile tema loomise hetkel ning mida siinse programmi puhul enam hiljem muuta ei saa.
private int nr;
nr = ++loendur;
tähendab, et kõigepealt suurendatakse loendurit (olemasolevate punktide arvu) ühe võrra ning saadud arv pannakse uue loodud punkti järjekorranumbriks.
Osuti, omistamine
Klassi eksemplaride puhul toimub omistamine teisiti kui struktuuri eksemplaride puhul. Struktuuri juures tehti lihtsalt väljade väärtustest koopia ja edasi elas kumbki eksemplar oma elu teisest sõltumata. Kui nüüd aga teha omistamised
Punkt p1 = new Punkt(3, 4);
Punkt p2 = p1; //Kasutab sama mälupiirkonda
siis on mälus tegelikult koht vaid ühe punkti andmete jaoks, muutujate p1 ning p2 kaudu pääseb lihtsalt ligi samadele andmetele.
Nii et kui ühe muutuja kaudu andmeid muuta:
p1.SetX(7);
siis muutuvad andmed ka teise märksõna alt paistvas vaates.
Ehk siis ükskõik, kas andmete poole pöörduda p1 või p2 kaudu – ikka saan tegelikult samad väärtused.
Console.WriteLine(p2.GetNr() + " " + p2.GetX() + " " + p2.GetY());
trükib välja
1 7 4
ehkki esialgu olid koordinaatideks 3 ja 4 ning p2 kohe deklareerimisel polnud sugugi algne koht esimese punkti andmetele ligi pääsemiseks.
Punkt järjekorranumbriga 2 on alles p3, sest tema loomisel käivitati uuesti Punkti konstruktor – loodi uus eksemplar, mille käigus suurendati loendurit ning anti uued kohad andmete mälus hoidmiseks.
Nüüd siis eelnevate seletuste kood tervikuna.
class Punkt
{
static int loendur = 0;
private int nr;
private int x;
private int y;
public Punkt(int ux, int uy)
{
x = ux;
y = uy;
nr = ++loendur;
}
public int GetX()
{
return x;
}
public int GetY()
{
return y;
}
public int GetNr()
{
return nr;
}
public void SetX(int ux)
{
x = ux;
}
public void SetY(int uy)
{
y = uy;
}
public double KaugusNullist()
{
return Math.Sqrt(x * x + y * y);
}
}
public class Program
{
public static void Main()
{
Punkt p1 = new Punkt(3, 4);
Punkt p2 = p1; //Kasutab sama mälupiirkonda
p1.SetX(7);
Console.WriteLine(p2.GetNr() + " " + p2.GetX() + " " + p2.GetY());
Punkt p3 = new Punkt(77, 32); //Punkt järgmise järjekorranumbriga
Console.WriteLine(p3.GetNr());
}
}
class Punkt
{
static int loendur = 0;
private int nr;
private int x;
private int y;
public Punkt(int ux, int uy)
{
x = ux;
y = uy;
nr = ++loendur;
}
public int GetX()
{
return x;
}
public int GetY()
{
return y;
}
public int GetNr()
{
return nr;
}
public void SetX(int ux)
{
x = ux;
}
public void SetY(int uy)
{
y = uy;
}
public double KaugusNullist()
{
return Math.Sqrt(x * x + y * y);
}
}
public class Program
{
public static void Main()
{
Punkt p1 = new Punkt(3, 4);
Punkt p2 = p1; //Kasutab sama mälupiirkonda
p1.SetX(7);
Console.WriteLine(p2.GetNr() + " " + p2.GetX() + " " + p2.GetY());
Punkt p3 = new Punkt(77, 32); //Punkt järgmise järjekorranumbriga
Console.WriteLine(p3.GetNr());
}
}Punktimassiiv
Nii nagu üksiku muutuja juures, nii ka massiivi puhul tuleb (erinevalt structist) igale uuele klassi eksemplarile new-käsuga mälu anda.
Punkt[] pd = new Punkt[10];
loob vaid kümme mälupesa, mis on võimelised näitama Punkt-tüüpi objektile. Samas punktiobjekte pole selle käsu peale veel ühtegi. Ehk kui keegi sooviks näiteks pd[3] olematu eksemplariga midagi teha, siis antaks veateade.
Alles siis, kui tsüklis luuakse iga ringi juures uus Punkti eksemplar ning pannakse massiivi element sellele näitama, on võimalik iga massiivi elemendiga kui Punkti eksemplariga ringi käia.
for (int i=0; i < pd.Length; i++)
pd[i] = new Punkt(i, i * i);
Näiteks küsida konkreetse elemendi väärtust.
Console.WriteLine(pd[4].GetY());
class Punkt
{
static int loendur = 0;
private int nr;
private int x;
private int y;
public Punkt(int ux, int uy)
{
x = ux;
y = uy;
nr = ++loendur;
}
public int GetX()
{
return x;
}
public int GetY()
{
return y;
}
public int GetNr()
{
return nr;
}
}
public class Program
{
public static void Main()
{
Punkt[] pd = new Punkt[10];
for (int i = 0; i < pd.Length; i++)
pd[i] = new Punkt(i, i * i);
Console.WriteLine(pd[4].GetY());
}
}
class Punkt
{
static int loendur = 0;
private int nr;
private int x;
private int y;
public Punkt(int ux, int uy)
{
x = ux;
y = uy;
nr = ++loendur;
}
public int GetX()
{
return x;
}
public int GetY()
{
return y;
}
public int GetNr()
{
return nr;
}
}
public class Program
{
public static void Main()
{
Punkt[] pd = new Punkt[10];
for (int i = 0; i < pd.Length; i++)
pd[i] = new Punkt(i, i * i);
Console.WriteLine(pd[4].GetY());
}
}Ülesandeid
- Koosta klass riidelapi andmete hoidmiseks: pikkus, laius, toon
- Koosta riidelappidest massiiv.
- Koosta uus lapimassiiv, kuhu pannakse osa eelmisest massiivist pärit lappe ja osa muid lappe (kaltsukott).
- Arvuta mõlema massiivi puhul välja seal leiduvate lappide pindalade summa.
- Koosta riidelapi klassile staatiline käsklus näitamaks loodud riidelappide keskmist pindala. Lappide puudumisel väärtuseks -1. Lisa vajalikud staatilised muutujad (lappide arv, kogupindala)
- Koosta klass isikukoodi andmete hoidmiseks
- Lisa käsklus sünnikuupäeva küsimiseks
- Lisa käsklus sünnikuu küsimiseks sõnana
- Lisa käsklus sünniaasta väljastamiseks ka sajandit arvestades
- Kontrolli isikukoodi objekti tegemisel koodi pikkust
- Kontrolli isikukoodi kontrollisumma õigsust vastavalt järgnevale algoritmile:
Isikukoodi kontrolli algoritm
Isikukoodis peavad kõik sugu ning kuupäeva tähistavad väärtused olema võimalikud ning viimane kontrollnumber arvutatakse järgneva algoritmi järgi: liidetakse kokku esimese üheksa numbri korrutised igale arvule vastava järjekorranumbriga, kümnenda numbri puhul ühega ning leitakse saadud summa jääk jagamisel 11-ga. Kui jääk on võrdne kümnega, siis tehakse arvutus uuesti ning võetakse teguriteks vastavalt 3, 4, 5, 6, 7, 8, 9, 1, 2, 3.
Näide: isikukoodi 37605030299 kontroll. Summa = 1 * 3 + 2 * 7 + 3 * 6 + 4 * 0 + 5 * 5 + 6 * 0 + 7 * 3 + 8 * 0 + 9 * 2 + 1 * 9 = 108. 108 jääk jagamisel 11-ga on 9, järelikult isikukoodi viimane number peab olema üheksa.
Objektorienteeritus
Dokumenteerimine
Dokumentatsiooni loomiseks on üks võimalus panna kolme kaldkriipsuga märgistatud kommentaarid koodi sisse, kommenteeritava ploki või muutuja ette. Sellised kommentaarid oskab kompilaator sobiva võtme järgi välja, eraldi kommentaaride faili korjata. Kommentaariks sobib xml-märgenditega piiratud tekst. Tavalisimaks elemendiks on <summary>, kuid eraldi on olemas ka kokkuleppelised käsklused parameetrite, versioonide jm. jaoks.
Readonly
Võtmesõna readonly muutuja ees tähendab, et sinna võib väärtuse omistada vaid ühe korra. Olgu siis muutuja kirjeldamisel või konstruktoris. Selliste muutujate puhul hoolitseb juba kompilaator, et poleks võimalik kirjutada käsklusi, mis readonlyga kaitstud mäluväljade väärtusi muudavad.
///
/// Tasandi punkti andmete hoidmine
///
class Punkt
{
///
/// Muutuja ainult lugemiseks.
/// Andmed sisestatavad vaid konstruktoris.
///
private readonly int x;
private readonly int y;
///
/// Algandmed punkti loomisel kindlasti vajalikud
///
public Punkt(int ux, int uy)
{
x = ux; y = uy;
}
public int GetX()
{
return x;
}
public int GetY()
{
return y;
}
///
/// Kaugus arvutatakse Pythagorase teoreemi järgi.
///
public double KaugusNullist()
{
return Math.Sqrt(x * x + y * y);
}
}
///
/// Eraldi klass punkti katsetamiseks.
///
public class Program
{
public static void Main(string[] arg)
{
Punkt p1 = new Punkt(3, 4);
Console.WriteLine(p1.KaugusNullist());
}
}
///
/// Tasandi punkti andmete hoidmine
///
class Punkt
{
///
/// Muutuja ainult lugemiseks.
/// Andmed sisestatavad vaid konstruktoris.
///
private readonly int x;
private readonly int y;
///
/// Algandmed punkti loomisel kindlasti vajalikud
///
public Punkt(int ux, int uy)
{
x = ux; y = uy;
}
public int GetX()
{
return x;
}
public int GetY()
{
return y;
}
///
/// Kaugus arvutatakse Pythagorase teoreemi järgi.
///
public double KaugusNullist()
{
return Math.Sqrt(x * x + y * y);
}
}
///
/// Eraldi klass punkti katsetamiseks.
///
public class Program
{
public static void Main(string[] arg)
{
Punkt p1 = new Punkt(3, 4);
Console.WriteLine(p1.KaugusNullist());
}
}
Kompileerimine
Kompileerides saab siis /doc võtmega anda ette failinime, kuhu kirjutatakse kommentaarid.
D:\kodu\0606\opikc#>csc Punktid6.cs /doc:Punktid6.xml
Kommentaarifail
Edasi on juba vastavalt kasutaja soovile - kas ta loeb tekkinud XML-faili lihtsa tekstiredaktoriga, mõne eraldi XML-lugejaga (nt. vastavate võimetega veebibrauser) või siis kirjutab juurde XSL-lehe andmete omale sobivale kujule muundamiseks.
Väike ülevaade tekkinud XML-failist. Esimene rida on ühine kõigile XML-vormingus dokumentidele.
Järgnevaks juurelemendiks on doc, mille sisse kõik ülejäänud andmed mahuvad.
<doc></doc>
Assembly tähendab kokkukompileeritud üksust. Siin on ta laiendiks .exe, mõnel juhul võib olla aga ka .dll
<assembly>
<name>Punktid6</name>
</assembly>
Ning edasi juba tulevadki klassi, muutuja, konstruktori ja meetodi töö kirjeldused.
<?xml version="1.0"?>
<doc>
<assembly>
<name>Punktid6</name>
</assembly>
<members>
<member name="T:Punktid6.Punkt">
<summary>
Tasandi punkti andmete hoidmine
</summary>
</member>
<member name="F:Punktid6.Punkt.x">
<summary>
Muutuja ainult lugemiseks.
Andmed sisestatavad vaid konstruktoris.
</summary>
</member>
<member name="M:Punktid6.Punkt.#ctor(System.Int32,System.Int32)">
<summary>
Algandmed punkti loomisel kindlasti vajalikud
</summary>
</member>
<member name="M:Punktid6.Punkt.KaugusNullist">
<summary>
Kaugus arvutatakse Pythagorase teoreemi j�rgi.
</summary>
</member>
<member name="T:Punktid6.Punktiproov">
<summary>
Eraldi klass punkti katsetamiseks.
</summary>
</member>
</members>
</doc>
Ülesandeid
- Koosta klass riidelapi andmete hoidmiseks, lisa kommentaarid.
- Tutvu kompileerimisel loodud kommentaaride failiga.
Objektorienteeritus
Pärilus
Ajapikku on objektorienteeritud programmeerimiskeelte juurde lisatud mitmesuguseid täiustusi ja võimalusi. Pärilus (inheritance) on mingil moel enamike objektorienteeritud keelte küljes olemas. Nii ka end suhteliselt arenenuks keeleks pidava C# juures.
Objektidega toimetamisel ning pärilusel sealhulgas on vähemalt kaks eesmärki. Püütakse lihtsustada koodi kohandamist. Samuti võimaldab pärilus vältida sama tööd tegeva koodi kopeerimist, lubades samu käsklusi kasutada mitmel pool.
Päriluse abil püütakse programmeerimiskeele objektitüüpide omavahelisi suhteid lähendada inimeste igapäevaselt tajutavale. Üheksakümnendate aastate algul ilmunud objektorienteeritud programmeerimist tutvustavas õpikus oli ilus näide: "Sebra on nagu hobune, kellel on triibud." Sarnane tuttava kaudu uue tüübi kokkupanek ongi päriluse põhisisu. Midagi lisatakse, midagi muudetakse, vahel harva ka kaotatakse. Ning märkamatult muutubki hobune sebraks või tühi paneel tekstiväljaks.
Nõnda õnnestub olemasolevate (pool)valmis tükkide abil omale sobiv komplekt kokku panna. Teiselt poolt võidakse objektide päriluspuu ehitada ka oludes, kus kõik programmi osad on enese määrata. Sellisel juhul pole küll eesmärk omale üks ja parim tüüp kokku panna, vaid otsitakse ühisosi, mille puhul on võimalik valminud tüübid gruppidesse jaotada ning nende gruppidega vajadusel koos midagi ette võtta. Tüüpilise näitena pole teatripiletit müües vaja teada inimese sugu ja ametit. Küll aga on balletirolli juures mõlemad andmed tähtsad. Tavaelus tundub loomulikuna, et eriomadusi arvestatakse vaid kohtades, kus need on vajalikud ning muul juhul aetakse vaid üldiste tunnustega läbi. Haruharva tuleb eraldi rõhutada, et "meie teatrisse tohivad vaatajaks tulla inimesed sõltumata usutunnistusest ja nahavärvist". Arvuti juures tuleb aga tasemed selgelt välja tuua. Igal keerukuse astmel tuleb määrata, millised oskused ja omadused sinna juurde kuuluvad ning millise rolli jaoks millisel tasemel oskuste komplekti vaja on. Keerukaks kiskunud seletuste kõrvale selgitused näidete abil.
Päriluseta näide
Alustame inimese klassiga, kus igasugune pärilus puudub. Üks klass oma muutuja, konstruktori ja meetodiga ning testprogrammis saab tema võimalusi katsetada.
class Inimene
{
protected int vanus;
public Inimene(int v)
{
vanus = v;
}
public void ÜtleVanus()
{
Console.WriteLine("Minu vanus on " + vanus + " aastat");
}
}
public class Program
{
public static void Main()
{
Inimene inim1 = new Inimene(13);
inim1.ÜtleVanus();
}
}
class Inimene
{
protected int vanus;
public Inimene(int v)
{
vanus = v;
}
public void ÜtleVanus()
{
Console.WriteLine("Minu vanus on " + vanus + " aastat");
}
}
public class Program
{
public static void Main()
{
Inimene inim1 = new Inimene(13);
inim1.ÜtleVanus();
}
}Alamklass
Edasi juba juures inimese alamklass (subclass) Modell, kel on olemas kõik inimese omadused (ehk siis selle programmi puhul võime oma vanust öelda), kuid juures käsklus enese esitlemiseks. Et esitlemine koosneb vanuse ja ümbermõõdu teatamisest, on see juba esitlemiskäskluse sisse kirjutatud.
Klass Inimene on Modelli suhtes ülemklassiks (superclass, base class, parent class). C# puhul on igal klassil alati täpselt üks ülemklass. Kui muud pole määratud, siis kasutatakse selleks vaikimisi nimeruumi System klassi Object. Muul juhul aga on ülemklass kirjas klassikirjelduse päriluse osas. Nii tekibki klasside puu, mis algab klassist Object.
Kui klassil konstruktor puudub, siis loob kompilaator vaikimisi tühja konstruktori, millel pole parameetreid ja mis ka midagi ei tee. Inimese puhul siis näiteks
public Inimene(){}
Kui aga vähemalt üks programmeerija loodud konstruktor on olemas, siis seda nähtamatut konstruktorit ei tehta. Päriluse puhul kutsutakse alamklassi eksemplari loomisel alati välja ülemklassi konstruktor. Vaikimisi võtab kompilaator selleks ülemklassi parameetritega konstruktori. Kui see aga puudub või soovitakse käivitada mõnda muud, siis tuleb sobiva konstruktori väljakutse alamklassi juures ära märkida. Siin märgitakse näiteks Modelli loomise juures, et Modelli isendi loomise juures tehakse kõigepealt valmis baasklassi (inimese) isend, kellele siis Modelli enese konstruktoris vajalikud lisandused juurde pannakse. Ülemklassi konstruktori määramine on kohe Modelli konstruktori juures. Pärast koolonit olev base(vanus) ütleb, et kasutatagu inimese puhul seda konstruktorit, kus tuleb täisarvuline vanus kohe ette öelda.
public Modell(int vanus, int ü) : base(vanus)
{
ümbermõõt = ü;
}
Ehkki praegu tegelikult muud võimalust polnudki, tuleb see ikkagi arvutile ette öelda.
class Inimene
{
protected int vanus;
public Inimene(int v)
{
vanus = v;
}
public void ÜtleVanus()
{
Console.WriteLine("Minu vanus on " + vanus + " aastat");
}
}
class Modell : Inimene
{
protected int ümbermõõt;
public Modell(int vanus, int ü)
: base(vanus)
{
ümbermõõt = ü;
}
public void Esitle()
{
ÜtleVanus();
Console.WriteLine("Mu ümbermõõt on " + ümbermõõt + " cm");
}
}
public class Program
{
public static void Main()
{
Modell m = new Modell(20, 90);
m.Esitle();
}
}
class Inimene
{
protected int vanus;
public Inimene(int v)
{
vanus = v;
}
public void ÜtleVanus()
{
Console.WriteLine("Minu vanus on " + vanus + " aastat");
}
}
class Modell : Inimene
{
protected int ümbermõõt;
public Modell(int vanus, int ü)
: base(vanus)
{
ümbermõõt = ü;
}
public void Esitle()
{
ÜtleVanus();
Console.WriteLine("Mu ümbermõõt on " + ümbermõõt + " cm");
}
}
public class Program
{
public static void Main()
{
Modell m = new Modell(20, 90);
m.Esitle();
}
}Ülesandeid
- Lisa Inimesele pikkuse väli.
- Pikkus tuleb sisestada konstruktoris sarnaselt vanusele.
- Modelli käsklus Esitle teatab eraldi lausena ka pikkuse.
- Omista Modell Inimese tüüpi muutujale. Küsi sealtkaudu vanus.
- Koosta inimeste massiiv. Lisa sinna nii Modelle kui tavalisi inimesi. Küsi kõikide vanused.
- Lisa Inimesele int-tagastusväärtusega käsklus pikkuse väljastamiseks.
- Lisa testprogrammi käsklus static bool KasMahubAllveelaeva(Inimene isik), mis väljastab tõese väärtuse juhul, kui pikkust on alla 165 sentimeetri. Katseta käsku nii Inimese kui Modelli puhul, samuti Modellidest ja Inimestest koosneva massiivi juures.
- Väljasta false-vastus ka null-sisendi korral. Allveelaeva luba ka kuni 170 sentimeetri pikkused modellid (painduvad hästi, kontrolliks isik is Modell).
- Loo mõlemale klassile taas ka ilma pikkuseta konstruktor. Sellisel juhul pannakse pikkuse väärtuseks -1 ning esitluse juures teatatakse, et pikkuse andmed puuduvad.
Objektorienteeritus
Ülekate
Mõnikord ei piirduta omaduste lisamisega - tahetakse olemasolevaid võimalusi ka muuta. Tavanäiteks on kujundid või graafikakomponendid, mis igaüks ennast vastavalt oma omadustele joonistavad. Aga sarnaselt üle kaetavad võivad olla voogudesse andmete kirjutamise käsklused või andmestruktuuridesse andmeid lisavad või sealt eemaldavad käsklused nii, et neid käsklusi kasutav programm võib lihtsalt kasutatavat tükki usaldada, et tal on vastava nimega käsklus olemas ning ta selle soovitud ajal sooritab.
Siin näites on Inimese alamklassiks tehtud Daam, kel kõik muud kirjeldatud omadused hariliku Inimesega sarnased. Kuid vanuse ütlemine käib mõnevõrra teisiti. Et käske saaks rahus üle katta, selleks on Inimese juurde käsu ehk meetodi ÜtleVanus ette lisatud sõna virtual, Daami vastava meetodi ette aga override. Selliselt on mõlemat tüüpi objektil vanuse ütlemine selge, need lihtsalt käituvad erinevalt.
class Inimene
{
protected int vanus;
public Inimene(int v)
{
vanus = v;
}
public virtual void ÜtleVanus()
{
Console.WriteLine("Minu vanus on " + vanus + " aastat");
}
}
class Daam : Inimene
{
public Daam(int vanus)
: base(vanus)
{
}
public override void ÜtleVanus()
{
Console.WriteLine("Minu vanus on " + (vanus - 5) + " aastat");
}
}
public class Program
{
public static void Main()
{
Inimene i1 = new Inimene(40);
Inimene i2 = new Daam(40);
i1.ÜtleVanus();
i2.ÜtleVanus();
}
}
class Inimene
{
protected int vanus;
public Inimene(int v)
{
vanus = v;
}
public virtual void ÜtleVanus()
{
Console.WriteLine("Minu vanus on " + vanus + " aastat");
}
}
class Daam : Inimene
{
public Daam(int vanus)
: base(vanus)
{
}
public override void ÜtleVanus()
{
Console.WriteLine("Minu vanus on " + (vanus - 5) + " aastat");
}
}
public class Program
{
public static void Main()
{
Inimene i1 = new Inimene(40);
Inimene i2 = new Daam(40);
i1.ÜtleVanus();
i2.ÜtleVanus();
}
}Ülesandeid
- Lisa Inimesele käsklus "KutsuEttekandja", katseta seda eksemplari juures.
- Katseta sama käsklust ka Daami eksemplari juures.
- Kata nimetatud käsklus Daami juures üle, pannes sisse pidulikum ja peenem tekst. Katseta.
- Loo Inimeste massiiv, kus on nii Inimesed kui Daamid. Palu igaühel teatada oma vanus ning kutsuda ettekandja.
Objektorienteeritus
Liidesed
Nagu eespool kirjeldatud, on C# puhul üks ja kindel objektitüüpide pärinemise puu. Igal klassil on oma ülemklass ning juureks on klass Object nimeruumist System. Samas aga mõnegi tüübi puhul on sellest klassist objekte mugav kasutada tunduvalt rohkemates kohtades, kui otsene päriluspuu ette näeb. Nii nagu Ambla kihelkonna Lehtse valla Läpi küla Troska talu perepoeg Karl oli lisaks nendele ametlikele tiitlitele veel küla sepp, puutöömees ning korralik vanapoiss, nii on hea mõnegi klassi puhul programmi ülesehituse lihtsuse huvides pakkuda sinna rohkem rolle kui otsese päriluse järgi neid kokku tuleks. Näiteks Karlal oli leivateenimise mõttes sepatööst kindlasti rohkem kasu kui teatest, et ta Ambla kirikuraamatus kirjas on. Niisamuti on klassidele võimalik juurde panna liideseid, mis annavad õiguse vastava klassi eksemplare kloonida, järjestada või muid kasulikke omadusi lisada. Liideste arv ühe klassi juures ei ole piiratud. Liidesed mõeldi programmeerimiskeelte juurde välja, kuna selgus, et nõnda kirjeldusi määrates ja kokku leppides õnnestub programmeerimisel vigu vähendada.
Järgnevas näites loodi liides IViisakas. I on liidesel ees sõnast Interface. Liidese juurde käib enamasti sisuline seletus selle kohta, millised seda liidest realiseerivad objektid on. Ning lisaks võivad olla mõned käsklused, millele vastavat liidest realiseerivad objektid on võimelised reageerima. Liidese IViisakas puhul valiti selliseks käskluseks Tervita, mis saab omale tekstilise parameetri. Ehk siis eeldatakse, et iga viisakas tegelane mõistab tervitusele vastata juhul, kui talle öeldakse, kellega on tegemist. Nagu näha - Lapse ja Koera puhul need tervitused on erinevad. Kuid nii nagu liides nõuab - nad on olemas. Sinnamaani suudab kompilaator kontrollida. Ülejäänu on juba programmeerija hoolitseda, et kindlaksmääratud nimega käskluse juurde ka vastavale klassile sobiv sisu saaks.
static void TuleKülla(IViisakas v)
{
v.Tervita("vanaema");
}
Loodud meetodi juures on näha, et parameetrina võetakse vastu vaid nende klasside eksemplare, kelle puhul liides IViisakas on realiseeritud. Ning igaühel neist palutakse tervitada vanaema. Kuidas keegi sellega hakkama saab, on juba klassi enese sees hoolitsetud.
class Inimene
{
protected int vanus;
public Inimene(int v)
{
vanus = v;
}
public virtual void ÜtleVanus()
{
Console.WriteLine("Minu vanus on " + vanus + " aastat");
}
}
interface IViisakas
{
void Tervita(String tuttav);
}
class Laps : Inimene, IViisakas
{
public Laps(int vanus)
: base(vanus)
{
}
public void Tervita(String tuttav)
{
Console.WriteLine("Tere " + tuttav);
}
}
class Koer : IViisakas
{
public void Tervita(String tuttav)
{
Console.WriteLine("Auh!");
}
}
public class Program
{
static void TuleKülla(IViisakas v)
{
v.Tervita("vanaema");
}
public static void Main(string[] arg)
{
Laps juku = new Laps(6);
juku.ÜtleVanus();
Koer muki = new Koer();
TuleKülla(juku);
TuleKülla(muki);
}
}
class Inimene
{
protected int vanus;
public Inimene(int v)
{
vanus = v;
}
public virtual void ÜtleVanus()
{
Console.WriteLine("Minu vanus on " + vanus + " aastat");
}
}
interface IViisakas
{
void Tervita(String tuttav);
}
class Laps : Inimene, IViisakas
{
public Laps(int vanus)
: base(vanus)
{
}
public void Tervita(String tuttav)
{
Console.WriteLine("Tere " + tuttav);
}
}
class Koer : IViisakas
{
public void Tervita(String tuttav)
{
Console.WriteLine("Auh!");
}
}
public class Program
{
static void TuleKülla(IViisakas v)
{
v.Tervita("vanaema");
}
public static void Main(string[] arg)
{
Laps juku = new Laps(6);
juku.ÜtleVanus();
Koer muki = new Koer();
TuleKülla(juku);
TuleKülla(muki);
}
}Kui mingil põhjusel jäänuks Lapsele või Koerale käsklus Tervita lisamata, siis annaks kompilaator veateate. Sama tekiks ka juhul, kui käskluse tekstis trükiviga tehtaks. Selline kompilaatoripoolne kontroll aitab vead üles leida juba enne tegelike andmetega katsetamise alustamist.
Ülesandeid
- Katseta, mis juhtub, kui Lapse tervita kirjutada väikese tähega.
- Lisa liidesesse IViisakas käsklus KoputaUksele.
- Muuda liidest realiseerivaid klasse nii, et kood kompileeruks.
- Testi töö tulemust.
- Koosta liides ISummeerija käsklustega Alusta, Lisa ning KüsiSumma. Esimene siis tühjendab andmed, teine lisab ühe väärtuse ning kolmas väljastab olemasolevate summa.
- Koosta liidest realiseeriv klass, kus on muutuja summa hoidmiseks. Alustamise peale pannakse see nulli, lisamise puhul suurendatakse väärtust ning summa küsimisel väljastatakse meeles olev summa. Omista klassi eksemplar liidese tüüpi muutujale. Katseta.
- Koosta sama liidest realiseerima teine klass, kus lisatavate andmete jaoks on olemas massiiv. Kogu aeg peetakse meeles lisatud väärtuste arv ning väärtused ise. Summa küsimisel arvutatakse kokku elementide väärtuste summa ning väljastatakse see.
- Koosta eraldi peaprogramm, mis eeldab, et seal sees on kasutada ISummeerija liidest realiseeriv objekt – tegelikult aga algul on seal tühiväärtus-null. Peaprogramm koostatakse nõnda, et ta küsib kasutajalt arve ja palub ISummeerijal need kokku liita. Kontrolli, et kood kompileeruks.
- Koosta libasummeerija, mille Alusta ja Lisa-käsklused ei tee midagi. KüsiSumma aga väljastab alati väärtuse -1. Katseta peaprogrammi loodud libaklassi objektiga.
- Loo eraldi klass SummeerijaVabrik summeerijaobjektide tootmiseks. Lisa sinna sisse staatiline käsklus LooSummeerija(int EeldatavKogus). Kui kogus on alla kümne, väljastatakse andmeid salvestav summeerija, muul juhul kohe andmeid kokku liitev summeerija. Katseta süsteemi toimimist.
Objektorienteeritus
Abstraktne klass
Liideseid pidi pärinevad vaid meetodite nimed. Klasside kaudu pärinevad nimed koos sisuga. Aga mõnikord soovitakse vahepealset varianti: et mõnedel meetoditel on olemas sisu, teistel aga veel mitte. Sellisel juhul aitab välja abstraktne klass. Tüüpiline näide on toodud allpool. Sirgete ja püstiste seintega kujundi puhul saab ruumala arvutada juhul, kui on teada põhja pindala ja kõrgus — valemi järgi piisab vaid nende omavahelisest korrutamisest. Kui aga kujundeid on palju, siis on niru seda valemit igale poole uuesti kirjutada. Rääkimata juhtudest, kus valem tunduvalt keerukam on, või neid iga kujundi kohta mitu.
Siin on kujundi näidetena toodud Tikutops ja Vorstijupp. Esimesel neist on standardmõõtude puhul suurus kohe teada, vastavad meetodid väljastavad kohe konkreetsed arvud. Teisel juhul antakse mõõtmed objekti loomisel ette, meetodid peavad küsimise peale need salvestatud väärtused välja andma. Ning kui vorst ilusti matemaatiliselt omale silindrina ette kujutada, siis annab pii korda raadiuse ruut ilusti põhja ehk ristlõike pindala välja.
Klassist Kujund enesest ei saa eksemplare luua. Kui klass sisaldab abstraktseid ehk defineerimata meetodeid, siis peab klass ka ise tervikuna olema abstraktne ning siis ei lubata temast eksemplare teha. Muidu ju tekiks probleem, kui soovitaks käivitada kujundi olematu koodiga käsklust KüsiKõrgus().
Küll aga tohib muutujale tüübist Kujund tegelikke objekte omistada — olgu nad siis Tikutopsid, Vorstijupid või pärit mõnest muust Kujundi alamklassist. Sarnaselt nagu võis Daami omistada Inimese tüüpi muutujale või Lapse muutujale tüübist IViisakas. Kui üle kaetud klassis on eelnevalt abstraktsetele meetoditele sisu antud, siis võib sellest klassist julgesti isendeid luua ning neid ka kõikidest ülemklassidest pärit muutujatele omistada.
abstract class Kujund
{
public abstract double KüsiPõhjaPindala();
public abstract double KüsiKõrgus();
public double KüsiRuumala()
{
return KüsiPõhjaPindala() * KüsiKõrgus();
}
}
class Tikutops : Kujund
{
public override double KüsiPõhjaPindala()
{
return 8;
}
public override double KüsiKõrgus()
{
return 1.5;
}
}
class Vorstijupp : Kujund
{
int pikkus, raadius;
public Vorstijupp(int p, int r)
{
pikkus = p;
raadius = r;
}
public override double KüsiPõhjaPindala()
{
return Math.PI * raadius * raadius;
}
public override double KüsiKõrgus()
{
return pikkus;
}
}
public class Program
{
public static void Main()
{
Tikutops t = new Tikutops();
Vorstijupp v = new Vorstijupp(10, 3);
Console.WriteLine("Ruumalad {0} ja {1}", t.KüsiRuumala(), v.KüsiRuumala());
}
}
abstract class Kujund
{
public abstract double KüsiPõhjaPindala();
public abstract double KüsiKõrgus();
public double KüsiRuumala()
{
return KüsiPõhjaPindala() * KüsiKõrgus();
}
}
class Tikutops : Kujund
{
public override double KüsiPõhjaPindala()
{
return 8;
}
public override double KüsiKõrgus()
{
return 1.5;
}
}
class Vorstijupp : Kujund
{
int pikkus, raadius;
public Vorstijupp(int p, int r)
{
pikkus = p;
raadius = r;
}
public override double KüsiPõhjaPindala()
{
return Math.PI * raadius * raadius;
}
public override double KüsiKõrgus()
{
return pikkus;
}
}
public class Program
{
public static void Main()
{
Tikutops t = new Tikutops();
Vorstijupp v = new Vorstijupp(10, 3);
Console.WriteLine("Ruumalad {0} ja {1}", t.KüsiRuumala(), v.KüsiRuumala());
}
}Ülesandeid
- Lisa klassile Kujund abstraktne meetod PõhjaÜmbermõõt ning meetod KüljePindala. Katseta - lisades vajalikud meetodid ka alamklassidesse.
- Loo Kujundi alamklass Risttahukas lisades talle vajalikud väljad kolme mõõtme hoidmiseks ja kattes üles Kujundi abstraktsed meetodid. Katseta mitmesuguste Risttahuka eksemplaridega.
- Koosta mitmesuguste Kujundite massiiv. Loo alamprogramm leidmaks massiivis olevate kujundite ruumalade summa. Loo eraldi alamprogramm leidmaks massiivis olevate kujundite pindalade summa.
Objektorienteeritus
Meetodite asendus
Harilikult kirjutatakse meetodite üle katmise juures ülemklassi meetodi ette virtual ning alamklassi juurde override. Sellisel juhul alamklassi (siinses näites Daami) objekti puhul kasutatakse alati seda meetodit, mis tema juurde käib - sõltumata, millisest tüübist on muutuja, mille kaudu eksemplari poole pöördutakse. C++ võimalusi säilitades aga on jäetud ka teine võimalus. Meetodi võib asendada nõnda, et tema kirjeldamise ette kirjutatakse sõna new. Sel juhul peidetakse vana meetod samuti ära. Vana meetodi saab aga kätte juhul, kui objekti poole pöörduda ülemklassi muutujast, kus vastav meetod vanal kujul kasutusel oli. Kui virtual/override puhul pidid parameetrid ja väljastustüüp samaks jääma, siis new loob täiesti uue ja eelmisest sõltumatu meetodi.
Järgnevas näites on ehitatud kunstlik pärilusahel. Ülemklassiks Inimene, kes ütleb oma vanuse nõnda nagu see on. Inimesest pärinenud Daam võtab ilma pikemalt mõtlemata 5 aastat maha. Daami alamklassiks olev Beib keeldub üldse vanuse teatamisest ning eriti kaugele arenenud KavalBeib palub kasutajal ise tema vanust pakkuda. Sõna sealed klassi juures näitab, et sellest klassist ei lubata enam edasi pärida. Selline määrang aitab kompilaatoril koodi optimeerida.
Alljärgnevalt katsetatakse, millist tüüpi muutuja kaudu millise tegeliku objekti poole pöördumisel milline tulemus saadakse. Et omistamine on võimalik ainult ülemklassi suunas, siis igaühe neist saab omistada Inimese tüüpi muutujale. Mida tase edasi, seda vähem on omistusvõimalusi.
Katsetamise käigus antakse Beiblastele vanuseks 17 aastat, teistele 40. Ning jälgitakse, milline meetod millise muutuja kaudu väljakutsel käima läheb. Kõige pikem ja keerukam lugu on KavalBeibega. Et ta on pärimispuus kõige kaugemal, siis teda on võimalik omistada kõikide selles puus olevate tüüpidega muutujatele. Enese tüübi puhul küsib ta vanuseks 19, nagu käskluses öeldud. Ka lihtsalt Beib-tüüpi muutuja kaudu küsib ta enesele 19, sest klassi Beib meetod ÜtleVanus on virtual ning tegelikult käima läheb objekti enese ehk klassis KavalBeib loodud meetod.
Edasi muutub lugu keerulisemaks. Daami-muutujast välja kutsutav KavalBeibe vanus tuleb 12, sest ta lahutab aastad maha nagu Daamile kohane. Ning ka hariliku inimese kaudu tuleb 12, sest virtual-piiritleja kaudu võetakse käsklus võimalikult objekti enese lähedalt.
class Inimene
{
protected int vanus;
public Inimene(int v)
{
vanus = v;
}
public virtual void ÜtleVanus()
{
Console.WriteLine("Minu vanus on " + vanus + " aastat");
}
}
class Daam : Inimene
{
public Daam(int vanus)
: base(vanus)
{
}
public override void ÜtleVanus()
{
Console.WriteLine("Minu vanus on " + (vanus - 5) + " aastat");
}
}
class Beib : Daam
{
public Beib(int vanus)
: base(vanus)
{
}
new public virtual void ÜtleVanus()
{
Console.WriteLine("Minu vanus pole sinu asi, vot!");
}
}
sealed class KavalBeib : Beib
{
//siit ei saa enam edasi areneda
public KavalBeib(int vanus)
: base(vanus)
{
}
public override void ÜtleVanus()
{
Console.WriteLine("Arva, kas olen {0}?", vanus + 2);
}
}
public class Program
{
public static void Main()
{
KavalBeib kb = new KavalBeib(17);
Beib b = new Beib(17), bkb = kb;
Daam d = new Daam(40), db = b, dkb = kb;
Inimene i = new Inimene(40), id = d, ib = b, ikb = kb;
kb.ÜtleVanus();
b.ÜtleVanus();
bkb.ÜtleVanus();
d.ÜtleVanus();
db.ÜtleVanus();
dkb.ÜtleVanus();
i.ÜtleVanus();
id.ÜtleVanus();
ib.ÜtleVanus();
ikb.ÜtleVanus();
}
}
class Inimene
{
protected int vanus;
public Inimene(int v)
{
vanus = v;
}
public virtual void ÜtleVanus()
{
Console.WriteLine("Minu vanus on " + vanus + " aastat");
}
}
class Daam : Inimene
{
public Daam(int vanus)
: base(vanus)
{
}
public override void ÜtleVanus()
{
Console.WriteLine("Minu vanus on " + (vanus - 5) + " aastat");
}
}
class Beib : Daam
{
public Beib(int vanus)
: base(vanus)
{
}
new public virtual void ÜtleVanus()
{
Console.WriteLine("Minu vanus pole sinu asi, vot!");
}
}
sealed class KavalBeib : Beib
{
//siit ei saa enam edasi areneda
public KavalBeib(int vanus)
: base(vanus)
{
}
public override void ÜtleVanus()
{
Console.WriteLine("Arva, kas olen {0}?", vanus + 2);
}
}
public class Program
{
public static void Main()
{
KavalBeib kb = new KavalBeib(17);
Beib b = new Beib(17), bkb = kb;
Daam d = new Daam(40), db = b, dkb = kb;
Inimene i = new Inimene(40), id = d, ib = b, ikb = kb;
kb.ÜtleVanus();
b.ÜtleVanus();
bkb.ÜtleVanus();
d.ÜtleVanus();
db.ÜtleVanus();
dkb.ÜtleVanus();
i.ÜtleVanus();
id.ÜtleVanus();
ib.ÜtleVanus();
ikb.ÜtleVanus();
}
}Ülesandeid
- Loo klass Punkt väljadega x ja y ning meetoditega KaugusNullist ja TeataAndmed. Esimene väljastab reaalarvuna kauguse koordinaatide alguspunktist. Teine tagastab tekstina koordinaatide väärtused.
- Loo Punktile alamklass RuumiPunkt. Lisa väli z, kata üle KaugusNullist ning asenda TeataAndmed. Esimene väljastab kauguse nullist kolme koordinaadi korral, teine aga kirjutab RuumiPunkti andmed ekraanile, meetodid tagastustüübiks on void.
- Katseta loodud objekte ja nende käsklusi igal võimalikul moel. Punkt Punkti muutujast, RuumiPunkt RuumiPunkti muutujast ning RuumiPunkt Punkti muutujast.
Objektorienteeritus
Omadused
Objektide juures tehakse enamasti selget vahet: väljad ehk muutujad (field) on andmete hoidmiseks, käsud ehk funktsioonid ehk meetodid (method) toimingute sooritamiseks, muuhulgas ka andmete poole pöördumiseks. Ning korralikult kapseldatud objektorienteeritud programmis pääseb väljadele otse ligi vaid objekti seest, kõik muud välised toimetused käivad meetodite kaudu.
Kirje (struct) puhul eeldatakse, et ta on loodud põhiliselt andmete hoidmiseks, andmete õigsuse ja kokkusobivuse eest hoolitseb väline programm, et tegemist on lihtsalt muutujate komplektiga nagu näiteks punkti koordinaadid. Kirje vaid hoolitseb, et x ja y alati kokku kuuluksid.
Siiski on erinevalt mõnest muust keelest C# puhul lubatud kirje juurde ka käsklusi lisada. Enamasti kasutatakse neid arvutatavate omaduste - nagu näiteks sünniaja järgi vanuse - leidmiseks. Kirje kasutamise teeb objektist mugavamaks käskude mõningane lühidus. Seal võib rahulikult kirjutada
p1.X = 17;
int a = p1.X;
Objektide puhul peetakse sellist otse muutujate poole pöördumist ebaviisakaks. Et lühidat kirjastiili säilitada ning samas jätta alles meetoditele iseloomulik kontrollitavus ja muudetavus, selleks ongi loodud võimalus objektile luua omadusi, mis näevad välja nagu muutujad, kuid käituvad nagu meetodid.
Järgnevas näites on klassi Ilmaandmed eksemplaridele lisatud omadus Temperatuur, millele saab väljapoolt väärtust omistada ning küsida nagu tavaliselt muutujalt ehk väljalt. Küsimise peale pannakse lihtsalt tööle get-koodiosa ning omistamise peale set-koodiosa. Sõna value tähistabki omistamisel antud väärtust. Koodi ülesandeks on vastava märksõna all saabunud andmed vajalikku kohta paigutada. Tegelikke andmeid hoitakse privaatses ehk väljapoolt nähtamatus muutujas nimega temperatuur. Vajadusel saab nõnda omaduse või meetodi kaudu andmete poole pöördudes peale panna näiteks kontrolli võimalike väärtuste üle omistamisel. Või siis saab programmeerija otsustada hoopis, et temperatuure endid ei hoita mingil hetkel enam muutujas, vaid hoopis andmebaasis. Et kogu andmetega toimetamine on jäetud get- ja set-meetodite hooleks, siis on selline asendus täiesti võimalik.
class Ilmaandmed
{
private int temperatuur;
public int Temperatuur
{
get
{
return temperatuur;
}
set
{
temperatuur = value;
}
}
}
public class Program
{
public static void Main()
{
Ilmaandmed ij = new Ilmaandmed();
ij.Temperatuur = 15;
Console.WriteLine(ij.Temperatuur);
}
}
class Ilmaandmed
{
private int temperatuur;
public int Temperatuur
{
get
{
return temperatuur;
}
set
{
temperatuur = value;
}
}
}
public class Program
{
public static void Main()
{
Ilmaandmed ij = new Ilmaandmed();
ij.Temperatuur = 15;
Console.WriteLine(ij.Temperatuur);
}
}Pöördumisstatistika näide
Siin ongi toodud võimalikult lihtne näide, kuidas peale omistamise/küsimise veel meelde jätta ja teada saada nende operatsioonide arv. Lihtsalt loendamise jaoks vastavad muutujad ja käsklused juures.
class Ilmaandmed
{
private int temperatuur;
private int muudetud;
private int loetud;
public int Temperatuur
{
get
{
loetud++;
return temperatuur;
}
set
{
muudetud++;
temperatuur = value;
}
}
public override string ToString()
{
return "Muudetud: " + muudetud + ", loetud: " + loetud + ", temperatuur: " + temperatuur;
}
}
public class Program
{
public static void Main()
{
Ilmaandmed ij = new Ilmaandmed();
ij.Temperatuur = 15;
Console.WriteLine(ij.Temperatuur);
Console.WriteLine(ij.Temperatuur);
Console.WriteLine(ij);
}
}
class Ilmaandmed
{
private int temperatuur;
private int muudetud = 0;
private int loetud = 0;
public int Temperatuur
{
get
{
loetud++;
return temperatuur;
}
set
{
muudetud++;
temperatuur = value;
}
}
public override string ToString()
{
return "Muudetud: " + muudetud + ", loetud: " + loetud + ", temperatuur: " + temperatuur;
}
}
public class Program
{
public static void Main()
{
Ilmaandmed ij = new Ilmaandmed();
ij.Temperatuur = 15;
Console.WriteLine(ij.Temperatuur);
Console.WriteLine(ij.Temperatuur);
Console.WriteLine(ij);
}
}Ülesandeid
- Kui temperatuuriks märgitakse üle 35, trüki hoiatusteade kahtlase väärtuse kohta
- Loo klass Kasutaja. Kasutajanimi määratakse konstruktoris, ning seda on hiljem võimalik ainult omadusena küsida, mitte muuta (set-osa puudub). Parooli saab ainult määrata, aga küsida pole võimalik (get-osa puudub). Lisa käsklus parooli kontrolliks. Lisa omadusena kasutaja telefoninumber, mida on võimalik küsida ja muuta. Kontrolli, et töötaksid vaid määratud tehted (st. et kord pandud parooli ei saaks küsida).
Objektorienteeritus
Indekseering
Massiivide puhul oleme harjunud, et kandiliste sulgude ja järjekorranumbri järgi küsime või seame omale väärtuse. C# süntaks lubab aga ka ise kirjeldatud objektidel omapoolsed käsud sellise pöördumise peale tööle panna. Iseenesest oleks võimalik kõik sellised toimetused mõne hariliku funktsiooni ehk meetodiga ära ajada, aga kandiliste sulgudega kirja panduna tehe näeb lühem välja. Ning mõnigi kord, kui andmed paistavad sarnased nagu massiivides hoitakse, võimaldab selline kantsulgudega tehe koodi ka harjunumalt kirja panna.
Esimeses, võimalikult lihtsas näites väljastatakse indekseerimistehte tulemusena etteantud arvu ruut. Kirjaviis on mõnevõrra sarnane omaduse kirjeldamisele: get-osas tuleb soovitud väärtus returni abil tagasi anda.
class Ruuduarvutus
{
public int this[int nr]
{
get
{
return nr * nr;
}
}
}
public class Program
{
public static void Main()
{
Ruuduarvutus r = new Ruuduarvutus();
Console.WriteLine(r[3]);
}
}
class Ruuduarvutus
{
public int this[int nr]
{
get
{
return nr * nr;
}
}
}
public class Program
{
public static void Main()
{
Ruuduarvutus r = new Ruuduarvutus();
Console.WriteLine(r[3]);
}
}Vahendus
Mõnelgi korral võib omaloodud indekseerimisel kasutada juba olemasolevat massiivi või muud andmekogumit. Ning indekseerimise kaudu saab lisada selle elementide kasutamisele mõne piirangu või ümberarvutusfunktsiooni. Siinses näites ehitati indekseerimise abil kest ümber paisktabelile — paaride kogumile, kus igas paaris on võti ja väärtus. Võtmeks on kuupäev, väärtuseks asukoht, kus vastaval päeval ansambel esineb. Andmete küsimisel vastatakse veel vaba päeva kohta küsimisel "Vaba", kinnipandud päeva puhul teatatakse, kus vastaval päeval esinemine on. Nimeruumis System.Collections.Generic asuval Dictionary klassist objektil on andmete salvestamise ja küsimise käsklused juba sisse ehitatud. Meetodiga ContainsKey saab kontrollida, kas väärtus on juba olemas. Andmete salvestamise juures, juhul kui vastav kuupäev kinni on, heidetakse erind veateatega, miks vastav päev ei sobi. Kui aga soovitud kuupäev on veel vaba, siis pannakse sinna juurde ilusti sobiv väärtus kirja.
class Ringreis
{
Dictionary<int, string> esinemised = new Dictionary<int, string>();
public string this[int kuupäev]
{
get
{
if (esinemised.ContainsKey(kuupäev))
return esinemised[kuupäev];
return "Vaba";
}
set
{
if (esinemised.ContainsKey(kuupäev))
throw new Exception("Juba kinni, esinemine linnas " + esinemised[kuupäev]);
esinemised[kuupäev] = value;
}
}
}
public class Program
{
public static void Main()
{
Ringreis r = new Ringreis();
r[3] = "Narva";
r[4] = "Tartu";
Console.WriteLine(r[3]);
Console.WriteLine(r[5]);
r[3] = "Viljandi";
}
}
class Ringreis
{
Dictionary<int, string> esinemised = new Dictionary<int, string>();
public string this[int kuupäev]
{
get
{
if (esinemised.ContainsKey(kuupäev))
return esinemised[kuupäev];
return "Vaba";
}
set
{
if (esinemised.ContainsKey(kuupäev))
throw new Exception("Juba kinni, esinemine linnas " + esinemised[kuupäev]);
esinemised[kuupäev] = value;
}
}
}
public class Program
{
public static void Main()
{
Ringreis r = new Ringreis();
r[3] = "Narva";
r[4] = "Tartu";
Console.WriteLine(r[3]);
Console.WriteLine(r[5]);
r[3] = "Viljandi";
}
}
class Ringreis
{
Dictionary esinemised = new Dictionary();
public string this[int kuupäev]
{
get
{
if (esinemised.ContainsKey(kuupäev))
return esinemised[kuupäev];
return "Vaba";
}
set
{
if (esinemised.ContainsKey(kuupäev))
throw new Exception("Juba kinni, esinemine linnas " + esinemised[kuupäev]);
esinemised[kuupäev] = value;
}
}
}
public class Program
{
public static void Main()
{
Ringreis r = new Ringreis();
r[3] = "Narva";
r[4] = "Tartu";
Console.WriteLine(r[3]);
Console.WriteLine(r[5]);
r[3] = "Viljandi";
}
}Ülesandeid
- Muuda arvu ruutu väljastavat indekseerimise näidet nii, et see väljastaks etteantud arvu kuubi.
- Loo objekt, mis võtaks konstruktoris vastu sõna. Väljasta nii mitmes täht, kui indeksiga näidatakse. Kui arv ületab sõna pikkuse, siis antakse teada, et sellise järjekorranumbriga tähte ei leidu. Kui indeksiks pandud arv on negatiivne, siis väljastatakse niimitmes täht sõna lõpust arvates.
- Loo indekseeringu kaudu kasutatav seitsmeelemendiline massiiv vastaval nädalapäeval tehtud töötundide arvu summeerimiseks. Igal omistamisel liidetakse töötunnid vastava päeva arvule otsa. Igal küsimisel antakse välja sellele nädalapäevale liidetud töötundide summa. Negatiivse arvu sisestamisel tuleb veateade.
Objektorienteeritus
Struktuurne andmestik
Mitmedki toimetused saab tehtud ühe klassiga. Vajalikud andmed algul konstruktoris sisse ning käskudega kannatab neid küsida, lisada ja muuta nagu parajasti sobiv ja võimalik on. Kui on tegemist korraga mitme sarnase ülesehituse, kuid erinevate väärtustega andmetega (näiteks mitme isikukoodiga), siis võib loodud klassist luua iga väärtuse jaoks omaette objekti ja sealtkaudu siis mõningast analüüsi vajavate andmetega ümber käia. Kui aga andmeid on palju, mitut tüüpi ja omavahel seotud, siis tasub mõelda nende omavahelise struktuuri peale.
Programmeerimist saab üldjuhul õppida „pastakast välja imetud” lihtsate näidete peal. Kui aga mõnda teemasse sisse minna, siis tuleb erialateadmised ja programmeerimisoskused ühendada. Mõlemad võivad eraldi võttes olla küllaltki lihtsad, kuid nende kokkupanek võib veidi peamurdmist nõuda. Samas aga teadmiste ja tehnika ühendamine võib anda eraldi toimetamisest märksa kasulikumaid tulemusi.
Järgnevas näites mängitakse läbi elektriskeemides loodetavasti põhikooliajast tuttavate takistite ja nende ühendamisega ette tulevad arvutused. Elektroonikule on seostub takistiga tõenäoliselt sageli punast värvi väike silinder, mille mõlemast otsast traat välja tuleb. Iseenesest aga käituvad takistina ka tavalised lambipirnid ja mõned muudki elektroonikaseadmed. Lihtsal takistil on keskkonnaoludest suhteliselt sõltumatu väärtusega takistus. Samuti on tal lubatud suurim eralduv võimsus, mille ületamisel võib takistist välja tulla „hall mull” ehk tossupilv ning komponent pole pärast seda enam kasutatav. Takistuseks nimetatakse juhtmeotstel ehk klemmidel oleva pinge (surve, voltides) ning takistit läbiva voolu (elektronide voog, amprites) suhet. Mida suurem takistus, seda vähem läheb sama pinge juures takistist voolu läbi. Takistil soojusena eralduv võimsus (wattides) leitakse takisti klemmidele pandud pinge ning takistit läbiva voolu korrutisena. Põhivalemid siis:
U/I=R
U*I=N
Kui veidi avaldada, siis leiab sealt, et I2 = N/R. Ehk siis teadaoleva lubatud maksimumvõimsuse ja takistuse põhjal on võimalik leida takistit läbi suurim lubatud vool.
Takisteid saab omavahel kombineerida. Tüüpilised ühendused on järjestikku (jadamisi) ja rööbiti (paralleelselt). Jadamisi ühendades on arvutuskäik lihtne – takistuste komplekti kogutakistus on võrdne ühendatud takistite takistuste summaga. R=R1+R2 või ka rohkem komponente üksteisele järele liidetult. Rööbiti on arvutuskäik veidi keerulisem, kuid ka mitte lootusetu. Kahe rööbiti takisti kogutakistus on väiksem kui kummalgi eraldi, kogutakistuse pöördväärtus on võrdne üksikute takistite takistuste pöördväärtuste summaga 1/R = 1/R1 + 1/R2 — esialgses programmis aga rööbiti ühendusega lihtsuse mõttes ei tegele.
Alustame kõige lihtsamast – loome klassi takisti andmete hoidmiseks ja sealt vajalike väärtuste küsimiseks või arvutamiseks vastavalt lisaandmetele. Takistil omal muutujateks takistus r ja maksimumvõimsus maxn. Need antakse sisse ka konstruktoris. Vastavate andmete kättesaamiseks eraldi omadused Takistus ja MaksimumVõimsus – kuna tegemist piiratud ligipääsuga (protected) muutujatega, siis muidu ei pruugi loodud objektist enam väärtusi näha. Otse muutujate poole pöördumine pole viisakas – ei võimalda programmeerijal hiljem enam loodud objektiga toimuvat kontrollida. Siin aga kui andmed antakse sisse konstruktoris ning hiljem on võimalik neid ainult küsida, siis pole muret, et takisti andmed seletamatul põhjusel muutuma hakkaksid. Juurde ka mõned käsklused voolu ja võimsuse arvutamiseks ning kontrollimiseks, et kas soovitud pinge või vool ka konkreetsele takistile lubatud on.
Kui takisti klass valmis, siis on hea seda katsetada. Loome konkreetsete omadustega takisti – näiteks 5Ω takisti maksimumvõimsusega 2W – ehk siis ettekujutatuna ühe pisikese lapse näpuotsasuuruse jupikese, millest kaks juhet välja tulevad. Kontrollime, kas sellise takisti kannataks ühendada 1,5-voldise patarei taha. Programm arvutab ja teatab, et kannatab küll, väljundvõimsuseks 0,45 Watti. Ise järgi arvutades võime tulemust kontrollida. 1,5 volti jagatud 5 oomiga annab 0,3 amprit. 0,3 amprit korrutatuna 1,5 voldiga teebki 0,45 watti. Mis siis teeb küll takisti õrnalt soojaks, aga ei lõhu seda veel ära. Kui küsitaks peale tunduvalt suurem pinge, siis meie programm peaks teatama, et sellise pingega tekkiv võimsus pole lubatud.
class Takisti
{
protected double r;
protected double maxn;
public Takisti(double r, double maxn)
{
this.r = r;
this.maxn = maxn;
}
public double LeiaVool(double u)
{
return u / r;
}
public double LeiaVõimsus(double u, bool sobivuskontroll = true)
{
double n = u * LeiaVool(u);
if (sobivuskontroll && n > maxn)
throw new Exception("Pingel " + u + " ületab võimsus " +
n + " lubatud maksimumvõimsust " + maxn);
return n;
}
public bool KasLubatudVõimsusVastavaltPingele(double u)
{
return LeiaVõimsus(u, false) <= maxn;
}
public double MaksimumVool
{
get
{
return Math.Sqrt(maxn / r);
}
}
public double Takistus
{
get
{
return r;
}
}
public double MaksimumVõimsus
{
get
{
return maxn;
}
}
}
public class Program
{
public static void Main()
{
Takisti t1 = new Takisti(5, 2); //5 oomi, 2 vatti
double pinge = 1.5; //volti
if (t1.KasLubatudVõimsusVastavaltPingele(pinge))
Console.WriteLine("Patareilt " + pinge + "V saadakse " +
"takistiga " + t1.Takistus + " oomi vool " +
t1.LeiaVool(pinge) + "A ja " +
"võimsus " + t1.LeiaVõimsus(pinge) + "W");
else
Console.WriteLine("Pingel " + pinge + "V tekkiv võimsus " +
t1.LeiaVõimsus(pinge, false) + "W ületab lubatud " +
"võimsust " + t1.MaksimumVõimsus + "W");
}
}
class Takisti
{
protected double r;
protected double maxn;
public Takisti(double r, double maxn)
{
this.r = r;
this.maxn = maxn;
}
public double LeiaVool(double u)
{
return u / r;
}
public double LeiaVõimsus(double u, bool sobivuskontroll = true)
{
double n = u * LeiaVool(u);
if (sobivuskontroll && n > maxn)
throw new Exception("Pingel " + u + " ületab võimsus " +
n + " lubatud maksimumvõimsust " + maxn);
return n;
}
public bool KasLubatudVõimsusVastavaltPingele(double u)
{
return LeiaVõimsus(u, false) <= maxn;
}
public double MaksimumVool
{
get
{
return Math.Sqrt(maxn / r);
}
}
public double Takistus
{
get
{
return r;
}
}
public double MaksimumVõimsus
{
get
{
return maxn;
}
}
}
public class Program
{
public static void Main()
{
Takisti t1 = new Takisti(5, 2); //5 oomi, 2 vatti
double pinge = 1.5; //volti
if (t1.KasLubatudVõimsusVastavaltPingele(pinge))
Console.WriteLine("Patareilt " + pinge + "V saadakse " +
"takistiga " + t1.Takistus + " oomi vool " +
t1.LeiaVool(pinge) + "A ja " +
"võimsus " + t1.LeiaVõimsus(pinge) + "W");
else
Console.WriteLine("Pingel " + pinge + "V tekkiv võimsus " +
t1.LeiaVõimsus(pinge, false) + "W ületab lubatud " +
"võimsust " + t1.MaksimumVõimsus + "W");
}
}Ühe või kahe takistiga saab rahumeeli toimetada. Nendega ümber käimiseks pole isegi programmi vaja. Kui aga andmeid rohkem, siis tasub mõelda nende haldamise peale. Elektroonikutel on suuremate takistikogustega ümber käimiseks kasutada takistussalved. Sarnase vahendi kannatab ka programmi poole pealt valmis teha. Nii nagu programmiobjektid ühel või teisel moel jäljendavad reaalse maailma objektid omadusi, nii ka siinpool. Koostame takistite jadamisi ühendamiseks eraldi takistussalve. Nii nagu pärissalves on kindel arv kohti takistite jaoks, nii siin on massiivil ka kindel hulk mälupesi takistiobjektide jaoks. Pesade arv määratakse kindlaks salve konstruktoris. Salvest voolu läbilaskmisel on piirajaks kõige nõrgemat voolu kannatava takisti maksimumvool. Kui aga salv on tühi, ka siis ei või sellest lõpmatult suurt voolu läbi lasta. Siin näites on määratud tühja salve maksimumvooluks 16 amprit – selline korraliku koduse pikendusjuhtme läbilaskevõime. Salvel on käsklus takisti lisamiseks – esialgu aga veel mitte eemaldamiseks või väljavahetamiseks – seda lihtsalt lühiduse ja lihtsuse mõttes. Lisamisel jäetakse meelde lisatud takistite arv – siis teab, millisesse pessa järgmine lisatav takisti panna. Samuti on võimalik hoiatusteade anda juhul, kui salve enam takisteid ei mahu. Kogutakistuse leidmiseks liidetakse salves olevate takistite takistuste summad kokku. Maksimumvoolu leidmiseks leitakse vool, mida kannatab ka kõige nõrgem takisti ahelas. Lubatud pinge kontrollimiseks leitakse salve kogutakistuse järgi arvutatud vool vastavalt pingele ning kontrollitakse, kas see on piisavalt väike, et kõik salves olevad takistid selle välja kannataksid.
Paar takistit sisse ja väike arvutus kogutakistuse, maksimumvoolu ja lubatud pinge kohta. Nagu näha, salv loodi viiele takistile, sinna sisse paneme praegu ainult kaks. Üks kümneoomine ühevatine ning teine kümneoomine kahevatine. Siis aimatavalt on kogutakistus kakskümmend oomi ehk kahe takisti takistuste summa:
class JadamisiTakistusSalv
{
Takisti[] Andmed;
int arv = 0;
const int maksimumvool = 16;
public JadamisiTakistusSalv(int maksimumarv)
{
Andmed = new Takisti[maksimumarv];
}
public void LisaTakisti(Takisti t)
{
if (arv < Andmed.Length)
{
Andmed[arv] = t;
arv++;
}
else
throw new Exception("Takistussalv täis!");
}
public double KoguTakistus
{
get
{
double summa = 0;
for (int i = 0; i < arv; i++)
summa = summa + Andmed[i].Takistus;
return summa;
}
}
public double MaksimumVool
{
get
{
double mv = maksimumvool;
for (int i = 0; i < arv; i++)
if (Andmed[i].MaksimumVool < mv)
mv = Andmed[i].MaksimumVool;
return mv;
}
}
public bool KasPingeSobib(double u)
{
return u / KoguTakistus <= MaksimumVool;
}
}
public class Program
{
public static void Main()
{
JadamisiTakistusSalv Salv = new JadamisiTakistusSalv(5);
Salv.LisaTakisti(new Takisti(10, 1));
Salv.LisaTakisti(new Takisti(10, 2));
Console.WriteLine("Kogutakistus: " + Salv.KoguTakistus);
Console.WriteLine("Maksimumvool: " + Salv.MaksimumVool);
Console.WriteLine("Kas 12 Volti salve klemmidel lubatud: " + Salv.KasPingeSobib(12));
}
}
class Takisti
{
protected double r;
protected double maxn;
public Takisti(double r, double maxn)
{
this.r = r;
this.maxn = maxn;
}
public double LeiaVool(double u)
{
return u / r;
}
public double LeiaVõimsus(double u, bool sobivuskontroll = true)
{
double n = u * LeiaVool(u);
if (sobivuskontroll && n > maxn)
throw new Exception("Pingel " + u + " ületab võimsus " +
n + " lubatud maksimumvõimsust " + maxn);
return n;
}
public bool KasLubatudVõimsusVastavaltPingele(double u)
{
return LeiaVõimsus(u, false) <= maxn;
}
public double MaksimumVool
{
get
{
return Math.Sqrt(maxn / r);
}
}
public double Takistus
{
get
{
return r;
}
}
public double MaksimumVõimsus
{
get
{
return maxn;
}
}
}
class JadamisiTakistusSalv
{
Takisti[] Andmed;
int arv = 0;
const int maksimumvool = 16;
public JadamisiTakistusSalv(int maksimumarv)
{
Andmed = new Takisti[maksimumarv];
}
public void LisaTakisti(Takisti t)
{
if (arv < Andmed.Length)
{
Andmed[arv] = t;
arv++;
}
else
throw new Exception("Takistussalv täis!");
}
public double KoguTakistus
{
get
{
double summa = 0;
for (int i = 0; i < arv; i++)
summa = summa + Andmed[i].Takistus;
return summa;
}
}
public double MaksimumVool
{
get
{
double mv = maksimumvool;
for (int i = 0; i < arv; i++)
if (Andmed[i].MaksimumVool < mv)
mv = Andmed[i].MaksimumVool;
return mv;
}
}
public bool KasPingeSobib(double u)
{
return u / KoguTakistus <= MaksimumVool;
}
}
public class Program
{
public static void Main()
{
JadamisiTakistusSalv Salv = new JadamisiTakistusSalv(5);
Salv.LisaTakisti(new Takisti(10, 1));
Salv.LisaTakisti(new Takisti(10, 2));
Console.WriteLine("Kogutakistus: " + Salv.KoguTakistus);
Console.WriteLine("Maksimumvool: " + Salv.MaksimumVool);
Console.WriteLine("Kas 12 Volti salve klemmidel lubatud: " + Salv.KasPingeSobib(12));
}
}Ülesandeid
- Tutvu näidetega, vaheta väärtusi, kontrolli vastuste usaldusväärsust.
- Lisa takistiklassile käsklus, kus leitakse etteantud voolu tekitamiseks vajalik pinge. Katseta
- Lisa sama käsklus takistussalvele
- Kontrolli takisti lisamisel salve, et sama takisti ei oleks seal juba olemas.
Ühine ülemklass
Kui eelmist näidet tähelepanelikult jälgida, siis paistab, et nii takistussalve kui takisti juures on sarnaseid käsklusi. Voolu järgi pinge või pinge järgi voolu leidmise valem on ikka sarnane – tuleb ainult omale selgeks teha, mida see takistus parajasti tähendab. Samuti ei tohi ei üksikule takistile ega salvele panna peale suuremat pinget, kui suurima lubatud voolu jaoks kõlbulik pinge on. Ning mis veel peenem – kui kord on kokku pandud takistussalv sobiva suurusega takistusega, siis võib selle salve juhtmeotsad hea tahtmise korral ühendada teise salve ühe takisti kohale. Nõnda kombineerides saab salvedest ja takistitest kokku ühendada päris paindliku süsteemi. Ja et see päriselus võimalik on, siis on programmeerimiskeelte loojad teinud kõik, et ka programmiklassidega maailma järele tehes sellise paindlikkuse kokku saaks. Nagu alljärgnevalt näha, see ka õnnestub.
Takistile ja takistussalvele (ja võibolla veel mõnele muule elektronide voolamist pidurdavale seadmele, näiteks lambipirnile) loodi ühine ülemklass TakistusKomponent. Käsklused LeiaTakistus ja LeiaMaksimumVool on määratud abstraktseteks. Sest igal seadmel on selle leidmise moodus isesugune, vastavad omadused on aga igal elektriseadmel ehk takistuskomponendil olemas. Ülejäänud abstraktse klassi käsklustes võime takistuse ja maksimumvoolu lugeda juba teatuks ning konstrueerida muid käsklusi neid käske kasutades. Kui takistus teada, siis voolu saab pinge jagades takistusega. Kui maksimumvool teada ja pingele vastav vool arvutatav, siis saab kergesti järele kontrollida, kas komponendi klemmidele tohib olemasolevat pinget rakendada või mitte.
Takistussalves tuleb ikka luua koht sinna sisse pandavate komponentide andmete hoidmiseks. Erinevalt eelnevast näitest aga nüüd on andmepesa tüübiks TakistusKomponent. See tähendab, et programmis lubatakse salve sisse panna ka teisi salvesid. Miski ei takista praegu salvel ka iseenese väljuvaid juhtmeid ühe oma takistikomponendi klemmidele ühendada – selline suhteline mõttetus jääks aga praegu lihtsalt programmeerija südametunnistusele. Kusjuures mõne programmi puhul pole iseenese andmete hoidmine sugugi mõttetu nähtus. Näiteks, kui grupijuht peab hoolitsema inimeste toiduportsude eest, siis peab ta kindlasti hoolitsema, et ta ka enese tarbeks supikausi muretseks. Kogutakistuse leidmisel leitakse üksikute pesade takistuste summad. Kui seal juhtuvad olema tavalised takistid, siis saadakse väärtused ühe käsuga muutujast kätte. Kui aga salve pesas juhtub olema teine salv, siis käsklus LeiaTakistus käivitab selle salve kogutakistuse leidmise käskluse. Samuti maksimumvoolu puhul leitakse eraldi iga komponendi maksimaalne lubatud vool. Ning kui mõneks komponendiks on salv, siis uuritakse läbi eraldi kõik selle salve pesad ja antakse tagasi sealse kõige õrnemat voolu kannatava komponendi vooluandmed.
Edasine on taas katsetus. Luuakse kaks salve. Ühele sisse kaks kümneoomist takistit, kahe- ja ühevatine. Teise salve üks 15-oomine kümnevatine takisti. Ning kolmanda salve sisse sootuks kaks esimest salve. Edasi võib juba rahus päringuid tegema hakata ja vaadata, milliseid andmeid kust tagastatakse.
abstract class TakistusKomponent
{
public abstract double LeiaTakistus();
public abstract double LeiaMaksimumVool();
public double LeiaVoolVastavaltPingele(double u)
{
return u / LeiaTakistus();
}
public bool KasLubatudPinge(double u)
{
return LeiaVoolVastavaltPingele(u) < LeiaMaksimumVool();
}
}
class Takisti : TakistusKomponent
{
protected double r, maxn;
public Takisti(double r, double maxn)
{
this.r = r;
this.maxn = maxn;
}
public override double LeiaTakistus()
{
return r;
}
public override double LeiaMaksimumVool()
{
return Math.Sqrt(maxn / r);
}
}
class JadamisiTakistiteSalv : TakistusKomponent
{
TakistusKomponent[] Andmed;
int arv = 0;
const double maksimumvool = 16;
public JadamisiTakistiteSalv(int kogus)
{
Andmed = new TakistusKomponent[kogus];
}
public void LisaTakistusKomponent(TakistusKomponent t)
{
Andmed[arv] = t;
arv++;
}
public override double LeiaTakistus()
{
double abi = 0;
for (int i = 0; i < arv; i++)
abi = abi + Andmed[i].LeiaTakistus();
return abi;
}
public override double LeiaMaksimumVool()
{
double mv = maksimumvool;
for (int i = 0; i < arv; i++)
if (Andmed[i].LeiaMaksimumVool() < mv)
mv = Andmed[i].LeiaMaksimumVool();
return mv;
}
}
public class Program
{
public static void Main()
{
JadamisiTakistiteSalv ts1 = new JadamisiTakistiteSalv(5);
ts1.LisaTakistusKomponent(new Takisti(10, 2));
ts1.LisaTakistusKomponent(new Takisti(10, 1));
JadamisiTakistiteSalv ts2 = new JadamisiTakistiteSalv(5);
ts2.LisaTakistusKomponent(new Takisti(15, 10));
JadamisiTakistiteSalv ts3 = new JadamisiTakistiteSalv(5);
ts3.LisaTakistusKomponent(ts1);
ts3.LisaTakistusKomponent(ts2);
Console.WriteLine("Esimese salve maksimumvool: " + ts1.LeiaMaksimumVool());
Console.WriteLine("Teise salve maksimumvool: " + ts2.LeiaMaksimumVool());
Console.WriteLine("Kolmanda salve maksimumvool: " + ts3.LeiaMaksimumVool());
}
}
abstract class TakistusKomponent
{
public abstract double LeiaTakistus();
public abstract double LeiaMaksimumVool();
public double LeiaVoolVastavaltPingele(double u)
{
return u / LeiaTakistus();
}
public bool KasLubatudPinge(double u)
{
return LeiaVoolVastavaltPingele(u) < LeiaMaksimumVool();
}
}
class Takisti : TakistusKomponent
{
protected double r, maxn;
public Takisti(double r, double maxn)
{
this.r = r;
this.maxn = maxn;
}
public override double LeiaTakistus()
{
return r;
}
public override double LeiaMaksimumVool()
{
return Math.Sqrt(maxn / r);
}
}
class JadamisiTakistiteSalv : TakistusKomponent
{
TakistusKomponent[] Andmed;
int arv = 0;
const double maksimumvool = 16;
public JadamisiTakistiteSalv(int kogus)
{
Andmed = new TakistusKomponent[kogus];
}
public void LisaTakistusKomponent(TakistusKomponent t)
{
Andmed[arv] = t;
arv++;
}
public override double LeiaTakistus()
{
double abi = 0;
for (int i = 0; i < arv; i++)
abi = abi + Andmed[i].LeiaTakistus();
return abi;
}
public override double LeiaMaksimumVool()
{
double mv = maksimumvool;
for (int i = 0; i < arv; i++)
if (Andmed[i].LeiaMaksimumVool() < mv)
mv = Andmed[i].LeiaMaksimumVool();
return mv;
}
}
public class Program
{
public static void Main()
{
JadamisiTakistiteSalv ts1 = new JadamisiTakistiteSalv(5);
ts1.LisaTakistusKomponent(new Takisti(10, 2));
ts1.LisaTakistusKomponent(new Takisti(10, 1));
JadamisiTakistiteSalv ts2 = new JadamisiTakistiteSalv(5);
ts2.LisaTakistusKomponent(new Takisti(15, 10));
JadamisiTakistiteSalv ts3 = new JadamisiTakistiteSalv(5);
ts3.LisaTakistusKomponent(ts1);
ts3.LisaTakistusKomponent(ts2);
Console.WriteLine("Esimese salve maksimumvool: " + ts1.LeiaMaksimumVool());
Console.WriteLine("Teise salve maksimumvool: " + ts2.LeiaMaksimumVool());
Console.WriteLine("Kolmanda salve maksimumvool: " + ts3.LeiaMaksimumVool());
}
}Ülesandeid
- Koosta salv nelja viievatise kümneoomise takistiga. Leia kogutakistus, samuti suurim lubatud vool.
- Lisa abstraktsele takistuskomponendile käsklus LeiaPingeVastavaltVoolule. Selle ning LeiaMaksimumVoolu abil koosta käsklus LeiaMaksimumPinge. Katseta seda käsklust nii üksiku takisti kui takistussalve juures. Võrdele leitud pinget eelmises ülesandes leitud voolule vastava pingega.
- Koosta TakistusKomponendi alamklass rööpühenduses oleva kahe takistuskomponendi kogutakistuse leidmiseks.
1/R = 1/R1 + 1/R2. Maksimumvoolu leidmisel tuleb arvestada, et mõlemale takistile mõjub sama pinge, takisteid läbiv vool aga jaotub pöördvõrdeliselt vastavalt takistusele – mida suurem takistus, seda väiksem vool. Võimalik on ka hakata pingeid katsetama väikese sammuga ja sealtkaudu leida sobiv maksimumvool. Katseta selle rööpühenduse klassi põhjal loodud objekti töö õigsust mitmesuguste takistite ja takistussalvede puhul – kaks ühesugust takistit rööpühenduses, kaks erinevat takistit rööpühenduses. Takisti ja jadamisi salv rööpühenduses. Kaks rööpühendust veel omakorda rööpühenduses.
Objektorienteeritus
Operaatorite üledefineerimine
Liitmine ei pruugi sugugi tähendada arvude aritmeetilist summeerimist, vaid võib vastavalt taustale ja teemale hoopis isesuguse tähenduse saada. Samuti võib omaloodud klassist objektide puhul määrata, mida üks või teine tehtemärk tegelikult nende objektidega teeb.
Eelpoolkirjeldatud indekseerimistehte juures määras programmeerija samuti, kuidas omaloodud objekti puhul kantsulgude operaatori juures toimida. Järgnevalt paistab aga, et omapoolse tõlgenduse võib lisada mitmetele kasutatavatele tehtemärkidele. Nii nagu kantsulgude puhul, nii ka igasugu muude tehtemärkide asenduste puhul saab sama töö ära teha tegelikult vaid omaloodud funktsioone kasutades. Ja seetõttu pole koodi kirjutamise juures siinne teema sugugi hädavajalik. Aga võõra koodi mõistmiseks või oma objektidega lihtsaks ja elegantseks toimetamiseks sobivad omapoolselt käituma määratud tehtemärgid hästi.
Näiteks on võetud inimestele hästi tuttav kellaaeg. Tunde ja minuteid saab sarnaselt liita nagu kõiksugu muid suurusi. Kui aga näiteks 14:45le liita kaks tundi ja 30 minutit, siis tulemuseks pole mitte 16:75, vaid 17:15 ja selline ümberarvutus tuleb ka programmile selgeks teha, et välja arvutatud tulemustega ka inimeste keskkonnas midagi peale hakata oleks.
Plussmärgi üledefineerimiseks luuakse funktsioon nimega operator+, parameetriteks antakse kaks kellaaega — ehk siis vasakul ning paremal pool tehtemärki olev. Ja funktsiooni tagastustüübiks on samuti Kellaaeg. See tähendab, et kui kokku liidetakse kaks Kellaaega, siis tulemuseks on ka Kellaaeg.
Funktsiooni sisu jääb praegu lühikeseks. Käsu new abil luuakse uue Kellaaja eksemplar ning mõlema välja jaoks antakse lihtsalt ette plussmärgi mõlemal pool olnud vastava välja summad. Et funktsiooni esimese parameetrina olev k1 on tehte väljakutsuja, siis siin on võetud tund ja minut ta väljade seest. Objekti k2 puhul on andmete poole pöördumiseks viisakalt meetodit kasutatud. Aga eks oleks võimalik ka mõlemal juhul meetodiga seda küsimist toimetada.
public static Kellaaeg operator +(Kellaaeg k1, Kellaaeg k2)
{
return new Kellaaeg(k1.tund + k2.Tund(), k1.minut + k2.Minut());
}
Et uue eksemplari loomisel andmed ilusti salvestatud saaksid, selle eest hoolitseb konstruktor. Samuti kutsub viimane välja käskluse aegKorda, mille ülesandeks on liigsed minutid tundideks muundada.
Senikaua, kui minuteid juhtub etteantud olukorras olema üle kuuekümne, minnakse järgmise tunni juurde ning võetakse minutite alt neid tunni jagu vähemaks.
void aegKorda()
{
while (minut > 60)
{
tund++;
minut -= 60;
}
}
Kõige tavalisemal juhul, ehk siis, kui minuteid ongi alla kuuekümne, on tsükli tingimus kohe algul väär, tsüklit ei täideta ühtegi korda ning funktsioon lõpetab töö ilma midagi tegemata. Aga vähemasti võime kindlad olla, et ajaga on kõik korras.
class Kellaaeg
{
int tund, minut;
public Kellaaeg(int tund, int minut)
{
this.tund = tund;
this.minut = minut;
aegKorda();
}
void aegKorda()
{
while (minut > 60)
{
tund++;
minut -= 60;
}
}
public override string ToString()
{
return tund + ":" + minut.ToString("00");
}
public static Kellaaeg operator +(Kellaaeg k1, Kellaaeg k2)
{
return new Kellaaeg(k1.tund + k2.tund, k1.minut + k2.minut);
}
}
public class Program
{
public static void Main()
{
Kellaaeg k1 = new Kellaaeg(12, 10);
Kellaaeg k2 = new Kellaaeg(1, 4);
Kellaaeg k3 = k1 + k2;
Console.WriteLine(k3);
}
}
class Kellaaeg
{
int tund, minut;
public Kellaaeg(int tund, int minut)
{
this.tund = tund;
this.minut = minut;
aegKorda();
}
void aegKorda()
{
while (minut > 60)
{
tund++;
minut -= 60;
}
}
public override string ToString()
{
return tund + ":" + minut.ToString("00");
}
public static Kellaaeg operator +(Kellaaeg k1, Kellaaeg k2)
{
return new Kellaaeg(k1.tund + k2.tund, k1.minut + k2.minut);
}
}
public class Program
{
public static void Main()
{
Kellaaeg k1 = new Kellaaeg(12, 10);
Kellaaeg k2 = new Kellaaeg(1, 4);
Kellaaeg k3 = k1 + k2;
Console.WriteLine(k3);
}
}Tüübimuundusoperaatorid
Tahtes reaalarvu täisarvuks muundada nõnda, et komakohad kaduma lähevad, piisab arvu ette sulgudes sõna (int) kirjutamisest. Näiteks
int a = (int)6.34;
Kui uut tüüpi ette ei kirjutaks, siis annaks kompilaator veateate, sest reaalarvu ei pruugi olla võimalik kadudeta muundada täisarvuks. Sama lugu kehtib ka erineva suurusvaruga täisarvude või erineva täpsusega reaalarvude puhul: kui muundusel võib andmeid kaduma minna, siis tuleb muunduskäsk selgelt välja kirjutada. Vastupidi võib lasta ka arvutil tüübimuunduse automaatselt ära teha. Näiteks
double b = 3;
Ehkki kirjutatud kolm on arvuti jaoks algselt tüübist int, lubatakse see rahus reaalarvule omistada.
Tüübimuundusi võib aga ka omaloodud tüüpide juures ette võtta.
Järgnevaga teatatakse, mis tuleb ette võtta juhul, kui kellaaeg omistatakse täisarvule. Sõna implicit ütleb, et omistada võib eraldi tüübiteisenduskäsku (int) näitamata.
public static implicit operator int(Kellaaeg k)
{
return k.tund *60 + k.minut;
}
Ehk kui algul kirjutatakse
Kellaaeg k1 = new Kellaaeg(12, 10);
ja pärast
int minutidPäevaAlgusest = k1;
siis kellaaja muutmine arvuks käib ilma, et programmeerija peaks sellele eraldi tähelepanu juhtima.
Kui operaatorite kirjeldajal aga tekib kahtlus, et teisenduste käigus võivad andmed ebatäpsemateks muutuda, või lihtsalt soovitakse, et kogemata ei tehtaks kirjeldatavat teisendust, siis tuleb lisada piiritlejaks sõna explicit.
public static explicit operator double(Kellaaeg k)
{
return k.tund + k.minut / 60.0;
}
Sellisel juhul tuleb omistamisel sobivasse kohta kirjutada (double), et teisendusest asja saaks. Muidu annab kompilaator lihtsalt veateate.
double tunnidPäevaAlgusest = (double)k1;
Operaatorid võivad töötada ka teises suunas - ehk siis olemasolevast tüübist uue loodava tüübi poole. Siin näites tehakse minutite hulgast taas Kellaaeg. Niipalju, kui jagub täistunde, pannakse tundide alla. Mis aga tundideks jagamisest jäägina üle jääb, see muutub minutiteks.
public static explicit operator Kellaaeg(int minutid)
{
return new Kellaaeg(minutid / 60, minutid % 60);
}
Ning omistamisel saabki minutid taas Kellaajaks.
Kellaaeg k4 = (Kellaaeg)minutidPäevaAlgusest;
Kui koodi töö tulemust vaadata, siis algul oli kellaaeg k1 12:10. Vahepeal muundati see muutujasse minutidPaevaAlgusest ja saadi täisarvuna 730. Lõpuks minutid taas tundide ja minutitena kellaaja sisse jaotatuna andsid jälle 12 tundi ja 10 minutit.
class Kellaaeg
{
int tund, minut;
public Kellaaeg(int tund, int minut)
{
this.tund = tund;
this.minut = minut;
aegKorda();
}
void aegKorda()
{
while (minut > 60)
{
tund++;
minut -= 60;
}
}
public override string ToString()
{
return tund + ":" + minut.ToString("00");
}
public static Kellaaeg operator +(Kellaaeg k1, Kellaaeg k2)
{
return new Kellaaeg(k1.tund + k2.tund, k1.minut + k2.minut);
}
public static implicit operator int(Kellaaeg k)
{
return k.tund * 60 + k.minut;
}
public static explicit operator double(Kellaaeg k)
{
return k.tund + k.minut / 60.0;
}
public static explicit operator Kellaaeg(int minutid)
{
return new Kellaaeg(minutid / 60, minutid % 60);
}
}
public class Program
{
public static void Main()
{
Kellaaeg k1 = new Kellaaeg(12, 10);
Kellaaeg k2 = new Kellaaeg(1, 4);
Kellaaeg k3 = k1 + k2;
Console.WriteLine(k3);
int minutidPäevaAlgusest = k1;
double tunnidPäevaAlgusest = (double)k1;
Console.WriteLine(minutidPäevaAlgusest);
Console.WriteLine(tunnidPäevaAlgusest);
Kellaaeg k4 = (Kellaaeg)minutidPäevaAlgusest;
Console.WriteLine(k4);
}
}
class Kellaaeg
{
int tund, minut;
public Kellaaeg(int tund, int minut)
{
this.tund = tund;
this.minut = minut;
aegKorda();
}
void aegKorda()
{
while (minut > 60)
{
tund++;
minut -= 60;
}
}
public override string ToString()
{
return tund + ":" + minut.ToString("00");
}
public static Kellaaeg operator +(Kellaaeg k1, Kellaaeg k2)
{
return new Kellaaeg(k1.tund + k2.tund, k1.minut + k2.minut);
}
public static implicit operator int(Kellaaeg k)
{
return k.tund * 60 + k.minut;
}
public static explicit operator double(Kellaaeg k)
{
return k.tund + k.minut / 60.0;
}
public static explicit operator Kellaaeg(int minutid)
{
return new Kellaaeg(minutid / 60, minutid % 60);
}
}
public class Program
{
public static void Main()
{
Kellaaeg k1 = new Kellaaeg(12, 10);
Kellaaeg k2 = new Kellaaeg(1, 4);
Kellaaeg k3 = k1 + k2;
Console.WriteLine(k3);
int minutidPäevaAlgusest = k1;
double tunnidPäevaAlgusest = (double)k1;
Console.WriteLine(minutidPäevaAlgusest);
Console.WriteLine(tunnidPäevaAlgusest);
Kellaaeg k4 = (Kellaaeg)minutidPäevaAlgusest;
Console.WriteLine(k4);
}
}Võrdlusoperaatorid
Ikka tahetakse võrrelda, kas midagi toimus enne või pärast; uus on vanast suurem või väiksem; keegi kellestki targem või rumalam. Mõningate objektide puhul ei pruugi selline võrdlus anda selgepiirilist jah/ei vastust ning sellisel juhul on mõistlik arvuti jaoks võrdlusoperaatori defineerimine ära jätta ning piirduda pigem mõnevõrra hägusema ja paindlikuma skaalaga. Kui aga enamikel juhtudel on objektid omavahel kindlalt võrreldavad, siis on vastav operaator omal kohal.
Võrdluse jaoks on tehteid palju: >, <, <=, <=, ==, !=. Enamasti pole aga otstarbekas kõiki neid kohe ja eraldi defineerima hakata. Pigem teha täisarvu väljastav levinud funktsioon Compare. Selles võrreldakse aktiivset objekti funktsiooni parameetrina antud objektiga. Kui programmeerija arvates on aktiivne objekt parameetrina antud objektist järjestusreas eespool, siis peaks funktsioon väljastama negatiivse arvu. Kui tagapool, siis positiivse. Ning kui objektide võrreldavad tunnused peaksid programmeerija arvates olema võrdsed, siis tuleb väljastada 0. Edasised võrdlustehted saab juba sedasama funktsiooni kasutades korda ajada.
Siin näites tehakse mõlema kellaaja sisu kõigepealt minutiteks alates päeva algusest, et oleks kergem võrrelda ning siis juba saab ühe lahutustehtega sobiva vastuse kätte.
public int Compare(Kellaaeg k)
{
return (int)this -(int)k;
}
Samuti on viisakas üle katta klassist Object kaasa tulev käsklus Equals, mille ülesandeks on teatada, kas aktiivne objekt on etteantud objektiga sisu poolest võrdne. Kui oma Compare juba defineeritud, siis läheb edasine juba küllalt sarnaselt iga objekti puhul. Tingimuse esimese poolega tasub kontrollida, et kas võrreldav objekt üldse on meie omaga sama tüüpi. Ehk siis küsitakse, kas
ob is Kellaaeg
Vastus on tõene vaid juhul, kui etteantud objekt tõesti oli Kellaaeg. Ainult sel juhul minnakse && abil kirjutatud võrdluse kontrollimisega edasi. Muul juhul on tingimus kohe vale, tingimusblokist hüpatakse üle ning väljastatakse funktsiooni lõpus tagastatav vastus teatamaks, et kindlasti ei ole meie Kellaaeg sama väärtusega kui võrdlemiseks etteantud objekt, sest see teine objekt lihtsalt ei ole Kellaaeg.
Et saadud objekti saaks Kellaajana Compare-meetodisse panna, peab eraldi teatama, et me teda ikka Kellaajana kasutame. Ehkki is-kontrolli abil tegime juba kindlaks, et tegemist on Kellaajaga, tuleb funktsioonile ette andmiseks see uuesti muundada. Üheks võimaluseks oleks
(Kellaaeg)ob
siin aga näitame teist sarnast võimalust
ob as Kellaaeg
mis käitub üldjoontes samamoodi. Ainsaks erinevuseks on, et kui peaks siiski tüübiprobleem tekkima, siis esimesel juhul heidetakse erind, teisel juhul väljastatakse aga lihtsalt tühiväärtus. Kuna siin on juba kontroll tehtud, siis veateadet nagunii ei saa tekkida ning kood töötaks mõlemal juhul sarnaselt. Käsule Compare antakse tulemus kontrollida. Kui väärtused loeti võrdseteks ning väljastatakse 0, siis sel juhul kannatab Equals välja anda true, muul juhul false.
public override bool Equals(Object ob)
{
return ob is Kellaaeg && (this.Compare(ob as Kellaaeg) == 0);
}
Oma võrdlusoperaatorite puhul on viisakas üle katta veel ka käsklus GetHashCode. Selle salapärase käsu ülesandeks on anda arvutile märku, kas objektid võiksid olla võrdse väärtusega. Selleks tuleb objekti andmete põhjal kokku panna üks täisarv. Kui kahe objekti andmed kattuvad, peab see arv olema ühesugune, muul juhul võimaluse korral erinev. Kasutatakse näiteks paisktabelites, andmete otsimise jms. korral. Et meil on juba olemas andmete minutiteks tegemise operaator, siis see sobib räsikoodiks imehästi. Piisab vaid andmed täisarvuks teha, kui juba ongi eri kellaaegade puhul erinev arv käes, sest sama arv minuteid päeva algusest saab olla vaid ühesuguse kellaaja korral.
public override int GetHashCode()
{
return (int)this;
}
Kui näiteks hoitaks ajahetke objektis nii kellaaegu kui kuupäevi, siis võiks olukord veidi raskemaks minna, sest neljabaidine int ei pruugi suuta näidata kõiki erinevaid väärtusi, mis aastatuhandete jooksul ette tulevad. Sel juhul tuleks juba keerukamaid arvutusi rakendada, et kõik objekti väljad oleksid räsikoodi arvutamisel rakendatud ja muudaksid tulemust. Samas aga oleks eri väärtusega objektidel kahe sarnase räsikoodi kokku juhtumine võimalikult haruldane. Ehk siis ligikaudu üks paari miljardi kohta - nii nagu neid int-muutuja erinevaid väärtusi on.
Kui funktsioonidega eeltöö tehtud, siis edasine operaatorite defineerimine on juba käkitegu: tuleb lihtsalt õige funktsioon välja kutsuda.
public static bool operator !=(Kellaaeg k1, Kellaaeg k2)
{
return !k1.Equals(k2);
}
Ning teiste võrdlustega sarnaselt. Nüüd aga kood tervikuna.
class Kellaaeg
{
int tund, minut;
public Kellaaeg(int tund, int minut)
{
this.tund = tund;
this.minut = minut;
aegKorda();
}
void aegKorda()
{
while (minut > 60)
{
tund++;
minut -= 60;
}
}
public override string ToString()
{
return tund + ":" + minut.ToString("00");
}
public static Kellaaeg operator +(Kellaaeg k1, Kellaaeg k2)
{
return new Kellaaeg(k1.tund + k2.tund, k1.minut + k2.minut);
}
public static implicit operator int(Kellaaeg k)
{
return k.tund * 60 + k.minut;
}
public static explicit operator double(Kellaaeg k)
{
return k.tund + k.minut / 60.0;
}
public static explicit operator Kellaaeg(int minutid)
{
return new Kellaaeg(minutid / 60, minutid % 60);
}
public int Compare(Kellaaeg k)
{
return (int)this - (int)k;
}
public override bool Equals(Object ob)
{
return ob is Kellaaeg && (this.Compare(ob as Kellaaeg) == 0);
}
public override int GetHashCode()
{
return (int)this;
}
public static bool operator ==(Kellaaeg k1, Kellaaeg k2)
{
return k1.Equals(k2);
}
public static bool operator !=(Kellaaeg k1, Kellaaeg k2)
{
return !k1.Equals(k2);
}
public static bool operator <(Kellaaeg k1, Kellaaeg k2)
{
return k1.Compare(k2) < 0;
}
public static bool operator >(Kellaaeg k1, Kellaaeg k2)
{
return k1.Compare(k2) > 0;
}
}
public class Program
{
public static void Main()
{
Kellaaeg k1 = new Kellaaeg(12, 10);
Kellaaeg k2 = new Kellaaeg(1, 4);
Kellaaeg k3 = new Kellaaeg(1, 4);
if (k2 == k3)
Console.WriteLine("Samad");
if (k2 < k1)
Console.WriteLine("Enne");
}
}
class Kellaaeg
{
int tund, minut;
public Kellaaeg(int tund, int minut)
{
this.tund = tund;
this.minut = minut;
aegKorda();
}
void aegKorda()
{
while (minut > 60)
{
tund++;
minut -= 60;
}
}
public override string ToString()
{
return tund + ":" + minut.ToString("00");
}
public static Kellaaeg operator +(Kellaaeg k1, Kellaaeg k2)
{
return new Kellaaeg(k1.tund + k2.tund, k1.minut + k2.minut);
}
public static implicit operator int(Kellaaeg k)
{
return k.tund * 60 + k.minut;
}
public static explicit operator double(Kellaaeg k)
{
return k.tund + k.minut / 60.0;
}
public static explicit operator Kellaaeg(int minutid)
{
return new Kellaaeg(minutid / 60, minutid % 60);
}
public int Compare(Kellaaeg k)
{
return (int)this - (int)k;
}
public override bool Equals(Object ob)
{
return ob is Kellaaeg && (this.Compare(ob as Kellaaeg) == 0);
}
public override int GetHashCode()
{
return (int)this;
}
public static bool operator ==(Kellaaeg k1, Kellaaeg k2)
{
return k1.Equals(k2);
}
public static bool operator !=(Kellaaeg k1, Kellaaeg k2)
{
return !k1.Equals(k2);
}
public static bool operator <(Kellaaeg k1, Kellaaeg k2)
{
return k1.Compare(k2) < 0;
}
public static bool operator >(Kellaaeg k1, Kellaaeg k2)
{
return k1.Compare(k2) > 0;
}
}
public class Program
{
public static void Main()
{
Kellaaeg k1 = new Kellaaeg(12, 10);
Kellaaeg k2 = new Kellaaeg(1, 4);
Kellaaeg k3 = new Kellaaeg(1, 4);
if (k2 == k3)
Console.WriteLine("Samad");
if (k2 < k1)
Console.WriteLine("Enne");
}
}Ülesandeid
- Alusta esimesest lühikesest näitest ning lisa kellaajale ka sekundid.
- Arvesta sekundeid ka kellaaegade liitmise juures.
- Juhul, kui Kellaaeg teisendatakse tüübiks int, anna väärtus endise minutite asemel sekundites.
- Loo klass Nihe, mille väljadeks on pikkus x- ja y-suunal.
- Defineeri operaator nihete liitmiseks.
- Võimalda nihkeid ka lahutada.
- Võrdlusoperaator loeb nihked võrdseks vaid juhul, kui mõlema koordinaattelje suunalised pikkused on võrdsed.
- Suurema ja väiksema võrdlemisel võrreldakse vaid nihete pikkusi.
- Hoolitse ka omaloodud räsikoodi eest.
Abivahendid
Erindid
Kui arvutil palutakse teha tema jaoks võimatu käsk, siis enamasti lõpeb programmi töö veateatega. Nii nagu järgnevas näites, kus tekst "Tere" püütakse muundada täisarvuks.
int.Parse("Tere");
public class Program
{
public static void Main()
{
int.Parse("Tere");
}
}Me saime teada, mis juhtus, aga kasutaja ei pruugi sellise teatega kuigivõrd rahul olla. Eriti kui tuleb ette süsteemne veateateaken, mis püüab andmeid kuhugi saata või programmi siluma hakata ning keeldub eest ära minemast.
Püüdmine
Arvuti veateate saab asendada enese omaga. Või siis hoopis paluda veateate andmise asemel arv uuesti sisestada või sisestusest loobuda. Siin antakse lihtsalt omapoolne väike seletus toimunu kohta.
Veateate püüdmiseks on C# keeles olemas try{} catch plokk. Kõik looksulgude vahel juhtunud probleemid saadetakse lahendamisele catch-ossa, kus on juba programmeerija otsustada, mida tekkinud olukorras peale hakata.
try
{
int.Parse("Tere");
}
catch (Exception x)
{
Console.WriteLine(x);
}
public class Program
{
public static void Main()
{
try
{
int.Parse("Tere");
}
catch (Exception x)
{
Console.WriteLine(x);
}
}
}Reageering tüübi põhjal
Sama koodilõigu juures võib ette tulla mitmesuguseid probleeme. Kord ei leita sobivat andmefaili, teinekord ei saa teksti arvuks muundada ning mõnikord võib hoopis ette tulla jagamine nulliga. Vanemate programmeerimiskeelte juures oli tavaks iga käsu juures kontrollida, kas see õnnestus, ning siis püüda koheselt reageerida. Kui kohene parandamine on võimalik, on selline lähenemine hea. Kui aga peab parandamiseks palju asju ära muutma, siis kulub palju tööd. Selle lihtsustamiseks erindid ja veahaldus välja mõeldigi.
Ploki lõpus oleva catchi sulgudesse kirjutatakse selline erinditüüp, millele ollakse valmis reageerima. Nagu eespool oli — System.FormatException tekkis sisendandmete vormingu vea tõttu ning sellele probleemile ka reageeriti. Võib tekkida aga olukord, kus sisendiks on küll kõik numbrid, aga kokku tuleb int-vormingu jaoks liiga suur arv. Sellisel juhul heidetakse hoopis OwerflowException. Eraldi catchidega püüdes saab nendele vigadele sobivalt reageerida.
Vigade klassid moodustavad isekeskis hierarhia. Selle puu juureks on klass nimega Exception. Tema alamklassideks on hulk .NET runtime käivitamisega seotud probleemiklasse, aga muuhulgas ka SystemException, mille alt siis omakorda kättesaadavad enamik meil programmides ettetulevaid System-nimeruumi objektidega seotud erindeid.
SystemExceptioni enese alt leiab omakorda näiteks ArithmeticExceptioni, mille juurest omakorda OverflowExceptioni ja DivideByZeroExceptioni. Kui me tahame liialt suurt arvu (ületäitumine) ning nulliga jagamist kontrollida sama catchi sees, siis võib piirduda ArithmeticExceptioni püüdmisega. Kui aga kummagi olukorra jaoks on soov käivitada eri kood, siis tasub need eraldi kinni püüda.
Viga jääb kinni ainult ühes catch-plokis. Seepärast pannakse detailsemad erindid püüdmisel ettepoole ning üldisemad tahapoole. Muidu juhtuks, et teade jääb üldisematele tingimustele vastavasse plokki kinni ja väljavalitud lõiku kunagi ei pruugitagi. Tahtes kõik teated kindlasti kätte saada, võib lõppu panna catch(Exception). Sellele tüübile vastavad kõik veateated — ka need, mis on kõigist muudest püünistest juba mööda tulnud.
Juhul, kui soovitakse saabunud veateate andmetega midagi lähemat ette võtta, saab selle püüda omaette muutujasse ning sealtkaudu erindiobjektiga suhelda. Nagu näiteks:
catch (FormatException x)
{
Console.WriteLine("Viga sisendandmetega: " + x.Message);
}
Kui aga piisab vaid teadmisest, et juhtus vastavat tüüpi olukord ning sellest teadmisest on meile reageerimiseks küllalt, siis võib oma muutuja loomata jätta nagu näiteks:
catch (OverflowException)
{
Console.WriteLine("Liiga suur arv");
}
finally-plokki püüniste lõpus kasutatakse käskluste jaoks, mis tulevad igal juhul ära teha. Näiteks faili sulgemine andmete lugemisel: isegi siis, kui lugemine ebaõnnestus, tuleb fail teistele kasutajatele kättesaadavaks teha. Aga mainimist väärib, et finally-plokki jõutakse siiski ainult juhul, kui viga polnud või sai sellele reageeritud. Nii et kindlaks lõpuni jõudmiseks on mõistlik panna viimaseks veapüüniseks ikkagi catch(Exception). Kuigi — vahel soovitatakse, et pigem jäta erind püüdmata, kui et püüad midagi, millega sa mõistlikku peale hakata ei oska. Et kui tuleb ametlik veateade, on see vahel parem, kui omalt poolt vea peitmine, mis võib hiljem vigaste andmete näol kusagil kätte maksta.
Nüüd aga näide tervikuna. Käsurea parameetrina oodatakse numbreid, mille programm arvuks teisendab ning välja trükib. Juhtub aga midagi sobimatut, siis teatatakse vastav veateade:
public class Program
{
public static void Main()
{
try
{
Console.WriteLine("Sisesta arv:");
Console.WriteLine("Sisestati " + int.Parse(Console.ReadLine()));
}
catch (FormatException x)
{
Console.WriteLine("Viga sisendandmetega: " + x.Message);
}
catch (OverflowException)
{
Console.WriteLine("Liiga suur arv");
}
catch (Exception)
{
Console.WriteLine("Tundmatu probleem");
}
finally
{
Console.WriteLine("Plokk otsas");
}
}
}
public class Program
{
public static void Main()
{
try
{
Console.WriteLine("Sisesta arv:");
Console.WriteLine("Sisestati " + int.Parse(Console.ReadLine()));
}
catch (FormatException x)
{
Console.WriteLine("Viga sisendandmetega: " + x.Message);
}
catch (OverflowException)
{
Console.WriteLine("Liiga suur arv");
}
catch (Exception)
{
Console.WriteLine("Tundmatu probleem");
}
finally
{
Console.WriteLine("Plokk otsas");
}
}
}Püüdmine alamprogrammist
Veapüüniste tähtsaim eelis varasema veakoodinduse ees ongi kogu rakenduse alamprogrammide rägastikus tekkinud probleemide transport konkreetsetesse kohtadesse kokku, kus nendega üheskoos on vahel mõnevõrra kergem hakkama saada.
Järgnevas näites tekibki tõenäoline probleem alamprogrammis nimega LoeArv juhul, kui sisendiks pole arv. Veateade aga trükitakse alles Main-meetodi juures. Nõnda võib näiteks paluda kasutajal arvutamise jaoks anda mitu arvu. Kui aga kasvõi ühel korral sisestusel eksiti, on tulemus ikka sama — tulemust pole võimalik kokku saada. Ning sellest antakse veapüünises ka teada.
public class Program
{
public static int LoeArv()
{
Console.WriteLine("Sisesta arv:");
return int.Parse(Console.ReadLine());
}
public static void Main(string[] arg)
{
try
{
Console.WriteLine("Sisestati " + LoeArv());
}
catch (FormatException x)
{
Console.WriteLine("Viga teisendusel: " + x.Message);
}
}
}
public class Program
{
public static int LoeArv()
{
Console.WriteLine("Sisesta arv:");
return int.Parse(Console.ReadLine());
}
public static void Main(string[] arg)
{
try
{
Console.WriteLine("Sisestati " + LoeArv());
}
catch (FormatException x)
{
Console.WriteLine("Viga teisendusel: " + x.Message);
}
}
}Erindi heitmine
Sugugi ei pea leppima vaid arvuti enese antud veateadetega. Kui ikka oma programmis paistab, et midagi läheb väga käest ära, siis on vahel kasulik ise märku anda, et sarnaselt edasi toimida pole enam mõtet. Näiteks, kui arvutuse algandmed on ilmselgelt valed (kolmnurga üks külg pikem kui teised kaks kokku), siis võib julgesti enne arvutamist teada anda, milles asi ning heita selleteemalise erindi. Edasi on juba vastavat koodilõiku väljakutsuva programmeerija ülesandeks silumise käigus kindlaks teha, millest probleem tekkis ning vastavalt edasi toimida.
Siin näites lihtsalt keelati sajast suuremate arvude sisestus. Kui arv juhtub liiga suur olema, heidetakse erind. Lihtsuse mõttes pole oma tüüpi loodud, kasutatakse SystemExceptionit. Kuigi — vähegi pikema programmi selguse huvides oleks oma tüübi loomine kasulik. Et peaprogrammis pole SystemExceptioni jaoks veapüünist, siis tuleb ette süsteemne veateade, mille järele programmeerija peab juba ise edasi mõtlema, mida edasi teha.
int a = int.Parse(Console.ReadLine());
if (a > 100)
throw new SystemException("Liiga suur arv");
public class Program
{
public static int LoeArv()
{
Console.WriteLine("Sisesta arv:");
int a = int.Parse(Console.ReadLine());
if (a > 100)
throw new SystemException("Liiga suur arv");
return a;
}
public static void Main(string[] arg)
{
try
{
Console.WriteLine("Sisestati " + LoeArv());
}
catch (FormatException x)
{
Console.WriteLine("Viga teisendusel: " + x.Message);
}
}
}Ülesandeid
- Katseta esimese näite juures, kuidas käitub programm juhul, kui ette anda veatu arv.
- Muuda täisarvu käsklused reaalarvu omadeks ja leia, mis kasutamisel muutus.
- Loo tsükkel, mille abil küsitakse arvu senikaua, kuni saadakse sobiv sisend.
- Muuda näidet nõnda, et see annaks peaprogrammis viisaka seletuse ka omaheidetud erindi korral.
- Loo erindeid kasutades programm, mis suurendaks faili arv.txt sisu ühe võrra. Kui fail puudub, või failis pole arv, siis antakse selgitusega veateade.
- Kui failis olev arv ületab 365, siis anna välja omapoolne erind ning püüa sellele reageerida.
Abivahendid
Enum
Ikka leidub kohti, kus on võimalik teha piiratud arv valikuid. Asukoht Eestis on ühes maakondadest. Ühissõiduk on üldjuhul rong, tramm, troll või buss jne. Kui programmikoodis tuleb leida käitumisjuhis ühele etteantud valikutest, siis on enum hea abivahend. Maakonna nime saab kirjutada mitmeti. Olgu siis "Harjumaa", või "Harju maakond", rääkimata suurte ja väikeste tähtede ning tühikute erisusest. Andmebaaside puhul kasutatakse üldjuhul võimalust, et ei kirjutata inimese andmete juurde maakonna nime, vaid pannakse selle maakonna kood. Ning selle järgi on vajadusel võimalik teisest andmetabelist järele vaadata, millise maakonnaga siis päriselt tegu.
Midagi sarnast toimub ka enumi puhul. Ehk siis vastavas loetelus kirjeldatakse ära kõik võimalikud väärtused. Ning hiljem programmi sees pole võimalik enam vastavat nimetust valesti kirjutada ilma, et kood kompileerimata jääks. Sedasi on võimalik vältida vigu, mis muidu üllatavatel hetkedel võiksid avalduda. Tüüpiliseks kasutuskohaks on näiteks alamprogrammi parameetrid, kus enumi abil määratakse, kuidas just sel korral vastavate andmetega tuleb käituda. Järgnevas näites siis tuuakse tugevuse kohta kolm konstanti: tumm, ühekordne ja mitmekordne. Ning praegusel juhul alamprogrammis esimese variandi puhul jäetakse etteantud tekst sootuks trükkimata. Teisel juhul trükitakse ühe korra ning viimasel juhul mitu korda. Kui aga ühekordne kirjutatuks nõrga g-ga, siis jäänuks kood kompileerimata. Pealtnäha iseenesestmõistetav. Aga kui enumi asemel olnuks kasutatud stringi, siis just sellised vead on kerged tulema.
public class Program
{
enum Tugevus
{
tumm,
ühekordne,
mitmekordne
};
static void Trüki(string tekst, Tugevus t)
{
switch (t)
{
case Tugevus.ühekordne:
Console.WriteLine(tekst);
break;
case Tugevus.mitmekordne:
Console.WriteLine(tekst);
Console.WriteLine(tekst);
break;
}
}
public static void Main(string[] arg)
{
Trüki("Tere", Tugevus.ühekordne);
}
}
public class Program
{
enum Tugevus
{
tumm,
ühekordne,
mitmekordne
};
static void Trüki(string tekst, Tugevus t)
{
switch (t)
{
case Tugevus.ühekordne:
Console.WriteLine(tekst);
break;
case Tugevus.mitmekordne:
Console.WriteLine(tekst);
Console.WriteLine(tekst);
break;
}
}
public static void Main(string[] arg)
{
Trüki("Tere", Tugevus.ühekordne);
}
}Ülesandeid
- Loo klass vooluallika andmete hoidmiseks (pingevahemik, enum näitamaks kas tegemist alalis- või vahelduvvooluga)
- Koosta sellistest vooluallikatest mitmesuguste väärtustega massiiv.
- Koosta alamprogramm, mis saab parameetriks soovitud pinge, voolutüübi ja vooluallikate massiivi ning trükib välja soovitule vastavate vooluallikate andmed.
Abivahendid
Lambdaavaldised
Lambdaavaldised võimaldavad defineerida anonüümse meetodi. Anonüümsed meetodid on kasulikud kohtades, kus neid kasutatakse vaid korra. Sellisel puhul pole suurt abi, kui meetod on kuskil mujal defineeritud ja talle on antud nimi. Sageli on mõttekam, kui meetod on defineeritud samas kohas kus seda kasutatakse.
Lihtne näide sellest on sündmus (event):
public class Program
{
static void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
Console.Write(e.Result.Length);
}
public static void Main()
{
WebClient wc = new WebClient();
wc.DownloadStringCompleted += new DownloadStringCompletedEventHandler(wc_DownloadStringCompleted);
wc.DownloadStringAsync(new Uri("http://rss.eneta.ee/eneta-foorum", UriKind.Absolute));
}
}
public class Program
{
static void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
Console.Write(e.Result.Length);
}
public static void Main()
{
WebClient wc = new WebClient();
wc.DownloadStringCompleted += new DownloadStringCompletedEventHandler(wc_DownloadStringCompleted);
wc.DownloadStringAsync(new Uri("http://rss.eneta.ee/eneta-foorum", UriKind.Absolute));
}
}Meetod wc_DownloadStringCompleted on kasutuses ainult ühes kohas, seega ei anna selle defineerimine eraldi meetodina mingit võitu, pigem teeb see koodi lugemise keerulisemaks.
Siin on sama töö tehtud lambdaavaldisega, kus => annab kompilaatorile teada, et tegu on lambdaavaldisega:
public class Program
{
public static void Main()
{
WebClient wc = new WebClient();
wc.DownloadStringCompleted += (sender, e) => { Console.Write(e.Result.Length); };
wc.DownloadStringAsync(new Uri("http://rss.eneta.ee/eneta-foorum", UriKind.Absolute));
}
}
public class Program
{
public static void Main()
{
WebClient wc = new WebClient();
wc.DownloadStringCompleted += (sender, e) => Console.Write(e.Result.Length);
wc.DownloadStringAsync(new Uri("http://rss.eneta.ee/eneta-foorum", UriKind.Absolute));
}
}Sulgude vahel on meetodi argumendid. Need peavad kattuma nõutuga, kusjuures argumentide tüüpi pole vaja deklareerida (süsteem teab neid nagunii). Pärast => on meetodi keha. Antud juhtumil võib keha ümbert loogelised sulud ära jätta, sest tegu on üheainsa reaga.
Kui lambdaavaldisega defineeritav meetod ei vaja argumente, kirjutatakse tühjad sulud (). Kui argumente on ainult üks, võib sulud selle ümbert ära jätta.
Lambdaavaldiste juures on veel üks hea asi: sellele on kättesaadavad kõik kohalikud muutujad. Samas võib see olla ohtlik: alati tuleb meeles pidada, mis muutuja väärtus on, kui avaldis käivitatakse. Proovi järgmist näidet:
public class Program
{
public static void Main()
{
foreach (string s in new string[] {"http://rss.eneta.ee/eneta-foorum", "http://rss.eneta.ee"})
{
WebClient wc = new WebClient();
wc.DownloadStringCompleted += (sender, e) => Console.WriteLine(s);
wc.DownloadStringAsync(new Uri(s, UriKind.Absolute));
}
}
}
public class Program
{
public static void Main()
{
foreach (string s in new string[] {"http://rss.eneta.ee/eneta-foorum", "http://rss.eneta.ee"})
{
WebClient wc = new WebClient();
wc.DownloadStringCompleted += (sender, e) => Console.WriteLine(s);
wc.DownloadStringAsync(new Uri(s, UriKind.Absolute));
}
}
}Kuigi päring tehakse kahele erinevale aadressile, on muutuja s lambdaavaldises mõlemal korral sama. Miks? Seepärast, et sündmus tekib hiljem (veebisaidi allalaadimise lõppedes) ja selleks ajaks on muutuja s väärtus jõudnud loetelu viimase väärtuseni. Probleemi vältimiseks tuleb teha tsükli sisse teine muutuja:
public class Program
{
public static void Main()
{
foreach (string s in new string[] {"http://rss.eneta.ee/eneta-foorum", "http://rss.eneta.ee"})
{
string uri = s;
WebClient wc = new WebClient();
wc.DownloadStringCompleted += (sender, e) => Console.WriteLine(uri);
wc.DownloadStringAsync(new Uri(s, UriKind.Absolute));
}
}
}
public class Program
{
public static void Main()
{
foreach (string s in new string[] {"http://rss.eneta.ee/eneta-foorum", "http://rss.eneta.ee"})
{
string uri = s;
WebClient wc = new WebClient();
wc.DownloadStringCompleted += (sender, e) => Console.WriteLine(uri);
wc.DownloadStringAsync(new Uri(s, UriKind.Absolute));
}
}
}Lambdaavaldisi võib kasutada edukalt ka omatehtud meetodite juures ja need ei ole piiratud sündmustega.
public class Program
{
static void GetWeb(string uri, Action<string> a)
{
WebClient wc = new WebClient();
wc.DownloadStringCompleted += (sender, e) => a(e.Result);
wc.DownloadStringAsync(new Uri(uri, UriKind.Absolute));
}
public static void Main()
{
GetWeb("http://rss.eneta.ee/eneta-foorum", r => Console.WriteLine(r.Length));
}
}
public class Program
{
static void GetWeb(string uri, Action<string> a)
{
WebClient wc = new WebClient();
wc.DownloadStringCompleted += (sender, e) => a(e.Result);
wc.DownloadStringAsync(new Uri(uri, UriKind.Absolute));
}
public static void Main()
{
GetWeb("http://rss.eneta.ee/eneta-foorum", r => Console.WriteLine(r.Length));
}
}Ülaltoodud näites defineerisime meetodi GetWeb, mis võtab teise argumendina meetodi, millel on üks string-tüüpi argument. Sellesse kirjutatakse allalaaditud tekst. Seejärel saab kasutada lambdaavaldist, et GetWeb välja kutsuda.
Action on süsteemis kirjeldatud void-meetodi definitsioon, kusjuures nurksulgudes lisatakse argumentide tüübid. Func on meetodi definitsioon, mis tagastab väärtuse. Nurksulgudes on viimasena näidatud tagastatava tüüp.
Näiteks Action nõuab meetodit void M(), samas kui Action<string> nõuab void M(string s) ja Func<string, string> nõuab string F(string s).
Loomulikult võib Actioni ja Funci rahuldamiseks viidata ka pärismeetodile (ei pea olema lambdaavaldis):
public class Program
{
static void GetWeb(string uri, Action<string> a)
{
WebClient wc = new WebClient();
wc.DownloadStringCompleted += (sender, e) => a(e.Result);
wc.DownloadStringAsync(new Uri(uri, UriKind.Absolute));
}
static void WriteLength(string s)
{
Console.WriteLine(s.Length);
}
public static void Main()
{
GetWeb("http://rss.eneta.ee/eneta-foorum", WriteLength );
}
}
public class Program
{
static void GetWeb(string uri, Action<string> a)
{
WebClient wc = new WebClient();
wc.DownloadStringCompleted += (sender, e) => a(e.Result);
wc.DownloadStringAsync(new Uri(uri, UriKind.Absolute));
}
static void WriteLength(string s)
{
Console.WriteLine(s.Length);
}
public static void Main()
{
GetWeb("http://rss.eneta.ee/eneta-foorum", WriteLength );
}
}Viimane näide on mõttekas juhul, kui meetodit kasutatakse rohkem kui ühes kohas. Muudel juhtudel on lambdaavaldis mõnusam ja selgem.
Lambdaavaldised on eriti kasulikud LINQ-meetodites (vaata peatükist "LINQ").
Abivahendid
Atribuudid
C# võimaldab klasside definitsioonidele külge panna lisainfot, atribuute (attribute). Atribuutide abil saab luua programme, mis klasse või selle omadusi ja meetodeid teatud standardite järgi kohtlevad.
Näiteks võib teha veebiteenuse, mis avalikustab mõned meetodid vastavalt nende atribuutidele, luua programmi, mis salvestab mõned klassid ja nende omadused andmebaasi või XML-failidesse.
Atribuudid on ise deklareeritud klassidena, mis on päritud klassist Attribute. Atribuut kirjutatakse klassi nimena kantsulgudesse deklaratsiooni ette. Süsteemis on defineeritud üle 500 erineva atribuudiklassi.
Näiteks [Obsolete] aitab märgistada meetodeid, mida kasutajad ei tohiks enam tarbida (mille asemele on ilmselt tulnud midagi paremat, kuid mida ei saa lihtsalt kõrvaldada, sest keegi võib seda kasutada). Alltoodud näites on meetod Increment märgitud atribuudiga Obsolete:
[Obsolete("Kasuta selle asemel i++")]
public static int Increment(int i)
{
return i + 1;
}
public static void Main()
{
Console.WriteLine(Increment(1));
}
public class Program
{
[Obsolete("Kasuta selle asemel i++")]
public static int Increment(int i)
{
return i + 1;
}
public static void Main()
{
Console.WriteLine(Increment(1));
}
}Meie koodinäidet siin kompileerides ei juhtu midagi erakordset, aga Visual Studio näitab selle meetodi kasutamisel atribuudile kaasa antud hoiatust. Lausega Obsolete("Kasuta selle asemel i++") käivitatakse klassi ObsoleteAttribute konstruktor, mis võtab parameetrina stringi ja salvestab selle oma omadusse. Karmima käitlemise jaoks saab anda kaasa teise parameetri, mis sätestab, et atribuudi kasutamisel tekitatakse veateade. Proovi seda käivitada ja vaata mis juhtub:
[Obsolete("Kasuta selle asemel i++", true)]
public static int Increment(int i)
{
return i + 1;
}
public static void Main()
{
Console.WriteLine(Increment(1));
}
public class Program
{
[Obsolete("Kasuta selle asemel i++", true)]
public static int Increment(int i)
{
return i + 1;
}
public static void Main()
{
Console.WriteLine(Increment(1));
}
}Ülaltoodud efekti oleks ilma atribuutideta raske saavutada. Pealegi võimaldab atribuutide süsteem luua piiramatul arvul omatehtud atribuute.
Omatehtud atribuudid
Atribuut on klass nagu iga teinegi, ainult et 1) ta on päritud klassist Attribute ja 2) tema nimi lõpeb sõnaga Attribute.
public class Program
{
public class MõttetuAttribute: Attribute
{
}
[Mõttetu]
public static int Increment(int i)
{
return i + 1;
}
public static void Main()
{
Console.WriteLine(Increment(1));
}
}
public class Program
{
public class MõttetuAttribute: Attribute
{
}
[Mõttetu]
public static int Increment(int i)
{
return i + 1;
}
public static void Main()
{
Console.WriteLine(Increment(1));
}
}Selleks, et niisugusest isetehtud atribuudist mingit kasu oleks, peab seda keegi lugeda oskama. Nimeruumis System.Reflection on atribuutide lugemise vahendid. Alltoodud näide loendab meetodi külge pandud atribuudid:
[Mõttetu]
[Obsolete]
public static int Increment(int i)
{
return i + 1;
}
public static void Main()
{
foreach (var a in typeof(Program).GetMethod("Increment").GetCustomAttributes(true))
Console.WriteLine(a.GetType().Name);
}
public class Program
{
public class MõttetuAttribute: Attribute
{
}
[Mõttetu]
[Obsolete]
public static int Increment(int i)
{
return i + 1;
}
public static void Main()
{
foreach (var a in typeof(Program).GetMethod("Increment").GetCustomAttributes(true))
Console.WriteLine(a.GetType().Name);
}
}Ja järgmine näide kontrollib, kas teatud atribuut on küljes:
if (typeof(Program).GetMethod("Increment").GetCustomAttributes(typeof(MõttetuAttribute), true).Any())
Console.WriteLine("Jah, Increment on mõttetu");
public class Program
{
public class MõttetuAttribute: Attribute
{
}
[Mõttetu]
[Obsolete]
public static int Increment(int i)
{
return i + 1;
}
public static void Main()
{
if (typeof(Program).GetMethod("Increment").GetCustomAttributes(typeof(MõttetuAttribute), true).Any())
Console.WriteLine("Jah, Increment on mõttetu");
}
}Kuna atribuut on tavaline klass, siis me võime talle teha konstruktori, mis võimaldab parameetreid kaasa anda. Pärast saame seda välja lugeda nagu iga teisegi klassi puhul:
public class Program
{
public class MõttetuAttribute: Attribute
{
public enum Kui { peaaegu, täiesti };
public Kui KuiMõttetu;
public MõttetuAttribute(Kui KuiMõttetu = Kui.peaaegu)
{
this.KuiMõttetu = KuiMõttetu;
}
}
[Mõttetu(MõttetuAttribute.Kui.täiesti)]
public static int Increment(int i)
{
return i + 1;
}
public static void Main()
{
MõttetuAttribute m = typeof(Program).GetMethod("Increment").GetCustomAttributes(typeof(MõttetuAttribute), true).OfType<MõttetuAttribute>().FirstOrDefault();
if (m != null)
Console.WriteLine("Increment on " + m.KuiMõttetu + " mõttetu");
}
}
public class Program
{
public class MõttetuAttribute: Attribute
{
public enum Kui { peaaegu, täiesti };
public Kui KuiMõttetu;
public MõttetuAttribute(Kui KuiMõttetu = Kui.peaaegu)
{
this.KuiMõttetu = KuiMõttetu;
}
}
[Mõttetu(MõttetuAttribute.Kui.täiesti)]
public static int Increment(int i)
{
return i + 1;
}
public static void Main()
{
MõttetuAttribute m = typeof(Program).GetMethod("Increment").GetCustomAttributes(typeof(MõttetuAttribute), true).OfType<MõttetuAttribute>().FirstOrDefault();
if (m != null)
Console.WriteLine("Increment on " + m.KuiMõttetu + " mõttetu");
}
}Omaduste ja väljade väärtuse andmiseks ei pea atribuudile konstruktorit tegema, väärtusi saab anda sulgude sees ka nime järgi:
public class MõttetuAttribute: Attribute
{
public enum Kui { peaaegu, täiesti };
public Kui KuiMõttetu;
}
[Mõttetu(KuiMõttetu = MõttetuAttribute.Kui.täiesti)]
public static int Increment(int i)
{
return i + 1;
}
public class Program
{
public class MõttetuAttribute: Attribute
{
public enum Kui { peaaegu, täiesti };
public Kui KuiMõttetu;
}
[Mõttetu ( KuiMõttetu = MõttetuAttribute.Kui.täiesti )]
public static int Increment(int i)
{
return i + 1;
}
public static void Main()
{
MõttetuAttribute m = typeof(Program).GetMethod("Increment").GetCustomAttributes(typeof(MõttetuAttribute), true).OfType<MõttetuAttribute>().FirstOrDefault();
if (m != null)
Console.WriteLine("Increment on " + m.KuiMõttetu + " mõttetu");
}
}Atribuudid on staatilised, see tähendab, et need on deklareeritud koodis ja programmi jooksmise ajal neid muuta ei saa. See ongi, mis eristab atribuute omadustest ja väljadest: omadused ja väljad on mõeldud andmete dünaamiliseks salvestamiseks, samas kui atribuudid on laiendid klassi definitsioonile.
Abivahendid
Lõimed
Tänapäevased protsessorid on mitme tuumaga, mis võimaldab teha samaaegselt mitut tööd. Ja igaüks teab, et pärast Windowsi käivitamist käivitatakse terve hulk programme ja teenuseid. Lisaks on protsessoritele sisse ehitatud võime teenindada mitut programmi korraga ka sama tuuma piires, jooksutades neid vaheldumisi vastavalt sellele, kui palju aega mingi programm vajab.
Ka programmisiseselt on võimalik käivitada mitu tööülesannet ehk lõime (thread). See võimaldab 1) kasutada paremini ära protsessoriressurssi ja 2) mitte hoida kasutajaliidest ootel (hangunud seisus), kuni ressursimahukad protsessid lõpule jõuavad.
Vaatame esiteks näidet programmist, kus jadamisi täidetakse 2 aegavõtvat ülesannet:
public class Program
{
public static void Main()
{
DateTime start = DateTime.Now;
for (int t = 0; t < 2; t++)
{
string s = "";
for (int i = 0; i < 50000; i++)
s += "1";
}
Console.WriteLine((DateTime.Now - start).TotalMilliseconds);
}
}
public class Program
{
public static void Main()
{
DateTime start = DateTime.Now;
for (int t = 0; t < 2; t++)
{
string s = "";
for (int i = 0; i < 50000; i++)
s += "1";
}
Console.WriteLine((DateTime.Now - start).TotalMilliseconds);
}
}Minu arvutis kulus selle teostamiseks 2925 millisekundit. Nüüd käivitame kummagi tööülesande eraldi lõimes, luues objekti Thread:
for (int t = 0; t < 2; t++)
{
new Thread(() =>
{
string s = "";
for (int i = 0; i < 50000; i++)
s += "1";
}).Start();
}
public class Program
{
public static void Main()
{
DateTime start = DateTime.Now;
for (int t = 0; t < 2; t++)
{
new Thread(() =>
{
string s = "";
for (int i = 0; i < 50000; i++)
s += "1";
}).Start();
}
Console.WriteLine((DateTime.Now - start).TotalMilliseconds);
}
}Thread võtab oma konstruktoris parameetriks ühegi argumendita Actioni, mille võib anda anonüümsena. Thread käivitatakse meetodiga Start. Seega me saame siin hakkama ühe reaga ja lõim ongi käivitatud. Aga... aega kulus alla 1 millisekundi. Miks? Seepärast, et need 2 käivitatud lõime küll alustati ja need tegid oma töö lõpuni, aga programm ise lõpetas enne kui need lõpule jõudsid. Selles selgusele saamiseks võid vaadata oma tegumihaldurit (Shift + Ctrl + Esc). Kui sul on mitu tuuma või mitu protsessorit, siis vaata Jõudluse sakis iga protsessori kohta eri graafikut. Käivita meie näide uuesti ja sa näed, et 2 protsessori kasutus tõusis natukeseks ajaks lakke.
Selleks, et meie programm ootaks ära kummagi lõime töö lõpulejõudmise, tuleks kummagi lõime puhul kasutada meetodit Join. Seega peame lõimele tegema muutuja.
for (int t = 0; t < 2; t++)
{
Thread lõim = new Thread(() =>
{
string s = "";
for (int i = 0; i < 50000; i++)
s += "1";
});
lõim.Start();
lõim.Join();
}
public class Program
{
public static void Main()
{
DateTime start = DateTime.Now;
for (int t = 0; t < 2; t++)
{
Thread lõim = new Thread(() =>
{
string s = "";
for (int i = 0; i < 50000; i++)
s += "1";
});
lõim.Start();
lõim.Join();
};
Console.WriteLine((DateTime.Now - start).TotalMilliseconds);
}
}Nüüd käivitati kumbki ülesanne eraldi lõimes ja pealõim (programm ise) ootas, kuni lõimed on lõpetanud. Aga ajavõitu ei tulnud, sest Join paneb pealõime ootama, seega käivitati teine lõim alles pärast seda, kui esimene on lõpetanud, mis on ka loogiline, sest Join ei jätka enne kui lõim on lõpetanud.
Appi tuleb WaitHandle.WaitAll(), mis tahab saada massiivi ManualResetEvent-tüüpi objektidest. Need objektid pole muud kui lõimekindlad lipukesed, millele saab teha Set(). Kui kõigile lipukestele on tehtud Set(), tagastub WaitAll().
ManualResetEvent[] events = new ManualResetEvent[2];
for (int t = 0; t < events.Length; t++)
{
var e = events[t] = new ManualResetEvent(false);
new Thread(() =>
{
string s = "";
for (int i = 0; i < 50000; i++)
s += "1";
e.Set();
}).Start();
};
WaitHandle.WaitAll(events);
public class Program
{
public static void Main()
{
DateTime start = DateTime.Now;
ManualResetEvent[] events = new ManualResetEvent[2];
for (int t = 0; t < events.Length; t++)
{
var e = events[t] = new ManualResetEvent(false);
new Thread(() =>
{
string s = "";
for (int i = 0; i < 50000; i++)
s += "1";
e.Set();
}).Start();
};
WaitHandle.WaitAll(events);
Console.WriteLine((DateTime.Now - start).TotalMilliseconds.ToString());
}
}Eeltoodud näide pole just märkimisväärselt kiirem, aga 2 lõime jookseb paralleelselt ja pealõim ootab, kuni mõlemad on lõpetanud. Pane tähele, et me loome kohaliku muutuja e, sest vastasel juhul oleks t lõime lõpetamise ajaks muutunud ja Set() tehtaks valele lipule.
Seda saab märksa lihtsamalt teha rööplemise abil, aga kuna Silverlight 4 veel rööplemist ei toeta, siis me seda koodinäidist anda ei saa. Loe rööplemise kohta lisa siit.
Lõimed graafilise kasutajaliidesega
Graafiline kasutajaliides on võimeline jooksma ainult ühes lõimes ja see peab olema pealõim. Teised lõimed ei tohi kasutajaliidest näppida. Selleks, et teised lõimed saaksid kasutajaliidesele ligi, on igal graafilisel elemendil omadus Dispatcher, mille meetod BeginInvoke paneb soovitud töö pealõimele ootele ja pealõim teostab esimesel võimalusel. BeginInvoke ei kompileeru lambdaavaldisega, seega peame looma uue Actioni eksemplari. Proovi alltoodud näidet, kus taustalõim uuendab pealõimes olevat graafilist kasutajaliidest:
public class Test: Grid
{
ProgressBar progress = new ProgressBar { Maximum = 100000 };
public Test()
{
Children.Add(progress);
new Thread(() =>
{
string s = "";
for (int i = 0; i < 100000; i++)
{
s += "1";
if ((i % 1000) == 999)
Dispatcher.BeginInvoke(new Action(() => { progress.Value = i; }), null);
}
}).Start();
}
}
public class Test: Grid
{
ProgressBar progress = new ProgressBar { Maximum = 100000 };
public Test()
{
Children.Add(progress);
new Thread(() =>
{
string s = "";
for (int i = 0; i < 100000; i++)
{
s += "1";
if ((i % 1000) == 999)
Dispatcher.BeginInvoke(new Action(() => { progress.Value = i; }), null);
}
}).Start();
}
}Abivahendid
Nõrkviide
Nõrkviide ehk weak reference on viide objektile, mida on lubatud ära koristada. Kui see on alles, siis võtan, kui ei, siis teen uue. See on kasulik suuremahuliste objektide puhul, mida väga tihti ei kasutata.
Oletame, et veebiserverisse tehakse vahel harva päringuid, mis nõuavad teatud loetelu mällu lugemist. Ühest küljest oleks hea, et see loetelu oleks kogu aeg mälus (nii saab tulemuse ruttu väljastada), teisest küljest aga oleks parem, kui ta seal ruumi ei võta, kui näiteks terve päeva jooksul ei tee keegi enam seda päringut. Aga ei tea ju, kumb variant on parem. Siin tulebki appi nõrkviide, mis ütleb prügikoristusele: "Selle objekti võib mälust kustutada, aga kui ma alles hiljuti kasutasin seda ja see pole veel kustutatud, siis anna mulle see tagasi." .NETi prügikoristus koristab prügi omal äranägemisel, aga üldiselt siis, kui see on kasulik. Seega, niikaua kui mälu on piisavalt, hoitakse sinu objekti mälus. Aga kui läheb kitsaks, siis ongi parem kui ei hoita.
Siin tuleb appi klass WeakReference. Sellel on omadus Target, mis sisaldab objekti, kui see on veel alles, ja ei sisalda seda, kui prügifirma on selle ära koristanud. Ilma WeakReference'ita oleks sul suurte objektide jaoks 2 varianti: 1) teha väli, kus see on kogu aeg olemas või 2) luua see iga kord uuesti. WeakReference võimaldab luua välja, mis sisaldab nõrkviidet. Iga kord, kui seda suurt objekti küsitakse, kontrollitakse, kas Target on tühi. Kui see on tühi, luuakse objekt uuesti. Kui see ei ole tühi, kasutatakse olemasolevat.
public class Program
{
static WeakReference wr = new WeakReference(null);
static string SuurObjekt
{
get
{
string result = wr.Target as string;
if (result == null)
wr.Target = result = "Oletame, et see tekst on hästi suur";
return result;
}
}
public static void Main()
{
Console.WriteLine(SuurObjekt);
Console.WriteLine(SuurObjekt);
}
}
public class Program
{
static WeakReference wr = new WeakReference(null);
static string SuurObjekt
{
get
{
string result = wr.Target as string;
if (result == null)
wr.Target = result = "Oletame, et see tekst on hästi suur";
return result;
}
}
public static void Main()
{
Console.WriteLine(SuurObjekt);
Console.WriteLine(SuurObjekt);
}
}Ülaltoodud näites kirjutatakse pulti kaks identset rida. Esimest rida kirjutades loodi sõne, teist rida kirjutades kasutati olemasolevat. Aga kui esimese ja teise kirjutamise vahel oleks olnud palju ressursinõudlikku tegevust, oleks süsteem võibolla otsustanud, seda teksti vahepeal mitte säilitada. Mis on hea, sest nii tagatakse optimaalne ressursikasutus.
Kus seda võiks kasutada?
WeakReference on kasutatav ka Silverlightis. Neid rakendusi, mis seda vajaksid, on suhteliselt vähe. Näiteks aruannete koostamine? Võib juhtuda, et kord koostatud aruannet ei vaadata sessiooni jooksul enam kordagi, samas võib juhtuda, et kasutaja klõpsib sellel 5 korda. Siis võib lasta süsteemil otsustada, kas on vaja see vahepeal mälust ära koristada.
Samalaadset loogikat on kogu aeg kasutatud SQL Serveris. Tee üks keeruline päring, mis võtab 2 sekundit. Seejärel tee sama päring kohe uuesti ja see tuleb hetkega. Tee see 10 minuti pärast ja kiirus sõltub sellest, kui palju on vahepeal tehtud muid päringuid. See näitab, et WeakReference on ülimalt kasulik tööriist veebiserveris, mis peab tagastama vastuseid, mille saamine nõuab mingit tööd, kuid mille salvestamise otstarbekust on väga raske mõõta (kuna kasutajate käitumine on ettearvamatu). WeakReference lubab salvestamise otsustada süsteemil, mis optimeerib end ise.
LINQ
Loend
Loend ehk IEnumerable<T> on liides, mis viitab klassile, mille elemente saab loendada. Loendiks sobivad massiivid (array) ja ka näiteks List<T>, samuti näiteks andmebaasi päringu vastused.
Loend ei tarvitse olla lõplik: näiteks andmebaasist pärides ei ole enne päringu tulemust lõpuni lugedes teada, mitu kirjet see sisaldab.
Loendi tekitamiseks on olemas lause yield return, mis tagastab loendi järgmise elemendi. Alltoodud näites on omadus Integers, mis tagastab lõputult täisarve:
public class Program
{
static IEnumerable<int> Integers
{
get
{
Random r = new Random();
while (true)
yield return r.Next();
}
}
public static void Main()
{
foreach (int i in Integers.Take(10))
Console.WriteLine(i);
}
}
public class Program
{
static IEnumerable<int> Integers
{
get
{
Random r = new Random();
while (true)
yield return r.Next();
}
}
public static void Main()
{
foreach (int i in Integers.Take(10))
Console.WriteLine(i);
}
}Nagu näed, kuigi koodis on while (true), ei jookse programm kinni. Seda sel põhjusel, et loend annab järgmise elemendi alles siis, kui seda reaalselt vaja läheb. Seega, loend ei ole lõplik. See võimaldab olla säästlik.
Toome näite. Kasutaja vaatab otsingumootorist oma otsingu resultaate. Kui ta ei leia neid esimeselt leheküljelt, vaatab ta teist, võibolla ka kolmandat. Vaevalt vaatab ta läbi miljonit otsingutulemust, kuid see on tema valik. Süsteem loob loendi, kuid ei saada kogu loendi sisu kasutajale (seadmata samas kasutajale piiranguid).
Loendi meetodid nagu siin esitletud Take kuuluvad LINQi (Language Integrated Query). Need meetodid on sisuliselt laiendid ehk filtrid, mitte traditsioonilised meetodid. Vahe on selles, et ka neid ei käivitata ilma vajaduseta. Näiteks Take(10) mitte ei võta 10 kirjet, vaid lubab võtta kuni 10 kirjet. Kui me lugemise pärast esimest kirjet katkestame, genereeritaksegi tegelikkuses ainult 1 kirje:
foreach (int i in Integers.Take(10))
{
Console.WriteLine(i);
break;
}
public class Program
{
static IEnumerable<int> Integers
{
get
{
Random r = new Random();
while (true)
yield return r.Next();
}
}
public static void Main()
{
foreach (int i in Integers.Take(10))
{
Console.WriteLine(i);
break;
}
}
}Selles seisnebki loendite ja LINQi võlu. Samas on LINQis laiendeid, mis eeldavad kõigi kirjete läbilugemist, näiteks on seda Count:
Console.WriteLine(Integers.Take(10).Count());
public class Program
{
static IEnumerable<int> Integers
{
get
{
Random r = new Random();
while (true)
yield return r.Next();
}
}
public static void Main()
{
Console.WriteLine(Integers.Take(10).Count());
}
}Kui viimases näites Take välja jätta, käiks tulemuste loendamine lõputult. Antud juhul genereeritakse nii palju kirjeid kui Take lubab ja loetakse nende arv.
LINQ
Koondfunktsioonid
Koondfunktsioon (aggregate function) on funktsioon, mis koondab kirjete grupi üheks tulemuseks. Lihtsaim koondfunktsioon on First(), mis tagastab esimese kirje.
First ja Last
public class Program
{
class Lind
{
public enum Liigid { Varblane = 1, Vares = 2, Kana = 3, Part = 4, Hani = 5, Kurg = 6, Kalkun = 7 };
public Liigid Liik;
public double Kaal;
public override string ToString()
{
return Liik + " " + Kaal.ToString("0.0") + " kg";
}
}
static IEnumerable<Lind> Linnud()
{
Random r = new Random();
for (int i = 0; i < 1000; i++)
{
Lind lind = new Lind { Liik = (Lind.Liigid)(1 + r.Next(7)) };
lind.Kaal = ((double)lind.Liik + r.NextDouble() * (double)lind.Liik) / 4.0;
yield return lind;
}
}
public static void Main()
{
Console.WriteLine(Linnud().First());
}
}
public class Program
{
class Lind
{
public enum Liigid { Varblane = 1, Vares = 2, Kana = 3, Part = 4, Hani = 5, Kurg = 6, Kalkun = 7 };
public Liigid Liik;
public double Kaal;
public override string ToString()
{
return Liik + " " + Kaal.ToString("0.0") + " kg";
}
}
static IEnumerable<Lind> Linnud()
{
Random r = new Random();
for (int i = 0; i < 1000; i++)
{
Lind lind = new Lind { Liik = (Lind.Liigid)(1 + r.Next(7)) };
lind.Kaal = ((double)lind.Liik + r.NextDouble() * (double)lind.Liik) / 4.0;
yield return lind;
}
}
public static void Main()
{
Console.WriteLine(Linnud().First());
}
}Last() tagastab viimase kirje:
Console.WriteLine(Linnud().Last());
public class Program
{
class Lind
{
public enum Liigid { Varblane = 1, Vares = 2, Kana = 3, Part = 4, Hani = 5, Kurg = 6, Kalkun = 7 };
public Liigid Liik;
public double Kaal;
public override string ToString()
{
return Liik + " " + Kaal.ToString("0.0") + " kg";
}
}
static IEnumerable<Lind> Linnud()
{
Random r = new Random();
for (int i = 0; i < 1000; i++)
{
Lind lind = new Lind { Liik = (Lind.Liigid)(1 + r.Next(7)) };
lind.Kaal = ((double)lind.Liik + r.NextDouble() * (double)lind.Liik) / 4.0;
yield return lind;
}
}
public static void Main()
{
Console.WriteLine(Linnud().Last());
}
}FirstOrDefault ja LastOrDefault
First() ja Last() annavad vea, kui ei ole midagi lugeda. FirstOrDefault() ja LastOrDefault() tagastavad vaikeväärtuse (Linnu puhul on see null), kui midagi ei olnud.
Count
Tagastab kirjete arvu loendis. Koondfunktsioonide katsetamiseks loome klassi Lind, millel on väljad Liik ja Kaal. Ja siis teeme juhuslike lindude generaatori. Et linde liiga palju ei tuleks, piirame generaatori tulemuste arvu tuhandega.
Nii, võtame siis lindude koguarvu:
Linnud().Count()
public class Program
{
class Lind
{
public enum Liigid { Varblane = 1, Vares = 2, Kana = 3, Part = 4, Hani = 5, Kurg = 6, Kalkun = 7 };
public Liigid Liik;
public double Kaal;
public override string ToString()
{
return Liik + " " + Kaal.ToString("0.0") + " kg";
}
}
static IEnumerable<Lind> Linnud()
{
Random r = new Random();
for (int i = 0; i < 1000; i++)
{
Lind lind = new Lind { Liik = (Lind.Liigid)(1 + r.Next(7)) };
lind.Kaal = ((double)lind.Liik + r.NextDouble() * (double)lind.Liik) / 4.0;
yield return lind;
}
}
public static void Main()
{
Console.WriteLine(Linnud().Count());
}
}Count() võimaldab lisada ka filtri ehk funktsiooni, mis võtab loendatava objekti ja tagastab false (ära loenda) või true (loenda). Meie kasutame siin lambdaavaldist, et loendada kõik varesed:
Linnud().Count(w => w.Liik == Lind.Liigid.Vares)
public class Program
{
class Lind
{
public enum Liigid { Varblane = 1, Vares = 2, Kana = 3, Part = 4, Hani = 5, Kurg = 6, Kalkun = 7 };
public Liigid Liik;
public double Kaal;
public override string ToString()
{
return Liik + " " + Kaal.ToString("0.0") + " kg";
}
}
static IEnumerable<Lind> Linnud()
{
Random r = new Random();
for (int i = 0; i < 1000; i++)
{
Lind lind = new Lind { Liik = (Lind.Liigid)(1 + r.Next(7)) };
lind.Kaal = ((double)lind.Liik + r.NextDouble() * (double)lind.Liik) / 4.0;
yield return lind;
}
}
public static void Main()
{
Console.WriteLine(Linnud().Count(w => w.Liik == Lind.Liigid.Vares));
}
}Muide, filtri võtavad samamoodi vastu ka First, Last, FirstOrDefault ja LastOrDefault.
Min, Max, Average, Sum
Min() ja Max() tagastavad miinimum- ja maksimumväärtuse. Lind ei ole arvuna võrreldav, seega same tagastada miinimum- või maksimumkaalu:
Linnud().Min(m => m.Kaal)
public class Program
{
class Lind
{
public enum Liigid { Varblane = 1, Vares = 2, Kana = 3, Part = 4, Hani = 5, Kurg = 6, Kalkun = 7 };
public Liigid Liik;
public double Kaal;
public override string ToString()
{
return Liik + " " + Kaal.ToString("0.0") + " kg";
}
}
static IEnumerable<Lind> Linnud()
{
Random r = new Random();
for (int i = 0; i < 1000; i++)
{
Lind lind = new Lind { Liik = (Lind.Liigid)(1 + r.Next(7)) };
lind.Kaal = ((double)lind.Liik + r.NextDouble() * (double)lind.Liik) / 4.0;
yield return lind;
}
}
public static void Main()
{
Console.WriteLine(Linnud().Min(m => m.Kaal));
}
}
Linnud().Max(m => m.Kaal)
public class Program
{
class Lind
{
public enum Liigid { Varblane = 1, Vares = 2, Kana = 3, Part = 4, Hani = 5, Kurg = 6, Kalkun = 7 };
public Liigid Liik;
public double Kaal;
public override string ToString()
{
return Liik + " " + Kaal.ToString("0.0") + " kg";
}
}
static IEnumerable<Lind> Linnud()
{
Random r = new Random();
for (int i = 0; i < 1000; i++)
{
Lind lind = new Lind { Liik = (Lind.Liigid)(1 + r.Next(7)) };
lind.Kaal = ((double)lind.Liik + r.NextDouble() * (double)lind.Liik) / 4.0;
yield return lind;
}
}
public static void Main()
{
Console.WriteLine(Linnud().Max(m => m.Kaal));
}
}Average() tagastab keskmise väärtuse ja Sum() tagastab summa. Ka need vajavad arv-väärtusi:
Linnud().Average(m => m.Kaal)
public class Program
{
class Lind
{
public enum Liigid { Varblane = 1, Vares = 2, Kana = 3, Part = 4, Hani = 5, Kurg = 6, Kalkun = 7 };
public Liigid Liik;
public double Kaal;
public override string ToString()
{
return Liik + " " + Kaal.ToString("0.0") + " kg";
}
}
static IEnumerable<Lind> Linnud()
{
Random r = new Random();
for (int i = 0; i < 1000; i++)
{
Lind lind = new Lind { Liik = (Lind.Liigid)(1 + r.Next(7)) };
lind.Kaal = ((double)lind.Liik + r.NextDouble() * (double)lind.Liik) / 4.0;
yield return lind;
}
}
public static void Main()
{
Console.WriteLine(Linnud().Average(m => m.Kaal));
}
}
Linnud().Sum(m => m.Kaal)
public class Program
{
class Lind
{
public enum Liigid { Varblane = 1, Vares = 2, Kana = 3, Part = 4, Hani = 5, Kurg = 6, Kalkun = 7 };
public Liigid Liik;
public double Kaal;
public override string ToString()
{
return Liik + " " + Kaal.ToString("0.0") + " kg";
}
}
static IEnumerable<Lind> Linnud()
{
Random r = new Random();
for (int i = 0; i < 1000; i++)
{
Lind lind = new Lind { Liik = (Lind.Liigid)(1 + r.Next(7)) };
lind.Kaal = ((double)lind.Liik + r.NextDouble() * (double)lind.Liik) / 4.0;
yield return lind;
}
}
public static void Main()
{
Console.WriteLine(Linnud().Sum(m => m.Kaal));
}
}Aggregate
Oletame, et me tahame leida kõige kergemat lindu. Seda saaks teha nii, et võtame esiteks Min(m => Kaal) ja siis otsime linnu, mis on selle kaaluga, kuid see eeldaks loetelu kahekordset läbitöötamist ja oleks ebaefektiivne. Siin tuleb appi Aggregate, mis võimaldab luua oma koondfunktsiooni.
See koondfunktsioon võtab argumentidena eelmise ja praeguse elemendi ning tagastab tulemuse. Nii tehakse kõigi elementidega. Meie võtame loetelus eelmise ja praeguse linnu. Kui praegune lind on kergem kui eelmine, tagastatakse praegune lind, vastasel juhul tagastatakse eelmine lind. See tagastatud väärtus antakse järgmise elemendi koondfunktsiooni sisendisse. Lõpuks jääb sõelale kõige kergem lind:
Linnud().Aggregate((eelmine, praegune) =>
{
if (eelmine == null || eelmine.Kaal > praegune.Kaal)
return praegune;
return eelmine;
})
public class Program
{
class Lind
{
public enum Liigid { Varblane = 1, Vares = 2, Kana = 3, Part = 4, Hani = 5, Kurg = 6, Kalkun = 7 };
public Liigid Liik;
public double Kaal;
public override string ToString()
{
return Liik + " " + Kaal.ToString("0.0") + " kg";
}
}
static IEnumerable<Lind> Linnud()
{
Random r = new Random();
for (int i = 0; i < 1000; i++)
{
Lind lind = new Lind { Liik = (Lind.Liigid)(1 + r.Next(7)) };
lind.Kaal = ((double)lind.Liik + r.NextDouble() * (double)lind.Liik) / 4.0;
yield return lind;
}
}
public static void Main()
{
Console.WriteLine(Linnud().Aggregate((eelmine, praegune) =>
{
if (eelmine == null || eelmine.Kaal > praegune.Kaal)
return praegune;
return eelmine;
}));
}
}Proovi muuta seda näidet nii, et leida kõige raskem part.
LINQ
Select
Select() võimaldab loendist välju valida enam-vähem samadel põhimõtetel nagu SQLi SELECT. Selle jaoks on mõttekas kasutada lambdaavaldist. Funkstioon võtab kirje ja tagastab valitud info.
Alltoodud näites tagastatakse objektist Lind ainult väli Liik, seega on muutuja liik tüüpi Lind.Liigid:
class Lind
{
public enum Liigid { Varblane = 1, Vares = 2, Kana = 3, Part = 4, Hani = 5, Kurg = 6, Kalkun = 7 };
public Liigid Liik;
public double Kaal;
public override string ToString()
{
return Liik + " " + Kaal.ToString("0.0") + " kg";
}
}
static IEnumerable<Lind> Linnud()
{
Random r = new Random();
for (int i = 0; i < 1000; i++)
{
Lind lind = new Lind { Liik = (Lind.Liigid)(1 + r.Next(7)) };
lind.Kaal = ((double)lind.Liik + r.NextDouble() * (double)lind.Liik) / 4.0;
yield return lind;
}
}
public static void Main()
{
foreach (var liik in Linnud().Select(s => s.Liik))
Console.WriteLine(liik);
}
public class Program
{
class Lind
{
public enum Liigid { Varblane = 1, Vares = 2, Kana = 3, Part = 4, Hani = 5, Kurg = 6, Kalkun = 7 };
public Liigid Liik;
public double Kaal;
public override string ToString()
{
return Liik + " " + Kaal.ToString("0.0") + " kg";
}
}
static IEnumerable<Lind> Linnud()
{
Random r = new Random();
for (int i = 0; i < 1000; i++)
{
Lind lind = new Lind { Liik = (Lind.Liigid)(1 + r.Next(7)) };
lind.Kaal = ((double)lind.Liik + r.NextDouble() * (double)lind.Liik) / 4.0;
yield return lind;
}
}
public static void Main()
{
foreach (var liik in Linnud().Select(s => s.Liik))
Console.WriteLine(liik);
}
}Ja selles näites tagastame lindude kaalu naelades:
foreach (var lbs in Linnud().Select(s => s.Kaal * 2.2046))
Console.WriteLine(lbs);
public class Program
{
class Lind
{
public enum Liigid { Varblane = 1, Vares = 2, Kana = 3, Part = 4, Hani = 5, Kurg = 6, Kalkun = 7 };
public Liigid Liik;
public double Kaal;
public override string ToString()
{
return Liik + " " + Kaal.ToString("0.0") + " kg";
}
}
static IEnumerable<Lind> Linnud()
{
Random r = new Random();
for (int i = 0; i < 1000; i++)
{
Lind lind = new Lind { Liik = (Lind.Liigid)(1 + r.Next(7)) };
lind.Kaal = ((double)lind.Liik + r.NextDouble() * (double)lind.Liik) / 4.0;
yield return lind;
}
}
public static void Main()
{
foreach (var lbs in Linnud().Select(s => s.Kaal * 2.20462262))
Console.WriteLine(lbs);
}
}Tagastada võib ka anonüümse tüübi:
foreach (var lbs in Linnud().Select(s => new { Liik = s.Liik, Lbs = s.Kaal * 2.2046 }))
Console.WriteLine(lbs.Liik + ": " + lbs.Lbs.ToString("0.0") + " lbs");
public class Program
{
class Lind
{
public enum Liigid { Varblane = 1, Vares = 2, Kana = 3, Part = 4, Hani = 5, Kurg = 6, Kalkun = 7 };
public Liigid Liik;
public double Kaal;
public override string ToString()
{
return Liik + " " + Kaal.ToString("0.0") + " kg";
}
}
static IEnumerable<Lind> Linnud()
{
Random r = new Random();
for (int i = 0; i < 1000; i++)
{
Lind lind = new Lind { Liik = (Lind.Liigid)(1 + r.Next(7)) };
lind.Kaal = ((double)lind.Liik + r.NextDouble() * (double)lind.Liik) / 4.0;
yield return lind;
}
}
public static void Main()
{
foreach (var lbs in Linnud().Select(s => new {Liik = s.Liik, Lbs = s.Kaal * 2.2046}))
Console.WriteLine(lbs.Liik + ": " + lbs.Lbs.ToString("0.0") + " lbs");
}
}SelectMany
Kui tulemus sisaldab IEnumerable<T> tüüpi väljasid, siis võib nendest kombineerida uue loendi, kasutades meetodit SelectMany().
Kui teha sõnele ToCharArray(), saame kõik tähemärgid massiivina. Nendest üksikustest massiividest võime koostada loendi:
foreach (char c in Linnud().SelectMany(s => s.Liik.ToString().ToCharArray()))
Console.Write(c);
public class Program
{
class Lind
{
public enum Liigid { Varblane = 1, Vares = 2, Kana = 3, Part = 4, Hani = 5, Kurg = 6, Kalkun = 7 };
public Liigid Liik;
public double Kaal;
public override string ToString()
{
return Liik + " " + Kaal.ToString("0.0") + " kg";
}
}
static IEnumerable<Lind> Linnud()
{
Random r = new Random();
for (int i = 0; i < 1000; i++)
{
Lind lind = new Lind { Liik = (Lind.Liigid)(1 + r.Next(7)) };
lind.Kaal = ((double)lind.Liik + r.NextDouble() * (double)lind.Liik) / 4.0;
yield return lind;
}
}
public static void Main()
{
foreach (char c in Linnud().SelectMany(s => s.Liik.ToString().ToCharArray()))
Console.Write(c);
}
}LINQ
Where
Where on filter, mis laseb läbi ainult need kirjed, mis vastavad tingimusele. See võtab (näiteks lambdaavaldisena) funktsiooni, mille argumendiks on kirje ja mis tagastab true/false (kirje sobib/ei sobi).
public class Program
{
class Lind
{
public enum Liigid { Varblane = 1, Vares = 2, Kana = 3, Part = 4, Hani = 5, Kurg = 6, Kalkun = 7 };
public Liigid Liik;
public double Kaal;
public override string ToString()
{
return Liik + " " + Kaal.ToString("0.0") + " kg";
}
}
static IEnumerable<Lind> Linnud()
{
Random r = new Random();
for (int i = 0; i < 1000; i++)
{
Lind lind = new Lind { Liik = (Lind.Liigid)(1 + r.Next(7)) };
lind.Kaal = ((double)lind.Liik + r.NextDouble() * (double)lind.Liik) / 4.0;
yield return lind;
}
}
public static void Main()
{
foreach (var lind in Linnud().Where(w => w.Kaal > 1.0))
Console.WriteLine(lind);
}
}
public class Program
{
class Lind
{
public enum Liigid { Varblane = 1, Vares = 2, Kana = 3, Part = 4, Hani = 5, Kurg = 6, Kalkun = 7 };
public Liigid Liik;
public double Kaal;
public override string ToString()
{
return Liik + " " + Kaal.ToString("0.0") + " kg";
}
}
static IEnumerable<Lind> Linnud()
{
Random r = new Random();
for (int i = 0; i < 1000; i++)
{
Lind lind = new Lind { Liik = (Lind.Liigid)(1 + r.Next(7)) };
lind.Kaal = ((double)lind.Liik + r.NextDouble() * (double)lind.Liik) / 4.0;
yield return lind;
}
}
public static void Main()
{
foreach (var lind in Linnud().Where(w => w.Kaal > 1.0))
Console.WriteLine(lind);
}
}See funktsioon võib näiteks otsida teisest loendist ja lubada läbi need kirjed, mis kattuvad:
Lind[] esimene = Linnud().ToArray();
foreach (var lind in Linnud().Where(w => esimene.Any(a => a.Liik == w.Liik && a.Kaal == w.Kaal)))
Console.WriteLine(lind);
public class Program
{
class Lind
{
public enum Liigid { Varblane = 1, Vares = 2, Kana = 3, Part = 4, Hani = 5, Kurg = 6, Kalkun = 7 };
public Liigid Liik;
public double Kaal;
public override string ToString()
{
return Liik + " " + Kaal.ToString("0.0") + " kg";
}
}
static IEnumerable<Lind> Linnud()
{
Random r = new Random();
for (int i = 0; i < 1000; i++)
{
Lind lind = new Lind { Liik = (Lind.Liigid)(1 + r.Next(7)) };
lind.Kaal = ((double)lind.Liik + r.NextDouble() * (double)lind.Liik) / 4.0;
yield return lind;
}
}
public static void Main()
{
Lind[] esimene = Linnud().ToArray();
foreach (var lind in Linnud().Where(w => esimene.Any(a => a.Liik == w.Liik && a.Kaal == w.Kaal)))
Console.WriteLine(lind);
}
}Where'il on üks erim, mis võimaldab saada funktsiooniga kaasa kirje numbri: (kirje, number) => true/false. Siin on üks kasutu näide, aga see tagastab iga kümnenda kirje:
foreach (var lind in Linnud().Where((w, n) => n % 10 == 0))
Console.WriteLine(lind);
public class Program
{
class Lind
{
public enum Liigid { Varblane = 1, Vares = 2, Kana = 3, Part = 4, Hani = 5, Kurg = 6, Kalkun = 7 };
public Liigid Liik;
public double Kaal;
public override string ToString()
{
return Liik + " " + Kaal.ToString("0.0") + " kg";
}
}
static IEnumerable<Lind> Linnud()
{
Random r = new Random();
for (int i = 0; i < 1000; i++)
{
Lind lind = new Lind { Liik = (Lind.Liigid)(1 + r.Next(7)) };
lind.Kaal = ((double)lind.Liik + r.NextDouble() * (double)lind.Liik) / 4.0;
yield return lind;
}
}
public static void Main()
{
foreach (var lind in Linnud().Where((w, n) => n % 10 == 0))
Console.WriteLine(lind);
}
}Selle asemel, et sa peaksid tegema Where().First(), Where().FirstOrDefault(), Where().Last(), Where().LastOrDefault() või Where().Count(), aktsepteerivad need meetodid samasugust filtri funktsiooni nagu Where(). Seega,
Console.WriteLine(Linnud().Where(w => w.Liik == Lind.Liigid.Vares).FirstOrDefault());
public class Program
{
class Lind
{
public enum Liigid { Varblane = 1, Vares = 2, Kana = 3, Part = 4, Hani = 5, Kurg = 6, Kalkun = 7 };
public Liigid Liik;
public double Kaal;
public override string ToString()
{
return Liik + " " + Kaal.ToString("0.0") + " kg";
}
}
static IEnumerable<Lind> Linnud()
{
Random r = new Random();
for (int i = 0; i < 1000; i++)
{
Lind lind = new Lind { Liik = (Lind.Liigid)(1 + r.Next(7)) };
lind.Kaal = ((double)lind.Liik + r.NextDouble() * (double)lind.Liik) / 4.0;
yield return lind;
}
}
public static void Main()
{
Console.WriteLine(Linnud().Where(w => w.Liik == Lind.Liigid.Vares).FirstOrDefault());
}
}saab lühemalt kirjutada nii:
Console.WriteLine(Linnud().FirstOrDefault(w => w.Liik == Lind.Liigid.Vares));
public class Program
{
class Lind
{
public enum Liigid { Varblane = 1, Vares = 2, Kana = 3, Part = 4, Hani = 5, Kurg = 6, Kalkun = 7 };
public Liigid Liik;
public double Kaal;
public override string ToString()
{
return Liik + " " + Kaal.ToString("0.0") + " kg";
}
}
static IEnumerable<Lind> Linnud()
{
Random r = new Random();
for (int i = 0; i < 1000; i++)
{
Lind lind = new Lind { Liik = (Lind.Liigid)(1 + r.Next(7)) };
lind.Kaal = ((double)lind.Liik + r.NextDouble() * (double)lind.Liik) / 4.0;
yield return lind;
}
}
public static void Main()
{
Console.WriteLine(Linnud().FirstOrDefault(w => w.Liik == Lind.Liigid.Vares));
}
}Võibolla sa ei tule selle peale. Seepärast toon välja, et lambdaavaldises võib olla ka keerukam programmijupp, milles saab tulemuse tagastada returni abil:
Console.WriteLine(Linnud().Count(w =>
{
switch (w.Liik)
{
case Lind.Liigid.Vares:
return w.Kaal > 1.0;
case Lind.Liigid.Varblane:
return w.Kaal < 1.0;
default:
return false;
}
}));
public class Program
{
class Lind
{
public enum Liigid { Varblane = 1, Vares = 2, Kana = 3, Part = 4, Hani = 5, Kurg = 6, Kalkun = 7 };
public Liigid Liik;
public double Kaal;
public override string ToString()
{
return Liik + " " + Kaal.ToString("0.0") + " kg";
}
}
static IEnumerable<Lind> Linnud()
{
Random r = new Random();
for (int i = 0; i < 1000; i++)
{
Lind lind = new Lind { Liik = (Lind.Liigid)(1 + r.Next(7)) };
lind.Kaal = ((double)lind.Liik + r.NextDouble() * (double)lind.Liik) / 4.0;
yield return lind;
}
}
public static void Main()
{
Console.WriteLine(Linnud().Count(w =>
{
switch (w.Liik)
{
case Lind.Liigid.Vares:
return w.Kaal > 1.0;
case Lind.Liigid.Varblane:
return w.Kaal < 1.0;
default:
return false;
}
}));
}
}LINQ
GroupBy
GroupBy võimaldab grupeerida tulemusi välja järgi samamoodi nagu SQLi GROUP BY. Tulemus on loetelu objektidest, millel on omadus Key ja mis sisaldab omakorda gruppi kuuluvaid kirjeid.
Vaata järgmist näidet, kus me saame iga liigi arvu:
public class Program
{
class Lind
{
public enum Liigid { Varblane = 1, Vares = 2, Kana = 3, Part = 4, Hani = 5, Kurg = 6, Kalkun = 7 };
public Liigid Liik;
public double Kaal;
public override string ToString()
{
return Liik + " " + Kaal.ToString("0.0") + " kg";
}
}
static IEnumerable<Lind> Linnud()
{
Random r = new Random();
for (int i = 0; i < 1000; i++)
{
Lind lind = new Lind { Liik = (Lind.Liigid)(1 + r.Next(7)) };
lind.Kaal = ((double)lind.Liik + r.NextDouble() * (double)lind.Liik) / 4.0;
yield return lind;
}
}
public static void Main()
{
foreach (var g in Linnud().GroupBy(w => w.Liik))
Console.WriteLine(g.Key + " " + g.Count());
}
}
public class Program
{
class Lind
{
public enum Liigid { Varblane = 1, Vares = 2, Kana = 3, Part = 4, Hani = 5, Kurg = 6, Kalkun = 7 };
public Liigid Liik;
public double Kaal;
public override string ToString()
{
return Liik + " " + Kaal.ToString("0.0") + " kg";
}
}
static IEnumerable<Lind> Linnud()
{
Random r = new Random();
for (int i = 0; i < 1000; i++)
{
Lind lind = new Lind { Liik = (Lind.Liigid)(1 + r.Next(7)) };
lind.Kaal = ((double)lind.Liik + r.NextDouble() * (double)lind.Liik) / 4.0;
yield return lind;
}
}
public static void Main()
{
foreach (var g in Linnud().GroupBy(w => w.Liik))
Console.WriteLine(g.Key + " " + g.Count());
}
}Nagu näed, määrasime, et grupeering tehakse välja Liik põhjal, mis on tulemustes kättesaadav nime all Key. Muus osas käitub tulemus samamoodi nagu tavaline loend ja seda saab ka niimoodi lugeda. Järgmises näites toome esiteks välja liigi ja seejärel loetleme liigi grupis olevad isendid:
foreach (var g in Linnud().GroupBy(w => w.Liik))
{
Console.WriteLine(g.Key);
foreach (var v in g)
Console.WriteLine(" " + v);
}
public class Program
{
class Lind
{
public enum Liigid { Varblane = 1, Vares = 2, Kana = 3, Part = 4, Hani = 5, Kurg = 6, Kalkun = 7 };
public Liigid Liik;
public double Kaal;
public override string ToString()
{
return Liik + " " + Kaal.ToString("0.0") + " kg";
}
}
static IEnumerable<Lind> Linnud()
{
Random r = new Random();
for (int i = 0; i < 100; i++)
{
Lind lind = new Lind { Liik = (Lind.Liigid)(1 + r.Next(7)) };
lind.Kaal = ((double)lind.Liik + r.NextDouble() * (double)lind.Liik) / 4.0;
yield return lind;
}
}
public static void Main()
{
foreach (var g in Linnud().GroupBy(w => w.Liik))
{
Console.WriteLine(g.Key);
foreach (var v in g)
Console.WriteLine(" " + v);
}
}
}Üsna lihtne, eks ole. Tasub mainida, et GroupBy lambdaavaldis, mis praegu tagastab w.Liik, võib tagastada mida iganes (mingi objekti või näiteks sõne, mis on kombineeritud mitmest väljast). Tähtis oleks, et väärtused oleksid võrreldavad. Kui väärtused ei ole võrreldavad, võid kaasa anda võrdleja, mis on IEqualityComparer<T> tüüpi, ja grupeerida ikkagi.
Ja loomulikult võib pärast ühte grupeerimisastet uuesti grupeerida:
foreach (var g in Linnud().GroupBy(w => w.Liik))
{
Console.WriteLine(g.Key);
foreach (var v in g.GroupBy(w => Math.Round(w.Kaal)))
Console.WriteLine(" " + v.Key + " kg: " + v.Count() + " tk");
}
public class Program
{
class Lind
{
public enum Liigid { Varblane = 1, Vares = 2, Kana = 3, Part = 4, Hani = 5, Kurg = 6, Kalkun = 7 };
public Liigid Liik;
public double Kaal;
public override string ToString()
{
return Liik + " " + Kaal.ToString("0.0") + " kg";
}
}
static IEnumerable<Lind> Linnud()
{
Random r = new Random();
for (int i = 0; i < 100; i++)
{
Lind lind = new Lind { Liik = (Lind.Liigid)(1 + r.Next(7)) };
lind.Kaal = ((double)lind.Liik + r.NextDouble() * (double)lind.Liik) / 4.0;
yield return lind;
}
}
public static void Main()
{
foreach (var g in Linnud().GroupBy(w => w.Liik))
{
Console.WriteLine(g.Key);
foreach (var v in g.GroupBy(w => Math.Round(w.Kaal)))
Console.WriteLine(" " + v.Key + " kg: " + v.Count() + " tk");
}
}
}Kui mõtelda, kui palju koodi sellise asja kirjutamiseks muidu vaja läheks, teeb ikka tuju heaks küll.
LINQ
LINQ to XML
LINQ-teek XMLi jaoks ehk XLINQ on DLLis System.Xml.Linq.dll ja see tuleb eraldi lisada. Meie projektile on see juba lisatud ja kasutusvalmis.
Kaks põhilist klassi on XDocument ja XElement. XDocument võimaldab lugeda XMLi veebiaadressilt (Uri), voost (Stream) ja sõnest (string). Silverlighti versioonis on veebiaadressilt lugemine paraku piiratud relatiivsete aadressidega, seetõttu kasutame oma näiteks WebClienti abi.
XDocument on XML-dokument ja XElementid on kõik, mis leiduvad selle sees, alates XDocument.Root.
XElement.Descendants() tagastab kõik lapsed elemendi puus. See võtab valikulise parameetrina elemendi nime (Name), kuid kui kasutatakse nimeruume, siis sisaldub nimes ka nimeruum. Sageli on lihtsam teha Descendands().Where(w => w.Name.LocalName == "comments").
XElement.Elements() tagastab esimese astme elemendid ja Element("title") tagastab esimese astme elemendi nimega "title".
Sama loogikaga on olemas ka XElement.Attributes(). Sisu saab kätte omadusega Value.
Siin siis kasutusnäide, kus me loome klassi Rss, mis sisaldab uudisvoo põhilisi elemente, ja tekitame nende klasside voo veebiaadressist, kasutades XMLi lugemiseks XDocument klassi ja XLINQi:
public class Program
{
class Rss
{
public string Title;
public DateTime PubDate;
public static void Load(string address, Action<IEnumerable<Rss>> a)
{
WebClient wc = new WebClient();
wc.DownloadStringCompleted += (sender, e) => a
(
XDocument
.Parse(e.Result)
.Descendants("item")
.Select(s => new Rss{ Title = s.Element("title").Value, PubDate = DateTime.Parse(s.Element("pubDate").Value) })
);
wc.DownloadStringAsync(new Uri(address, UriKind.Absolute));
}
}
public static void Main()
{
Rss.Load("http://rss.eneta.ee/eneta-foorum", rss =>
{
foreach (var r in rss.OrderByDescending(o => o.PubDate))
Console.WriteLine(r.Title + " (" + r.PubDate + ")");
});
}
}
public class Program
{
class Rss
{
public string Title;
public DateTime PubDate;
public static void Load(string address, Action<IEnumerable<Rss>> a)
{
WebClient wc = new WebClient();
wc.DownloadStringCompleted += (sender, e) => a
(
XDocument
.Parse(e.Result)
.Descendants("item")
.Select(s => new Rss{ Title = s.Element("title").Value, PubDate = DateTime.Parse(s.Element("pubDate").Value) })
);
wc.DownloadStringAsync(new Uri(address, UriKind.Absolute));
}
}
public static void Main()
{
Rss.Load("http://rss.eneta.ee/eneta-foorum", rss =>
{
foreach (var r in rss.OrderByDescending(o => o.PubDate))
Console.WriteLine(r.Title + " (" + r.PubDate + ")");
});
}
}Muidugi on kõik omadused ka kirjutatavad ja XLINQi saab edukalt kasutada ka XML-dokumendi kirjutamiseks.
LINQ
Otstarbekusest
LINQi erinevaid võimalusi kasutades tuleb alati meeles pidada otstarbekust. Ma ei räägi siin 100 kirjest, vaid olukorrast, kus neid on rohkem, ja õige meetodi kasutamisest sõltub programmi võimekus.
Count() vs Any()
On normaalne, et selleks, et teada saada, kas sõne sisaldab midagi, küsitakse s.Length > 0 ja sama kehtib ka näiteks massiivide kohta. Aga LINQi puhul peab kasutama õiget meetodit. Alltoodud näide peab läbi käima kõik kirjed:
public class Program
{
class Lind
{
public enum Liigid { Varblane = 1, Vares = 2, Kana = 3, Part = 4, Hani = 5, Kurg = 6, Kalkun = 7 };
public Liigid Liik;
public double Kaal;
public override string ToString()
{
return Liik + " " + Kaal.ToString("0.0") + " kg";
}
}
static IEnumerable<Lind> Linnud()
{
Random r = new Random();
for (int i = 0; i < 1000; i++)
{
Lind lind = new Lind { Liik = (Lind.Liigid)(1 + r.Next(7)) };
lind.Kaal = ((double)lind.Liik + r.NextDouble() * (double)lind.Liik) / 4.0;
yield return lind;
}
}
public static void Main()
{
if (Linnud().Count() > 0)
Console.WriteLine("Linde on");
}
}
public class Program
{
class Lind
{
public enum Liigid { Varblane = 1, Vares = 2, Kana = 3, Part = 4, Hani = 5, Kurg = 6, Kalkun = 7 };
public Liigid Liik;
public double Kaal;
public override string ToString()
{
return Liik + " " + Kaal.ToString("0.0") + " kg";
}
}
static IEnumerable<Lind> Linnud()
{
Random r = new Random();
for (int i = 0; i < 1000; i++)
{
Lind lind = new Lind { Liik = (Lind.Liigid)(1 + r.Next(7)) };
lind.Kaal = ((double)lind.Liik + r.NextDouble() * (double)lind.Liik) / 4.0;
yield return lind;
}
}
public static void Main()
{
if (Linnud().Count() > 0)
Console.WriteLine("Linde on");
}
}Seda sellepärast, et IEnumerable ei tea oma kirjete arvu enne kui kõik on läbi käidud. Õige oleks siinkohal kasutada meetodit Any, mis tagastab tulemuse kohe pärast esimese kirje leidmist:
if (Linnud().Any())
Console.WriteLine("Linde on");
public class Program
{
class Lind
{
public enum Liigid { Varblane = 1, Vares = 2, Kana = 3, Part = 4, Hani = 5, Kurg = 6, Kalkun = 7 };
public Liigid Liik;
public double Kaal;
public override string ToString()
{
return Liik + " " + Kaal.ToString("0.0") + " kg";
}
}
static IEnumerable<Lind> Linnud()
{
Random r = new Random();
for (int i = 0; i < 1000; i++)
{
Lind lind = new Lind { Liik = (Lind.Liigid)(1 + r.Next(7)) };
lind.Kaal = ((double)lind.Liik + r.NextDouble() * (double)lind.Liik) / 4.0;
yield return lind;
}
}
public static void Main()
{
if (Linnud().Any())
Console.WriteLine("Linde on");
}
}LINQi kasutades peab endale pidevalt teadvustama, kas teatud meetod peab kõik kirjed läbi käima või mitte. Sellest sõltub meetodite kasutamise järjekord.
OrderBy
SQLis on andmebaasimootori mure, mis järjestuses ta mingeid käsklusi täidab, aga LINQis võib valesti teha. Võib öelda ka nii, et SQL ei ole programmeerimine, aga LINQi puhul tuleb meeles pidada, et sa oled programmeerija. Vaata seda:
foreach (var lind in Linnud().OrderBy(o => o.Kaal).Where(w => w.Liik == Lind.Liigid.Vares))
Console.WriteLine(lind);
public class Program
{
class Lind
{
public enum Liigid { Varblane = 1, Vares = 2, Kana = 3, Part = 4, Hani = 5, Kurg = 6, Kalkun = 7 };
public Liigid Liik;
public double Kaal;
public override string ToString()
{
return Liik + " " + Kaal.ToString("0.0") + " kg";
}
}
static IEnumerable<Lind> Linnud()
{
Random r = new Random();
for (int i = 0; i < 1000; i++)
{
Lind lind = new Lind { Liik = (Lind.Liigid)(1 + r.Next(7)) };
lind.Kaal = ((double)lind.Liik + r.NextDouble() * (double)lind.Liik) / 4.0;
yield return lind;
}
}
public static void Main()
{
foreach (var lind in Linnud().OrderBy(o => o.Kaal).Where(w => w.Liik == Lind.Liigid.Vares))
Console.WriteLine(lind);
}
}Mis on ülaltoodud näites valesti? Esiteks järjestatakse kõik linnud kaalu järgi ja seejärel valitakse neist ainult varesed. Ülejäänud lindude järjestamine on ajaraisk. Õige oleks teha nii:
foreach (var lind in Linnud().Where(w => w.Liik == Lind.Liigid.Vares).OrderBy(o => o.Kaal))
Console.WriteLine(lind);
public class Program
{
class Lind
{
public enum Liigid { Varblane = 1, Vares = 2, Kana = 3, Part = 4, Hani = 5, Kurg = 6, Kalkun = 7 };
public Liigid Liik;
public double Kaal;
public override string ToString()
{
return Liik + " " + Kaal.ToString("0.0") + " kg";
}
}
static IEnumerable<Lind> Linnud()
{
Random r = new Random();
for (int i = 0; i < 1000; i++)
{
Lind lind = new Lind { Liik = (Lind.Liigid)(1 + r.Next(7)) };
lind.Kaal = ((double)lind.Liik + r.NextDouble() * (double)lind.Liik) / 4.0;
yield return lind;
}
}
public static void Main()
{
foreach (var lind in Linnud().Where(w => w.Liik == Lind.Liigid.Vares).OrderBy(o => o.Kaal))
Console.WriteLine(lind);
}
}LINQ võimaldab kirjutada üksteise otsa lõputult andmetega opereerimise käsklusi. Kogu programmi jõudlus võib suuresti sõltuda sellest, mis järjekorras need täidetakse. Lihtne reegel on teha kulukamaid protseduure väiksema hulga andmetega ehk pärast kõiki filtreerimisi. Kui andmete filter kasutab objektide omadusi, mille väärtuse saamine eeldab arvutusi (nagu näiteks kuupäevaarvutused), oleks mõttekas grupeerida käsud selliselt, et neid arvutusi tuleks iga kirje kohta teha ainult üks kord.
Where
LINQis Where'i kasutades tuleb arvestada, et lambdaavaldisena antud funktsiooni käivitatakse iga kirje puhul. Kas sealsed arvutused on otstarbekad?
Lisame linnule näiteks kaalu naelades ja siis leiame linnud, kes on raskemad kui 2 naela:
class Lind
{
public enum Liigid { Varblane = 1, Vares = 2, Kana = 3, Part = 4, Hani = 5, Kurg = 6, Kalkun = 7 };
public Liigid Liik;
public double Kaal;
public override string ToString()
{
return Liik + " " + Kaal.ToString("0.0") + " kg";
}
public double Naelades
{
get
{
return Kaal * 2.20462262;
}
}
}
public static void Main()
{
foreach (var lind in Linnud().Where(w => w.Naelades > 2))
Console.WriteLine(lind);
}
public class Program
{
class Lind
{
public enum Liigid { Varblane = 1, Vares = 2, Kana = 3, Part = 4, Hani = 5, Kurg = 6, Kalkun = 7 };
public Liigid Liik;
public double Kaal;
public override string ToString()
{
return Liik + " " + Kaal.ToString("0.0") + " kg";
}
public double Naelades
{
get
{
return Kaal * 2.20462262;
}
}
}
static IEnumerable<Lind> Linnud()
{
Random r = new Random();
for (int i = 0; i < 1000; i++)
{
Lind lind = new Lind { Liik = (Lind.Liigid)(1 + r.Next(7)) };
lind.Kaal = ((double)lind.Liik + r.NextDouble() * (double)lind.Liik) / 4.0;
yield return lind;
}
}
public static void Main()
{
foreach (var lind in Linnud().Where(w => w.Naelades > 2))
Console.WriteLine(lind);
}
}Haa, see on mugav küll, aga eeldab iga linnu puhul kaalu naeladesse arvestamist, et teda filtreerida. Meie näite puhul pole erilist vahet, aga kui andmeid on palju, on vahe olemas (ja võib olla päris suur).
Mis oleks siis õige lähenemine? Noh, arvestame esiteks, mitu kilo on 2 naela, salvestame selle väärtuse muutujasse ja siis filtreerime kilode järgi:
var kilodes = 2.20462262;
foreach (var lind in Linnud().Where(w => w.Kaal > kilodes))
Console.WriteLine(lind);
public class Program
{
class Lind
{
public enum Liigid { Varblane = 1, Vares = 2, Kana = 3, Part = 4, Hani = 5, Kurg = 6, Kalkun = 7 };
public Liigid Liik;
public double Kaal;
public override string ToString()
{
return Liik + " " + Kaal.ToString("0.0") + " kg";
}
}
static IEnumerable<Lind> Linnud()
{
Random r = new Random();
for (int i = 0; i < 1000; i++)
{
Lind lind = new Lind { Liik = (Lind.Liigid)(1 + r.Next(7)) };
lind.Kaal = ((double)lind.Liik + r.NextDouble() * (double)lind.Liik) / 4.0;
yield return lind;
}
}
public static void Main()
{
var kilodes = 2.20462262;
foreach (var lind in Linnud().Where(w => w.Kaal > kilodes))
Console.WriteLine(lind);
}
}Kokkuvõtteks, mõistus tuleb appi võtta. Taolisi lihtsaid asju tähele pannes võib kiiruse vahe olla tuhandekordne. LINQ on väga mugav ja sellest satub kiiresti sõltuvusse, aga laisaks ei tohi minna. Lülita ikka aju sisse.
Graafiline kasutajaliides
Hello World
Graafilise kasutajaliidese osas käsitleme Silverlight/WPF-tüüpi liidest. Need tööriistad võimaldavad kasutada kujunduse jaoks ka XAML-keelt, aga meil siin on C#-koodilabor, seega vaatame, kuidas teha seda puhtalt C# abil.
Käesolev keskkond oskab luua akent, millesse paneb sisu, mis on päritud klassist UIElement. Kõik graafilise kasutajaliidese visuaalsed elemendid on päritud sellest. Käivita esimene proov (avaneva akna saad kinni panna nurgas olevast X-st):
public class Proov: UserControl
{
public Proov()
{
Content = new TextBlock
{
Text = "Hello World!",
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
};
}
}
public class Proov: UserControl
{
public Proov()
{
Content = new TextBlock
{
Text = "Hello World!",
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
};
}
}Nagu näed, nii lihtne see ongi. UserControl on üldkasutatav põhjaks mõeldud element. TextBlock on teksti kuvamiseks mõeldud element. Sellel on omadus Text, mis sisaldab teadagi mida. Kaks ülejäänud omadust on graafilistele elementidele üldomased, võimaldades väärtusi Left, Center, Right ja Stretch (viimane venitab elemendi kogu ruumi peale).
WPF-standard on lihtne selle poolest, et läbi kõigi elementide on püütud järgida sama joont. Ainuke suurem erand on see, et mõne elemendi sisu on nimega Content (näiteks UserControl ja Button), mõnel on Child (näiteks Border) ja mõnel on Text (näiteks TextBlock ja TextBox).
Vajalikud omadused on veel Margin (perimeetri laius elemendi ümber) ja Padding (elemendi sisu ümber oleva täidise mõõtmed).
Tsekka seda näidet:
public class Proov: Grid
{
public Proov()
{
Children.Add(new TextBlock
{
Text = "Akna sulgemiseks klõpsa nupul",
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Margin = new Thickness(0, -40, 0, 0)
});
Button b = new Button
{
Content = "Sulge",
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Padding = new Thickness(4)
};
b.Click += delegate
{
(Parent as ChildWindow).DialogResult = false;
};
Children.Add(b);
}
}
public class Proov: Grid
{
public Proov()
{
Children.Add(new TextBlock
{
Text = "Akna sulgemiseks klõpsa nupul",
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Margin = new Thickness(0, -40, 0, 0)
});
Button b = new Button
{
Content = "Sulge",
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Padding = new Thickness(4)
};
b.Click += delegate
{
(Parent as ChildWindow).DialogResult = false;
};
Children.Add(b);
}
}See element Proov on päritud elemendist Grid. Sellel on UserControliga oluline vahe: kui UserControl.Content võimaldab sisaldada ainult ühte elementi (mille sees võib loomulikult olla teisi), siis Grid võimaldab lisada piiramatul arvul lapsi omadusse Children. Me lisamegi esiteks TextBlocki ja teiseks Buttoni.
Pane tähele, et mõlemad on paigutatud keskele, ometi ei ole nad kattuvad, sest TextBlock.Margin = new Thickness(0, -40, 0, 0). Margin nihutab teksti -40 pikseli võrra alla ehk tegelikult 40 pikseli võrra üles (Thickness võtab argumentidena left, top, right, bottom).
Buttonile on antud Content, mis on tekst (aga tegelikult võib sisaldada ka suvalist graafilist elementi). Button.Padding teeb nuppu natuke suuremaks, et ta ei oleks liiga tihedalt teksti ümber. Nagu näed, võib Thicknessi defineerida ka ühe arvuna, mis on siis võrdne kõigis külgedes.
Olgu lisatud, et kõik WPF-koordinaadid on alati ja ainult pikselites ning alati ja ainult reaalarvudena (double). Seega võib kõiki elemente paigutada ka näiteks poole pikseli peale (mitte et see alati ilus oleks :)
Nupu sündmusele Click (klõpsa) on lisatud protseduur, mis sulgeb akna. Akna sulgemine on loomulikult sõltuv sellest, millise sisu sees graafiline element parasjagu asub, aga antud juhtumil genereeritakse meie koodilaboris alati ChildWindow tüüpi aken.
Graafiline kasutajaliides
StackPanel
StackPanel ehk virnpaneel on tähtis vahend elementide paigutamiseks. Elemente saab virnastada vertikaalselt:
public class My: StackPanel
{
public My()
{
Orientation = Orientation.Vertical;
Children.Add(new TextBlock { Text = "Esimene" });
Children.Add(new TextBlock { Text = "Teine" });
Children.Add(new TextBlock { Text = "Kolmas" });
}
}
public class My: StackPanel
{
public My()
{
Orientation = Orientation.Vertical;
Children.Add(new TextBlock { Text = "Esimene" });
Children.Add(new TextBlock { Text = "Teine" });
Children.Add(new TextBlock { Text = "Kolmas" });
}
}või horisontaalselt:
Orientation = Orientation.Horizontal;
public class My: StackPanel
{
public My()
{
Orientation = Orientation.Horizontal;
Children.Add(new TextBlock { Text = "Esimene" });
Children.Add(new TextBlock { Text = "Teine" });
Children.Add(new TextBlock { Text = "Kolmas" });
}
}Vertikaalse paigutuse korral on käitumine sarnane HTMLi ühetulbalise tabeliga ja horisontaalse korral üherealise tabeliga.
WPFi eripära on, et elementidel ei ole Paddingu ja Margini vaikeväärtusi (need on alati nullis) ning ühelgi elemendil, mis võimaldab lapsi lisada, ei ole elementidevaheliste vahede määramise võimalust (nagu cellpadding HTMLi tabelis). Seega on meie teises näites tekstid täiesti üksteise otsas. See ei ole halb, sest igale elemendile saab Margini ise määratleda, kuigi vahel võib see tunduda tülikas. Paneme järgmises näites igale elemendile parempoolse Margini:
Orientation = Orientation.Horizontal;
for (int i = 0; i < 20; i++)
Children.Add(new TextBlock { Text = i.ToString(), Margin = new Thickness(0, 0, 10, 0) });
public class My: StackPanel
{
public My()
{
Orientation = Orientation.Horizontal;
for (int i = 0; i < 20; i++)
Children.Add(new TextBlock { Text = i.ToString(), Margin = new Thickness(0, 0, 10, 0) });
}
}Loomulikult saab igale elemendile määrata, kuidas ta vertikaalselt paigutub. Margin võimaldab siis sellest paiknemisest nihkeid tekitada. Vaata seda näidet, kus ülemine Margin muutub. Panin paneeli enda ülemisse serva paiknema ja taustavärvuse kollaseks, et oleks võimalik näha, kuidas selle mõõtmed seoses elementide Margini reguleerimisega muutuvad:
public class My: StackPanel
{
public My()
{
VerticalAlignment = VerticalAlignment.Top;
Orientation = Orientation.Horizontal;
Background = new SolidColorBrush(Colors.Yellow);
for (int i = 0; i < 20; i++)
Children.Add(new TextBlock { Text = i.ToString(), Margin = new Thickness(0, i, 10, 0) });
}
}
public class My: StackPanel
{
public My()
{
VerticalAlignment = VerticalAlignment.Top;
Orientation = Orientation.Horizontal;
Background = new SolidColorBrush(Colors.Yellow);
for (int i = 0; i < 20; i++)
Children.Add(new TextBlock { Text = i.ToString(), Margin = new Thickness(0, i, 10, 0) });
}
}Vaata sama näidet veel, ainult et nüüd ma muutsin elementide vertikaalse paigutuse allserva:
public class My: StackPanel
{
public My()
{
VerticalAlignment = VerticalAlignment.Top;
Orientation = Orientation.Horizontal;
Background = new SolidColorBrush(Colors.Yellow);
for (int i = 0; i < 20; i++)
Children.Add(new TextBlock { Text = i.ToString(), Margin = new Thickness(0, i, 10, 0), VerticalAlignment = VerticalAlignment.Bottom });
}
}
public class My: StackPanel
{
public My()
{
VerticalAlignment = VerticalAlignment.Top;
Orientation = Orientation.Horizontal;
Background = new SolidColorBrush(Colors.Yellow);
for (int i = 0; i < 20; i++)
Children.Add(new TextBlock { Text = i.ToString(), Margin = new Thickness(0, i, 10, 0), VerticalAlignment = VerticalAlignment.Bottom });
}
}Tulemuseks on see, et erinev Margin ei avalda mõju nende paigutusele, küll aga suurendab viimase elemendi Margin kogu paneeli kõrgust.
Proovi seada tekstide Margin asendisse Center:
VerticalAlignment = VerticalAlignment.Center
public class My: StackPanel
{
public My()
{
VerticalAlignment = VerticalAlignment.Top;
Orientation = Orientation.Horizontal;
Background = new SolidColorBrush(Colors.Yellow);
for (int i = 0; i < 20; i++)
Children.Add(new TextBlock { Text = i.ToString(), Margin = new Thickness(0, i, 10, 0), VerticalAlignment = VerticalAlignment.Center });
}
}Sel juhul on esimene element täpselt keskel, kuid teised nihkuvad allapoole. Viimane element on all ääres, sest teda sunnib sinna tema Margin, samas ei ole StackPanel liiga palju kasvanud, sest allpool elementidel Marginit seatud ei ole.
Üks katse veel... Keegi ei keela Marginit negatiivseks teha:
for (int i = 0; i < 20; i++)
Children.Add(new TextBlock { Text = i.ToString(), Margin = new Thickness(0, -i, 10, 0) });
public class My: StackPanel
{
public My()
{
VerticalAlignment = VerticalAlignment.Top;
Orientation = Orientation.Horizontal;
Background = new SolidColorBrush(Colors.Yellow);
for (int i = 0; i < 20; i++)
Children.Add(new TextBlock { Text = i.ToString(), Margin = new Thickness(0, -i, 10, 0) });
}
}Kokkuvõtteks võib öelda, et WPF annab programmeerijale täieliku vabaduse paigutada elemente nii nagu ta soovib. Seejuures kehtivad need standardid igal ajal ja igal pool ning Silverlighti komponendi puhul ei pea kartma veebisirvijate vahelisi erinevusi.
Graafiline kasutajaliides
Canvas
Canvas ehk lõuend annab kasutajale täieliku vabaduse paigutada elemente sinna kuhu ta soovib. Kui me vaatame järgmises osas Gridi, mis lubab samuti Margini abil elemente paigutada, siis Canvas on eriline selles mõttes, et tal ei ole tausta ja ta ei pea kasvatama elementide näitamiseks oma mõõtmeid.
Canvasel elementide paigutamiseks sobib nii Margin kui ka Left ja Top. Need Left ja Top on Canvase DependencyPropertyd ehk sõltomadused. Sõltomadus on omadus, mis võib olla defineeritud teises klassis, kuid mida võib enda külge pookida suvaline klass. Näiteks TextBlock võib enda külge võtta Canvase sõltomadused Left ja Top. Samas hoolib neist ainult Canvas, mis paneb siis selle TextBlocki vastavale positsioonile.
Siin Canvase näide Margini abil:
public class My: Canvas
{
public My()
{
for (int i = 0; i < 20; i++)
Children.Add(new TextBlock { Text = i.ToString(), Margin = new Thickness(i * 10, i * 10, 0, 0) });
}
}
public class My: Canvas
{
public My()
{
VerticalAlignment = VerticalAlignment.Top;
Background = new SolidColorBrush(Colors.Yellow);
for (int i = 0; i < 20; i++)
Children.Add(new TextBlock { Text = i.ToString(), Margin = new Thickness(i * 10, i * 10, 0, 0) });
}
}Ja siin sama näide sõltomaduste abil:
public class My: Canvas
{
public My()
{
for (int i = 0; i < 20; i++)
{
TextBlock tb = new TextBlock { Text = i.ToString() };
Canvas.SetLeft(tb, i * 10);
Canvas.SetTop(tb, i * 10);
Children.Add(tb);
}
}
}
public class My: Canvas
{
public My()
{
VerticalAlignment = VerticalAlignment.Top;
Background = new SolidColorBrush(Colors.Yellow);
for (int i = 0; i < 20; i++)
{
TextBlock tb = new TextBlock { Text = i.ToString() };
Canvas.SetLeft(tb, i * 10);
Canvas.SetTop(tb, i * 10);
Children.Add(tb);
}
}
}Visuaalselt on tulemus sama. Nagu näha, saab sõltomadust kirjutada Canvas.SetLeft(tb, 1) abil, andes esimeseks argumendiks elemendi, millele seda rakendatakse, ja teiseks väärtuse. Sõltomadust saab lugeda Canvas.GetLeft(tb).
Elementide paigutust Z-teljel saab muuta Canvase sõltomaduse ZIndex kaudu:
public class My: Canvas
{
public My()
{
for (int i = 1; i < 256; i += 16)
{
Border b = new Border { Width = 40, Height = 40, Background = new SolidColorBrush(Color.FromArgb(255, (byte)i, (byte)i, (byte)i)) };
Canvas.SetLeft(b, i);
Canvas.SetTop(b, i);
Canvas.SetZIndex(b, i);
Children.Add(b);
}
}
}
public class My: Canvas
{
public My()
{
for (int i = 1; i < 256; i += 16)
{
Border b = new Border { Width = 40, Height = 40, Background = new SolidColorBrush(Color.FromArgb(255, (byte)i, (byte)i, (byte)i)) };
Canvas.SetLeft(b, i);
Canvas.SetTop(b, i);
Canvas.SetZIndex(b, i);
Children.Add(b);
}
}
}Ja selles näiteks jookseb ZIndex tagurpidisuunas:
Canvas.SetZIndex(b, -i);
public class My: Canvas
{
public My()
{
for (int i = 1; i < 256; i += 16)
{
Border b = new Border { Width = 40, Height = 40, Background = new SolidColorBrush(Color.FromArgb(255, (byte)i, (byte)i, (byte)i)) };
Canvas.SetLeft(b, i);
Canvas.SetTop(b, i);
Canvas.SetZIndex(b, -i);
Children.Add(b);
}
}
}Olgu öeldud, et kuigi ZIndex on Canvase sõltomadus (ja Silverlighti 1.0-versioonis oligi ainult Canvas), siis Canvas.SetZIndex avaldab mõju ka teistes paneelides (näiteks Grid peab sellest samuti lugu).
Graafiline kasutajaliides
Grid
Grid ehk ruudustik on algselt kavandatud töötama tabelina, milles on tulbad ja read. Kuna aga kõigi elementide jaoks on võimalus seada Margin, võib seda kasutada Canvase asemel. Grid erineb Canvasest selle poolest, et Gridil on mõõtmed ja teda ennast võib paigutada. See teeb tema Canvasest praktilisemaks. Alltoodud näitest paistab välja ka see erinevus, et kui elementidele pole antud HorizontalAlignment ja VerticalAlignment, siis need püütakse üle pinna laiali venitada (või paigutada keskele, kui on antud Width ja Height). Canvas paigutab kõik vaikimisi Left-Top. Veel üks erinevus on see, et Gridil võib olla ka taust (Background).
Kõik elemendid, mis lisatakse Gridi ilma rida (row) ja tulpa (column) defineerimata, lähevad ritta 0 ja tulpa 0, mis on vaikimisi olemas. Ja nagu eelmises artiklis mainitud, elementide järjestust Z-teljel saab määrata sõltomadusega Canvas.ZIndex.
public class My: Grid
{
public My()
{
for (int i = 1; i < 256; i += 16)
{
Border b = new Border
{
Width = 40,
Height = 40,
Background = new SolidColorBrush(Color.FromArgb(255, (byte)i, (byte)i, (byte)i)),
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
Margin = new Thickness(i, i, 0, 0)
};
Canvas.SetZIndex(b, -i);
Children.Add(b);
}
}
}
public class My: Grid
{
public My()
{
for (int i = 1; i < 256; i += 16)
{
Border b = new Border
{
Width = 40,
Height = 40,
Background = new SolidColorBrush(Color.FromArgb(255, (byte)i, (byte)i, (byte)i)),
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
Margin = new Thickness(i, i, 0, 0)
};
Canvas.SetZIndex(b, -i);
Children.Add(b);
}
}
}Muidugi saab Gridi kasutada ka tulpade ja ridade jaoks (milleks ta algselt mõeldud oligi). Et see toimiks, tuleb tulbad ja read käsitsi luua (alltoodud näites teeme kumbagi 4). Tulp ja rida määratakse Gridi sõltomadustega Row ja Column, kasutades meetodeid Grid.SetRow ja Grid.SetColumn:
public class My: Grid
{
public My()
{
for (int i = 0; i < 4; i++)
{
RowDefinitions.Add(new RowDefinition());
ColumnDefinitions.Add(new ColumnDefinition());
}
for (int i = 1; i < 256; i += 16)
{
Border b = new Border
{
Width = 40,
Height = 40,
Background = new SolidColorBrush(Color.FromArgb(255, (byte)i, (byte)i, (byte)i)),
};
Grid.SetRow(b, i / 64);
Grid.SetColumn(b, (i / 16) % 4);
Children.Add(b);
}
}
}
public class My: Grid
{
public My()
{
for (int i = 0; i < 4; i++)
{
RowDefinitions.Add(new RowDefinition());
ColumnDefinitions.Add(new ColumnDefinition());
}
for (int i = 1; i < 256; i += 16)
{
Border b = new Border
{
Width = 40,
Height = 40,
Background = new SolidColorBrush(Color.FromArgb(255, (byte)i, (byte)i, (byte)i)),
};
Grid.SetRow(b, i / 64);
Grid.SetColumn(b, (i / 16) % 4);
Children.Add(b);
}
}
}Kui sa nüüd seda eelmist näidet vaatad, siis Grid laiutab üle terve ruumi ja jagab kõik read ja tulbad võrdselt. Kui sul ei ole mingi lukuaugu suurune monitor, siis seda on ilmselt rohkem kui vaja. Selleks, et Grid paneks oma read ja tulbad sisu järgi mõõtu, tuleb kasutada mõõtu GridLength.Auto:
RowDefinitions.Add(new RowDefinition{ Height = GridLength.Auto });
ColumnDefinitions.Add(new ColumnDefinition{ Width = GridLength.Auto });
public class My: Grid
{
public My()
{
for (int i = 0; i < 4; i++)
{
RowDefinitions.Add(new RowDefinition{ Height = GridLength.Auto });
ColumnDefinitions.Add(new ColumnDefinition{ Width = GridLength.Auto });
}
for (int i = 1; i < 256; i += 16)
{
Border b = new Border
{
Width = 40,
Height = 40,
Background = new SolidColorBrush(Color.FromArgb(255, (byte)i, (byte)i, (byte)i)),
};
Grid.SetRow(b, i / 64);
Grid.SetColumn(b, (i / 16) % 4);
Children.Add(b);
}
}
}Ja loomulikult saab kõrgusi ja laiusi ka käsitsi paika panna:
RowDefinitions.Add(new RowDefinition{ Height = new GridLength(50) });
ColumnDefinitions.Add(new ColumnDefinition{ Width = new GridLength(50) });
public class My: Grid
{
public My()
{
for (int i = 0; i < 4; i++)
{
RowDefinitions.Add(new RowDefinition{ Height = new GridLength(50) });
ColumnDefinitions.Add(new ColumnDefinition{ Width = new GridLength(50) });
}
for (int i = 1; i < 256; i += 16)
{
Border b = new Border
{
Width = 40,
Height = 40,
Background = new SolidColorBrush(Color.FromArgb(255, (byte)i, (byte)i, (byte)i)),
};
Grid.SetRow(b, i / 64);
Grid.SetColumn(b, (i / 16) % 4);
Children.Add(b);
}
}
}Kolmas variant on veel, see on Star ehk suhe teistega. Kui ühe tulba laius on 1 stari ja teisel 2 stari, siis teine tulp on 2 korda laiem.
RowDefinitions.Add(new RowDefinition{ Height = new GridLength(i, GridUnitType.Star) });
ColumnDefinitions.Add(new ColumnDefinition{ Width = new GridLength(i, GridUnitType.Star) });
public class My: Grid
{
public My()
{
for (int i = 0; i < 4; i++)
{
RowDefinitions.Add(new RowDefinition{ Height = new GridLength(i, GridUnitType.Star) });
ColumnDefinitions.Add(new ColumnDefinition{ Width = new GridLength(i, GridUnitType.Star) });
}
for (int i = 1; i < 256; i += 16)
{
Border b = new Border
{
Width = 40,
Height = 40,
Background = new SolidColorBrush(Color.FromArgb(255, (byte)i, (byte)i, (byte)i)),
};
Grid.SetRow(b, i / 64);
Grid.SetColumn(b, (i / 16) % 4);
Children.Add(b);
}
}
}Nagu eelmistes artiklites märgitud, on WPFi süsteem selline, et igal elemendil on paigutusparameetrid (HorizontalAlignment, VerticalAlignment, Margin, Padding) endal olemas ja Gridi kaudu neid mõjutada ei saa (nagu tehakse HTMLi tabelis). Gridis ei eksisteeri sellist elementi nagu cell ehk ruut (nagu HTMLis). Kõik elemendid on otse Gridi lapsed ja Grid lihtsalt paigutab neid kujuteldavatesse ruutudesse.
Jõudlusest
Kui Gridis on palju sisu (eriti teksti), siis GridLength.Auto tähendab seda, et kõik tekstid tuleb enne paigutamist pikkust- ja laiustpidi ära mõõta ja juba mõnesajarealise tabeli puhul võib see olla liiga aeglane. Seega, kui võimalik, soovitaksin mõõdud sellistel puhkudel käsitsi paika panna.
Graafiline kasutajaliides
TextBlock
TextBlock on vahend mitteredigeeritava teksti kuvamiseks. Tal on omadus Text:
public class Test: Grid
{
TextBlock tb = new TextBlock
{
Text = "Tere sõbrad"
};
public Test()
{
Children.Add(tb);
}
}
public class Test: Grid
{
TextBlock tb = new TextBlock
{
Text = "Tere sõbrad"
};
public Test()
{
Children.Add(tb);
}
}Nagu igat elementi, saab teda paigutada omaduste HorizontalAlignment, VerticalAlignment, Margin ja Padding abil. Lisaks sellele on TextBlockil ka TextAlignment, mis võimaldab paigutada teksti kasti sees. See tähendab, et kast ise võib olla suurem, kuid tekst paigutatakse selle sees:
HorizontalAlignment = HorizontalAlignment.Stretch,
TextAlignment = TextAlignment.Right
public class Test: Grid
{
TextBlock tb = new TextBlock
{
Text = "Tere sõbrad",
HorizontalAlignment = HorizontalAlignment.Stretch,
TextAlignment = TextAlignment.Right
};
public Test()
{
Children.Add(tb);
}
}Teksti vormindamine
Fonti saab määratleda omadusega FontFamily. Seejuures võimaldab FontFamily konstruktor anda kaasa mitu komadega eraldatud fondinime. Kui kasutaja arvutis esimest ei ole, valitakse järgmine font (samamoodi nagu CSSis). Samuti võib see viidata kohalikule ressursile.
FontFamily = new FontFamily("Consolas, Courier New")
public class Test: Grid
{
TextBlock tb = new TextBlock
{
Text = "Tere sõbrad",
FontFamily = new FontFamily("Consolas, Courier New")
};
public Test()
{
Children.Add(tb);
}
}Fonti saab alla laadida ka otse veebist (kui see on samas domeenis või serveris on crossdomain.xml). Selleks tuleb esiteks seada FontSource, seejärel pead teadma fondi nime, et luua FontFamily:
WebClient wc = new WebClient();
wc.OpenReadCompleted += (sender, e) =>
{
tb.FontSource = new FontSource(e.Result);
tb.FontFamily = new FontFamily("Shadows Into Light");
};
wc.OpenReadAsync(new Uri("http://web.koodilabor.ee/files/ShadowsIntoLight.ttf"), null);
public class Test: Grid
{
TextBlock tb = new TextBlock
{
Text = "Tere sõbrad"
};
public Test()
{
Children.Add(tb);
WebClient wc = new WebClient();
wc.OpenReadCompleted += (sender, e) =>
{
tb.FontSource = new FontSource(e.Result);
tb.FontFamily = new FontFamily("Shadows Into Light");
};
wc.OpenReadAsync(new Uri("http://web.koodilabor.ee/files/ShadowsIntoLight.ttf"), null);
}
}Teksti suuruse muutmiseks on omadus FontSize, samuti on olemas sellised nagu FontWeight, TextDecorations, FontStyle ja FontStretch, mille loogika on sarnane CSSi loogikaga:
FontSize = 50,
FontWeight = FontWeights.Bold,
TextDecorations = TextDecorations.Underline,
FontStyle = FontStyles.Italic
public class Test: Grid
{
TextBlock tb = new TextBlock
{
Text = "Tere sõbrad",
FontSize = 50,
FontWeight = FontWeights.Bold,
TextDecorations = TextDecorations.Underline,
FontStyle = FontStyles.Italic
};
public Test()
{
Children.Add(tb);
}
}Erineva vorminguga osad
TextBlock võimaldab omaduse Text asemel paigutada tekstijuppe (Run) omadusse Inlines. Neil võib siis olla erinev vorming. Reavahesid saab lisada elemendiga LineBreak.
tb.Inlines.Add(new Run { Text = "i", FontFamily = new FontFamily("Webdings") });
tb.Inlines.Add(new Run { Text = " Siin natuke " });
tb.Inlines.Add(new LineBreak());
tb.Inlines.Add(new Run { Text = "infot", Foreground = new SolidColorBrush(Colors.Blue) });
public class Test: Grid
{
TextBlock tb = new TextBlock{ FontSize = 30 };
public Test()
{
Children.Add(tb);
tb.Inlines.Add(new Run { Text = "i", FontFamily = new FontFamily("Webdings") });
tb.Inlines.Add(new Run { Text = " Siin natuke " });
tb.Inlines.Add(new LineBreak());
tb.Inlines.Add(new Run { Text = "infot", Foreground = new SolidColorBrush(Colors.Blue) });
}
}Tore on see, et siis, kui Inlines sisaldab vormindatud tekstijuppe, saab lihtteksti ikkagi lugeda omadusest Text. Proovi seda näidist, kus puldis kuvatakse TextBlocki lihttekst:
Console.WriteLine(tb.Text);
public class Test: Grid
{
TextBlock tb = new TextBlock{ FontSize = 30 };
public Test()
{
Loaded += (sender, e) => (Parent as ChildWindow).HorizontalAlignment = HorizontalAlignment.Left;
Children.Add(tb);
tb.Inlines.Add(new Run { Text = "i", FontFamily = new FontFamily("Webdings") });
tb.Inlines.Add(new Run { Text = " Siin natuke " });
tb.Inlines.Add(new LineBreak());
tb.Inlines.Add(new Run { Text = "infot", Foreground = new SolidColorBrush(Colors.Blue) });
Console.WriteLine(tb.Text);
}
}Muud vormindamist puudutavad omadused
Nagu eelviimasest näitest näha, saab teksti pintslit seada omaduse Foreground kaudu. Taust on TextBlockil alati läbipaistev. Kui TextBlocki tausta on vaja muuta, pane see näiteks Gridi või Borderi sissse.
Nagu kõiki elemente, nii saab ka TextBlocki keerata, väänata, skaleerida, teha poolläbipaistvaks, animeerida jne. Need asjad väärivad eraldi artikleid.
Graafiline kasutajaliides
Brush
Pintsel ehk Brush on baasklass erinevatele pintsitele, millega saab täita kõiki pindu ja raame, sealhulgas ka teksti.
Brushist on päritud mitu klassi. Esimene neist on SolidColorBrush:
public class Brushes: Grid
{
public Brushes()
{
Background = new SolidColorBrush(Colors.Yellow);
}
}
public class Brushes: Grid
{
public Brushes()
{
Background = new SolidColorBrush(Colors.Yellow);
}
}Graafilise kasutajaliidese elementidel ei ole värvuse omadust. Kõik täidetavad pinnad peavad olema pintslid. Ja see on hea, sest pintsleid on erinevaid. Järgnevalt pildiga pintsel ImageBrush:
new TextBlock
{
Foreground = new ImageBrush{ ImageSource = new System.Windows.Media.Imaging.BitmapImage(new Uri("http://web.koodilabor.ee/files/water.jpg", UriKind.Absolute)) },
Text = "Tere",
FontSize = 200
}
public class Brushes: Grid
{
public Brushes()
{
Children.Add(new TextBlock
{
Foreground = new ImageBrush
{
ImageSource = new System.Windows.Media.Imaging.BitmapImage
(new Uri("http://web.koodilabor.ee/files/water.jpg", UriKind.Absolute))
},
Text = "Tere",
FontSize = 200
});
}
}Muidugi tekkis sul loogiline küsimus: kas videopintsel on ka olemas? Jah, on. Teeme ellipsi ja täidame selle VideoBrushiga. Pane tähele, et meil on vaja luua MediaElement ja see peab olema mõne visuaalse elemendi sees (kuigi peidetud: Visibility.Collapsed), muidu video ei mängi.
public class Brushes: Grid
{
public Brushes()
{
MediaElement me = new MediaElement
{
Source = new Uri("http://web.koodilabor.ee/files/03.wmv", UriKind.Absolute),
Visibility = Visibility.Collapsed
};
Children.Add(me);
VideoBrush vb = new VideoBrush();
vb.SetSource(me);
Children.Add(new Ellipse
{
Width = 320,
Height = 240,
Fill = vb
});
}
}
public class Brushes: Grid
{
public Brushes()
{
MediaElement me = new MediaElement
{
Source = new Uri("http://web.koodilabor.ee/files/03.wmv", UriKind.Absolute),
Visibility = Visibility.Collapsed
};
Children.Add(me);
VideoBrush vb = new VideoBrush();
vb.SetSource(me);
Children.Add(new Ellipse
{
Width = 320,
Height = 240,
Fill = vb
});
}
}Astmikud
Astmike loomiseks on klass LinearGradientBrush. See võib võtta nii mitu astet kui soovid.
public class Brushes: Grid
{
public Brushes()
{
LinearGradientBrush lb = new LinearGradientBrush
{
StartPoint = new Point(0, 0.5),
EndPoint = new Point(1, 0.5)
};
lb.GradientStops.Add(new GradientStop{ Color = Colors.Yellow, Offset = 0.0 });
lb.GradientStops.Add(new GradientStop{ Color = Colors.Red, Offset = 0.25 });
lb.GradientStops.Add(new GradientStop{ Color = Colors.Blue, Offset = 0.75 });
lb.GradientStops.Add(new GradientStop{ Color = Colors.Green, Offset = 1.0 });
Background = lb;
}
}
public class Brushes: Grid
{
public Brushes()
{
LinearGradientBrush lb = new LinearGradientBrush
{
StartPoint = new Point(0, 0.5),
EndPoint = new Point(1, 0.5)
};
lb.GradientStops.Add(new GradientStop{ Color = Colors.Yellow, Offset = 0.0 });
lb.GradientStops.Add(new GradientStop{ Color = Colors.Red, Offset = 0.25 });
lb.GradientStops.Add(new GradientStop{ Color = Colors.Blue, Offset = 0.75 });
lb.GradientStops.Add(new GradientStop{ Color = Colors.Green, Offset = 1.0 });
Background = lb;
}
}StartPoint ja EndPoint on astmiku algus- ja lõpppunkt, kusjuures koordinaadistikuks on astmik ise skaalas 0.0 kuni 1.0 (vasak ülemine nurk on 0.0, 0.0 ja parem alumine 1.0, 1.0). Proovi ülaltoodud näidet, muutes astmiku diagonaalseks.
Astmed pannakse kujuteldavale joonele, mis läheb StartPointist EndPointi, ja astmete asukoht on jälle märgitud skaalas 0.0 kuni 1.0. Proovi muuta astmete asukohti.
See oli lineaarne (sirgjooneline) astmik, aga astmik võib olla ka radiaalne (ringjooneline). Allpool siis üks RadialGradientBrush, mille keskpunkt (Center) on küll keskel, aga astmiku lähtekoht (GradientOrigin) nihutatud:
public class Brushes: Grid
{
public Brushes()
{
RadialGradientBrush lb = new RadialGradientBrush
{
GradientOrigin = new Point(0.75, 0.25),
Center = new Point(0.5, 0.5),
RadiusX = 0.5,
RadiusY = 0.5
};
lb.GradientStops.Add(new GradientStop{ Color = Colors.Yellow, Offset = 0.0 });
lb.GradientStops.Add(new GradientStop{ Color = Colors.Red, Offset = 0.25 });
lb.GradientStops.Add(new GradientStop{ Color = Colors.Blue, Offset = 0.75 });
lb.GradientStops.Add(new GradientStop{ Color = Colors.Green, Offset = 1.0 });
Background = lb;
}
}
public class Brushes: Grid
{
public Brushes()
{
RadialGradientBrush lb = new RadialGradientBrush
{
GradientOrigin = new Point(0.75, 0.25),
Center = new Point(0.5, 0.5),
RadiusX = 0.5,
RadiusY = 0.5
};
lb.GradientStops.Add(new GradientStop{ Color = Colors.Yellow, Offset = 0.0 });
lb.GradientStops.Add(new GradientStop{ Color = Colors.Red, Offset = 0.25 });
lb.GradientStops.Add(new GradientStop{ Color = Colors.Blue, Offset = 0.75 });
lb.GradientStops.Add(new GradientStop{ Color = Colors.Green, Offset = 1.0 });
Background = lb;
}
}Mäletad ikka, et need kõik on pintslid, millega võib täita kõiki jooni, alasid ja tekste? Lase siis oma fantaasial lennata...
Graafiline kasutajaliides
Animatsioon
Silverlight/WPF on animeerimise väga lihtsaks teinud. Animatsioone saab ehitada sama edukalt ka käsitsi, taimeri abil, aga sisseehitatud klassidega on see palju lihtsam. Animatsioonid on võimelised end ise arvuti jõudlusele vastavalt seadistama: mida jõudsam arvuti, seda sujuvamad on animatsioonid ja vastupidi. Samuti suudetakse animeerimiseks ära kasutada kõigi protsessorite jõudlus.
Animeerimiseks on vaja kahte klassi: Animation ja Storyboard. Animation muudab mingit konkreetset omadust ja Storyboard jooksutab ühe või mitut Animationit. Alltoodud näites jookseb ühes Storyboardis 2 Animationit, kusjuures üks muudab horisontaal- ja teine vertikaalasendit.
public class Ani: Canvas
{
public Ani()
{
Loaded += delegate
{
Ellipse e = new Ellipse { Height = 30, Width = 30, Fill = new SolidColorBrush(Colors.Red) };
Children.Add(e);
DoubleAnimation top = new DoubleAnimation
{
Duration = TimeSpan.FromSeconds(3), From = 0, To = ActualHeight - e.Height,
RepeatBehavior = RepeatBehavior.Forever, AutoReverse = true
};
Storyboard.SetTarget(top, e);
Storyboard.SetTargetProperty(top, new PropertyPath(Canvas.TopProperty));
DoubleAnimation left = new DoubleAnimation
{
Duration = TimeSpan.FromSeconds(2.3), From = 0, To = ActualWidth - e.Width,
RepeatBehavior = RepeatBehavior.Forever, AutoReverse = true
};
Storyboard.SetTarget(left, e);
Storyboard.SetTargetProperty(left, new PropertyPath(Canvas.LeftProperty));
Storyboard sb = new Storyboard();
sb.Children.Add(top);
sb.Children.Add(left);
sb.Begin();
};
}
}
public class Ani: Canvas
{
public Ani()
{
Loaded += delegate
{
Ellipse e = new Ellipse { Height = 30, Width = 30, Fill = new SolidColorBrush(Colors.Red) };
Children.Add(e);
DoubleAnimation top = new DoubleAnimation
{
Duration = TimeSpan.FromSeconds(3), From = 0, To = ActualHeight - e.Height,
RepeatBehavior = RepeatBehavior.Forever, AutoReverse = true
};
Storyboard.SetTarget(top, e);
Storyboard.SetTargetProperty(top, new PropertyPath(Canvas.TopProperty));
DoubleAnimation left = new DoubleAnimation
{
Duration = TimeSpan.FromSeconds(2.3), From = 0, To = ActualWidth - e.Width,
RepeatBehavior = RepeatBehavior.Forever, AutoReverse = true
};
Storyboard.SetTarget(left, e);
Storyboard.SetTargetProperty(left, new PropertyPath(Canvas.LeftProperty));
Storyboard sb = new Storyboard();
sb.Children.Add(top);
sb.Children.Add(left);
sb.Begin();
};
}
}Storyboard.SetTarget() valib objekti, mille omadust animeeritakse. Pane tähele, et objekt pannakse animatsiooni külge (objekt e animatsiooni top külge). Storyboard.SetTargetProperty() määrab omaduse, mida muuta. Kuna meie objekt on Canvase peal, siis tema asukoha muutmiseks tuleb muuta omadust Canvas.Top (ilma animeerimata saaks seda asukohta muuta näiteks Canvas.SetTop(e, 100)).
Esiteks teeme ühe punase mummu (e). Siis loome DoubleAnimationi, mille ülesanne on muuta double-tüüpi väärtust väärtusest From kuni väärtuseni To. Duration on aeg, mille jooksul väärtus muutub, antud juhul siis 3 või 2.3 sekundit. Omadus AutoReverse paneb ühe tsükli lõppedes väärtuse tagurpidi liikuma (tagasi From-suunas). RepeatBehavior.Forever tähendab, et animatsioon ei piirdu ühe korraga, vaid jääb lõputult käima.
Kordame sama teise animatsiooniga left. Me paneme talle erineva kestuse, et palli asukoht ei hakkaks korduma.
Viimane ülesanne on luua Storyboard. Sellele lisame 2 last: top ja left. Seejärel teeme talle Begin() ja animatsioon läheb lahti.
Selleks, et animeerida võimalikult mitmesuguseid omadusi, on süsteemis defineeritud järgmised animatsiooniklassid: BooleanAnimation, ByteAnimation, CharAnimation, ColorAnimation, DecimalAnimation, DoubleAnimation, Int16Animation, Int32Animation, Int64Animation, MatrixAnimation, ObjectAnimation, Point3DAnimation, PointAnimation, QuaternionAnimation, RectAnimation, Rotation3DAnimation, SingleAnimation, SizeAnimation, StringAnimation, ThicknessAnimation, Vector3DAnimation, VectorAnimation.
Me ei hakka neid kõiki käsitlema, aga sa võid ise mõelda, mis kasu võiks olla BooleanAnimationist või StringAnimationist. Igatahes peab kasutatava sihtomaduse andmetüüp kattuma animatsiooni andmetüübiga.
Animatsiooni sundimatuks muutmine
Igasuguste arvutianimatsioonide probleem on, et nad ei paista nagu päris. Isegi multikates liiguvad objektid tihti nagu ujudes. Et animatsioon ei paistaks tehislik, on loodud mitmeid sundimatuks muutmise funktsioone (EasingFunction).
Neid saab igale animatsioonile lihtsalt külge panna. EasingMode määrab, kas funktsiooni rakendatakse alguses (EaseIn), lõpus (EaseOut) või alguses ja lõpus (EaseInOut).
Erinevate variantide küllusega saab tutvuda siin.
Näiteks BounceEase paneb palli põrkama:
EasingFunction = new BounceEase { EasingMode = EasingMode.EaseInOut }
public class Ani: Canvas
{
public Ani()
{
Loaded += delegate
{
Ellipse e = new Ellipse { Height = 30, Width = 30, Fill = new SolidColorBrush(Colors.Red) };
Children.Add(e);
DoubleAnimation top = new DoubleAnimation
{
Duration = TimeSpan.FromSeconds(3), From = 0, To = ActualHeight - e.Height,
RepeatBehavior = RepeatBehavior.Forever, AutoReverse = true,
EasingFunction = new BounceEase { EasingMode = EasingMode.EaseInOut }
};
Storyboard.SetTarget(top, e);
Storyboard.SetTargetProperty(top, new PropertyPath(Canvas.TopProperty));
DoubleAnimation left = new DoubleAnimation
{
Duration = TimeSpan.FromSeconds(2.3), From = 0, To = ActualWidth - e.Width,
RepeatBehavior = RepeatBehavior.Forever, AutoReverse = true
};
Storyboard.SetTarget(left, e);
Storyboard.SetTargetProperty(left, new PropertyPath(Canvas.LeftProperty));
Storyboard sb = new Storyboard();
sb.Children.Add(top);
sb.Children.Add(left);
sb.Begin();
};
}
}Võimalusi on tohutult. Lase oma fantaasial lennata.