C# on Microsoftin kehittämä ohjelmointikieli, joka julkaistiin kesäkuussa 2000. Kieli kehitettiin yhdistämään C++:n tehokkuus ja Javan helppokäyttöisyys. Kielen on kehittänyt Anders Hejlsberg, jolla oli paljon aikaisempaa kokemusta mm. Turbo Pascal, Delph ja J++ kehitystyöstä. C#:n kehityksen päätavoitteena oli luoda useampiin ympäristöihin helppokäyttöinen oliopohjainen ohjelmointi. Kielenä C# on vahvasti tyypitetty eli muuttujille on annettava jokin tietotyyppi (esim. kokonaisluku, merkkijono, ...).
C# ohjelmointikielen tarkka ja täydellinen spesifikaatio C# Language Specification on ladattavissa Microsoft Download Center:ssä Suosittelen tutustumaan mieluummin kuin "alkaa arvailemaan".
Aika moni ohjelmoija pitää C#:a Microsoftin vastauksena Java-ohjelmoinnille. Mielestäni tässä ei ole tärkeää lähteä erottelemaan tai vertailemaan näitä kahta ohjelmointikieltä. Tällä kurssilla on tärkeintä oppia lisää ohjelmointia ja sillä ei ole merkistystä tehdäänkö se C#- vai Java-ohjelmointikielellä. Molemmilla on tärkeä rooli tämän päivän ohjelmistoteollisuudessa.
Tiobe-indeksin mukaan C# on pysynyt jo pitkään viiden suosituimman ohjelmointikielen joukossa: Tiobe
Alla on näkyvissä ensimmäinen pieni C#-ohjelmoinnilla toteutettu konsolipohjainen ohjelma (FirstTest.cs), joka tulostaa henkilön nimen näyttölaitteelle.
Rivi 1: Sovellus käyttää System nimiavaruutta, sisältää C#:n perusluokkia, joita käytetään yleisesti
Rivi 3: Määrittelee tämän sovelluksen nimiavaruuden
Rivi 5: Määrittelee sovelluksen pääluokan
Rivi 6: Määrittelee sovelluksen main-metodin, ohjelman aloituspiste on aina staattinen main-metodi
Rivit 9-14: Sovelluksen toiminnalliset lauseet
Kirjoitettu C#-koodi voidaan kääntää ajettavaksi sovellukseksi käyttämällä csc.exe-komentoa Windowsin komentokehotteessa tai suoraan Visual Studio IDE:ä kääntämään ja suorittamaan sovellus. Molemissa tapauksissa tuotetaan .exe-päätteinen tiedosto, jota voidaan suorittaa Windows-ympäristössä.
C#-ohjelmointikoodia voidaan kirjoittaa milla tahansa tekstieditorilla. Tämän kurssin kaikki harjoitteet ohjelmoidaan ja suoritetaan Visual Studiosta käsin.
Maailmassa on useita satoja eri ohjelmointikieliä, joilla on jokaisella (tai ainakin usealla) omat tyylinsä kuinka ohjelmointikoodia tulee kirjoittaa. C#:n osalta kannattaa opetella erittäin tarkkaan seuraavat periaatteet: Naming Guidelines
Ohjelmointikieliin liittyvillä avainsanoilla on tietty erityinen tehtävä ja merkitys. Näitä varattuja sanoja ei voi käyttää omassa ohjelmassa tunnisteina (tietyin keinoin tosin voi, mutta sellaisia kiertoteitä ei kannata käyttää).
C#:n varatut avainsanat löydät täältä: C# Keywords.
Tunnisteet ovat ohjelmoijan antamia nimiä muuttujille, luokille, metodeille, jne..., jolla ohjelman eri objektit tunnistetaan. Tunnisteiden nimissä voi käyttää isoja ja pieniä kirjaimia, numeroita sekä esimerkiksi alaviivaa. Tunnisteen ensimmäinen merkki ei voi olla numero.
Hyvä tunnisteen nimi kuvaa hyvin sen sisältöä ja mitaltaan suhteellisen lyhyt, jotta sen kirjoittaminen on nopeaa ja ei virhealtista.
Muuttujissa säilytetään tietoa ohjelman suorituksen aikana. Sovelluksen muuttujalle pitää antaa nimi (kts. tunniste) sekä sille pitää määritellä tyyppi, jonka mukaista tietoa muuttuja voi sisältää. Muuttujat kannattaa nimetä kuvaavasti, jotta niiden käyttö on selkeää kaikille.
Muuttujien yleinen määrittely on seuraava:
tyyppi muuttuja;
Alla on näkyvissä muutamia eri tyyppisiä muuttujien määrittelyjä:
int luku;
string nimi;
bool tosi;
// samaa tyyppiä olevia muuttujia voi esitellä useita kerralla
int luku1, luku2, luku3;
// muuttujille voidaan alustaa esittelyn yhteydessä myös alkuarvo
string nimi = "Kirsi Kernel";
String etunimi = "Kirsi", sukunimi = "Kernel";
bool tosi = false;
// muuttujia voidaan käyttää esittelyn jälkeen pelkällä muuttujan nimellä (eli sijoittaa uusi arvo)
int luku = 10;
luku = luku + 25;
Muuttujien tyyppeinä käytään useasti C#:n sisäänrakennettuja tietotyyppejä. Näiden lisäksi opintojaksolla opetellaan tekemään omia tietotyyppejä (tietueita, lueteltuja tyyppejä, luokkia ja liittymiä).
C#:n tietotyypit löydät täältä: Built-In Data Types.
Oheisessa kuvassa on esitetty C#-tyyppijärjestelmä, eli arvotyypit ja viittaustyypit.
Oheisessa kuvassa on esitelty C#:n käytetyimmät tietotyypit.
Arvotyypit (yllä luetellut tietotyypit) sisältävät muuttujan arvon suoraan ja niiden muistivaraus tehdään pinosta (stack). Parametrien välityksessä arvoparametrien kohdalla käytetään oletuksena aina kopiota. Jos arvotyyppia halutaan käsitellä parametrien välityksessä kuten viittaustyyppejä, tulee sekä kutsussa, että parametrien esittelyssä käyttää ref-avainsanaa.
// molemmat luvut ovat tallessa pinossa, molemmilla oma muistivaraus
int lukuA = 100;
int lukuB = 200;
// sijoitetaan lukuA lukuB:hen
lukuB = lukuA;
lukuA = 300;
Console.WriteLine(lukuA); // tulostaa 300
Console.WriteLine(lukuB); // tulostaa 100
int luku = 10;
Add(luku); // kopio
Console.WriteLine("luku = " + luku); // tulostaa 10
// metodi
static void Add(int luku) // kopio, alkuperäinen ei muutu
{
luku++;
}
Add2(ref luku); // viite
Console.WriteLine("luku = " + luku); // tulostaa 11
// metodi
static void Add2(ref int luku)
{
luku++;
}
Yllä olevilla lukuA ja lukuB on omat muistialueensa, rivillä 6 sijoitetaan lukuA:n kopio lukuB:hen. Demottu myös parametrien välityksessä arvotyypin toimintaa.
Viittaustyyppi on nimensäkin mukaisesti viittaus varsinaiseen muuttujaan. Viittaus sijaitsee pinossa, mutta sen varsinainen tieto (string,object - class, liittymät ja delefaatit) varataan sijaitsee keossa (heap). Parametrien välityksessä viittaustyyppien kohdalla toteutetaan myös kopio, mutta alkuperäinen ja parametrinä oleva osoittaa samaan muistialueeseen eli funktiossa/aliohjelmassa/metodissa mahdollisesti muutettu arvo muuttaa myös alkuperäistä.
StringBuilder sbA = new StringBuilder("Hello World!");
StringBuilder sbB = new StringBuilder("Terve Maailma!");
sbA = sbB;
Console.WriteLine(sbA.ToString()); // Terve Maailma!
Adder adder = new Adder();
adder.luku = 10;
Add3(adder); // viite
Console.WriteLine("luku = " + adder.luku); // tulostaa 11
// metodi
static void Add3(Adder adder) // viite alkuperäiseen, muuttuu!
{
adder.luku++;
}
// luokka
class Adder
{
public int luku;
}
Yllä olevassa rivillä 4 sbA:han sijoitetaan sbB:n viittaus eli molemmat viittaavat samaan muistialueeseen keossa (heap). Demottu myös parametrien välityksessä viitetyyppien toimintaa.
Arvotyypit ja viittaustyypit käyttävät muistia eri tavalla. Arvotyypit tallennetaan pinoon (stack) kun taas viittaustyypin varsinainen tieto on keossa (heap), ja tietoon on viittaus pinosta. Tämä luo mielenkiintoisia seurauksia ja mahdollisuuksia olio-ohelmointiin. Alla oleva kuva selventää hieman muistin erilaista käyttöä.
Enum on lueteltu tyyppi, jonka jokaista jäsentä vastaa numeroarvo. Enumia käytetään yleensä tilanteessa, jossa tarvitaan kuvata jotain tilaa tai vaihtoehtoa. Ihmisen on helpompi muistaa käsitteitä kuin numeroita.
Voitaisiin tutkia esim. äänenvoimakkuuden arvoa seuraavasti. Tällöin tulisi muistaa, että esim. numero 1 tarkoittaa medium äänenvoimakkuutta.
int volume = 1;
switch (voimakkuus) {
case 0: Console.WriteLine("Volume is low."); break;
case 1: Console.WriteLine("Volume is medium."); break;
case 2: Console.WriteLine("Volume is high."); break;
}
Sama asia voitaisiin toteuttaa lueteltujen tyyppien avulla seuraavasti, jolloin mitään erityisiä numeroarvoja ei tarvitsisi muistaa.
enum Volume {
Low,
Medium,
High
}
class Program
{
static void Main(string[] args)
{
// create enum instance
Volume volume = Volume.Medium;
// check volume
switch (volume)
{
case Volume.Low:
Console.WriteLine("Volume is low.");
break;
case Volume.Medium:
Console.WriteLine("Volume is medium.");
break;
case Volume.High:
Console.WriteLine("Volume is high.");
break;
}
}
}
Enum-numeroarvo on teknisesti kokonaisluku (alkaa nollasta), muuta siihen voidaan esittelyn yhteydessä alustaa mikä tahansa haluttu arvo.
Taulukko on ohjelmoijan perustietorakenne silloin, kun on ohjelmoinnissa on tarve käyttää useampaa samantyyppistä muuttujaa. Taulukon muuttujiin (alkioihin) voidaan viitata taulukon nimellä. Taulukon indeksointi alkaa nollasta eli taulukon ensimmäinen alkio on positiossa nolla.
int[] luvut; // kokonaislukutaulukko
string[] nimet; // merkkijonotaulukko
// taulukko voidaan alustaa myös esittelyn yhdessä
int[] luvut = new int[3];
string[] nimet = new string[3];
// taulukkoa käytetään sen nimen avulla
luvut[0] = 10;
luvut[1] = 20;
luvut[2] = 30;
luvut[0] = luvut[1] + luvut[2];
// tulostetaan taulukon alkiot
for (int i=0;i<luvut.Length;i++) Console.write(luvut[i]); // tulostaa: 50 20 30
// taulukkoon voidaan suoraan myös alustaa halutut arvot
string[] nimet = {"Kirsi", "Pekka", "Juuso", "Matti"};
Console.write(nimet[1]); // tulostaa: Pekka
Kommentointi kuuluu ohjelmointiin erittäin tärkeänä osana. Kommentoinnin tarkoitus on parantaa koodin luettavuutta ja antaa lisätietoa koodin toiminnasta. Kommentointi helpottaa sovelluksen ylläpitoa ja jatkokehitystä. Valitettavan usein ohjelmoijat jättävät kommentoinnin tekemättä tai ainakin tekevät sen puutteellisesti.
Kommentoimaton ohjelma ei ole merkki ohjelmoijan asiantuntevuudesta, vaan laiskuudesta!
Kommentointi voidaan toteuttaa muutamalla eri tavalla:
// merkki aloittaa kommentin ko. rivillä. Kääntäjä ohittaa loput merkit ko. rivillä
/*
useammalle riville ulottuva kommentti
aloitetaan /* merkeillä ja lopetaan */ merkeillä
*/
Ohjelmoinnin kasvaessa ja varsinkin silloin kun aletaan tehdä omia luokkia, käyttöön tulee myös dokumenttikomentti. Tiettyä syntaksia noudattamalla voidaan dokumenttikommentit muuttaa sellaiseen muotoon, että niitä voidaan tarkastella esimerkiksi nettiselaimella. Dokumenttikommentti on syytä kirjoittaa ainakin ennen jokaista luokkaa, pääohjelmaa ja toimintoa ennen. Lisäksi jokainen C#-tiedosto alkaa dokumenttikommentilla, josta selviää tiedoston tarkoitus, tekijä, versio (ja esim. päivämäärä).
Dokumenttikomentti kirjoitetaan siten, että rivin alussa on aina aina kolme vinoviivaa. Dokumentointi toteutetaan tagien avulla (kuten HTML-kielessä). Suositeltavat tagit löytyvät täältä: Recommended Tags for Documentation Comments (C# Programming Guide).
/// @author Pasi Manninen
/// @version 01.1.2016
/// <summary>
/// Ensimmäinen testisovellus, joka tulostaa "Hello World" -tekstin näytölle
/// </summary>
public class HelloWorld
{
/// <summary>
/// Pääohjelma toteuttaa tekstin tulostamisen näytölle.
/// </summary>
/// <param name="args">ei käytössä</param>
public static void Main(string[] args)
{
System.Console.WriteLine("Hello World!");
}
}
Ohjelmointiin liittyvien operaattorien tehtävä on sijoittaa ja/tai vertailla kahden eri operandin arvoja toisiinsa.
Alla muutamia operattoreita, jotka esiintyvät ohjelmoinnissa useasti:
| operaattori | tehtävä | esimerkki |
|---|---|---|
| = | sijoitus | int luku = 10; |
| == | yhtäsuuret | if (luku == 10) { } |
| != | erisuuret | if (luku != 10) { } |
| < | pienempi | if (luku < 10) { } |
| <= | pienempi tai yhtäsuuri | if (luku <= 10) { } |
| > | suurempi | if (luku > 10) { } |
| >= | suurempi tai yhtäsuuri | if (luku <= 10) { } |
| + | yhteenlasku | int luku = 10 + 5; |
| + | merkkijonojen liittäminen | string kokonimi = "Kirsi" + "Kernel"; |
| - | vähennyslasku | int luku = 10 - 5; |
| * | kertolasku | int luku = 10 * 5; |
| / | jakolasku | int luku = 10 / 5; |
| % | jakojäännös | int luku = 10 % 5; |
| ++ | lisää lukua yhdellä | luku++; |
| -- | vähennä lukua yhdellä | luku--; |
| += | lisää itseensä | luku += 10; |
| -= | vähennä itseensä | luku -= 10; |
| /= | jaa itseensä | luku /= 10; |
| *= | kerro itseensä | luku *= 10; |
| %= | jakojäännös itseensä | luku %= 10; |
| && | Ja | if (ikä >= 18 && ikä <= 60) { } |
| || | Tai | if (vuosi == 2015 || vuosi == 2016) { } |
| ! | Negaatio | bool aikuinen = true; if (!aikuinen) { } |
Oheisessa kuvassa on esitetty yhteenveto C#:n operaattoreista.
Valintarakenteista if-lausetta käytetään testaamaan onko sulkeissa annettu lauseke kokonaisuudessaan tosi tai epätosi. Ainoastaan siinä tapauksessa, että ehtolause on tosi, suoritetaan ehtolauseen jälkeinen lauseke tai lauselohko. Suoritettavat lauseet on suljettava aaltosulkujen sisälle (poikkeus yhden lauseen tapaus, jolloin aaltosulkeita ei tarvita). if-lauseeseen on mahdollisuus liittää else-lohko, joka suoritetaan, jos varsinainen if-lauseen ehto ei toteudu. Ehtolauseita voidaan myös ketjuttaa. Tällöin toteutuneen ehdon jälkeen ei enään tarkisteta jäljelle jääneitä ehtoja. Tällöin puhutaan if else if -lauseryhmästä.
// ehtolauseeseen liittyy yksi lause
if (ehto) lause;
// ehtolauseeseen liittyy useita lauseita, käytä {}-merkkejä
if (ehto) {
lause1;
lause2;
lauseN;
}
// vaihtoehtoisia ehtoja
if (ehto1) lause;
else if (ehto2) lause;
else if (ehto3) lause;
else lause;
Alla muutamia esimerkkejä:
if (saldo < 10) Console.WriteLine("Rahaa alla 10 euroa. Ei voida nostaa rahaa!");
if (saldo < 10) {
Console.WriteLine("Rahaa alle 10 euroa. Ei voida nostaa rahaa!");
} else {
Console.WriteLine("Anna nostettava rahamäärä: ");
// tähän koodi, missä luetaan nostettava rahan määrä
}
Ketjutetun if else if -lauseen kasvaessa pitkäksi ohjelmasta muodostuu helposti hankalasti luettava. Tällöin on parempi käyttää switch-lausetta. Switch-lauseessa on myös hieno mahdollisuus yhdenvertaistaa valintoja jättämällä halutuista case-vaihtoehdoista break-lause pois, jolloin suoritetaan myös seuraava case-kohta.
char kirjain='t';
// tutkitaan kirjain
switch (kirjain) {
case 'k':
case 'K': Console.WriteLine("Sait kiitettävän!");
break;
case 'h':
case 'H': Console.WriteLine("Sait hyvän arvosanan!");
break;
case 't':
case 'T': Console.WriteLine("Sait tyydyttävän arvosanan!");
break;
case 'v':
case 'V': Console.WriteLine("Sait välttävan arvosanan!");
break;
default: Console.WriteLine("Kirjain ei ollut mikään vaihtoehdoista!");
break;
}
While-silmukka on yksinkertainen toistorakenne. Siinä testataan onko ehtolause tosi ja silmukan sisällä olevien lauseiden toistaminen jatkuu niin kauan aikaa, kun ehto pysyy totena. Heti kun ehto ei enään ole voimassa, sovelluksen suoritus jatkuu toistolausetta seuraavasta lauseesta.
while (ehto) lause;
while (ehto) {
lause1;
lause2;
lauseN;
}
Alla oleva esimerkki tulostaa luvut 1-10:
int lkm = 1;
while (lkm <= 10) {
Console.WriteLine(lkm);
lkm++;
}
Rakenne muistuttaa while-toistolausetta. Merkittävin ero on kuitenkin siinä, että do while -lauseessa ehto suoritetaan vasta lauseiden suorittamisen jälkeen eikä ennen lauserakennetta. Näin ollen do while -toistolauserakenne suoritetaan ainakin yhden kerran.
do lause; while (ehto);
do {
lause1;
lause2;
lauseN;
} while (ehto);
Alla oleva esimerkki tulostaa satunnaisia lukuja väliltä 0-10, kunnes satunnainen luku on 10.
int sLuku;
Random rand = new Random();
do {
sLuku = rand.Next(10);
Console.WriteLine("Arvottu satunnaisluku on : " + sLuku);
} while (sLuku != 10);
Alla olevassa esimerkissä käyttäjältä kysytään yksi luku väliltä 1-21. Luku tarkistetaan että se on annetulla välillä. Jollei se ole niin käyttäjälle annetaan ilmoitusasiasta, ja ohjelman suoritus päättyy. Tee ohjelmaan seuraavat muutokset: 1) Muuta ohjelmaa että käyttäjältä kysytään lukua niin kauan että se on annetulta väliltä 2) Tee ohjelmaan muutos että käyttäjä voi pelata niin kauan kunnes hän antaa syötteeksi: X tai exit. Jos käyttäjä antaa jonkin muun merkkijonon joka ei ole kokonaisluku käyttäjälle näytetään ohje sallituista syötteistä. 3) Tee muutos että myös pöydän korttien arvo arvotaan väliltä 10-21. Näytä arvottu luku tuloksen yhteydessä.
for-silmukka toistaa lausetta tai lauseita, kunnes toistoehto ei ole enään voimassa. Toistorakenetta käytetään yleensä yksikertaisissa toistolauseissa, joissa toistomäärä on tiedossa. Lauserakenne eroaa huomattavasti muista toistorakenteista. for-silmukassa on tavallisesti mukana kaikki kolme osaa:
- alustus: määritellään kierroslaskuri, jonka avulla määritellään for-silmukan toistomäärä
- ehto: määrää toiston jatkumisen
- päivitys: kasvatetaan tai vähennetään kierroslaskuria
for (alustus; ehto; päivitys) lause;
for (alustus; ehto; päivitys) {
lause1;
lause2;
lauseN;
}
Alla oleva esimerkki tulostaa kymmenen tähtimerkkiä:
for (int i=0; i<10; i++) Console.Write("*");
foreach-toistolauseella voidaan käydä taulukon tai kokoelman alkiot helposti läpi. Toiston aikana kokoelmasta ei saa poistaa tai lisätä alkioita.
foreach (tyyppi muuttuja in kokoelma) {
// muuttuja on vuorossa oleva alkio kokoelmasta
}
Alla oleva tulostaa nimet konsolille:
string[] nimet = {"Kirsi", "Pirjo", "Martti"};
foreach (string nimi in nimet) {
Console.WriteLine(nimi);
}
break-lauseella voidaan katkaista toistolauseen toisto. Toinen yleinen käyttökohde break-lauseelle on aikaisemmin esitelty switch-lauseesta poistuminen.
Alla olevassa esimerkissä poistutaan silmukasta, kun käyttäjä antaa negatiivisen luvun:
while (true) { // ikuinen silmukka
Console.Write("Anna luku > ");
int luku = int.Parse(Console.ReadLine());
...
if (luku < 0) break;
}
// suoritus jatkuu täältä, kun annettu luku on < 0
continue-lauseella voidaan aloittaa toistorakenteen "uusi kierros". Lauseella voi siis ohittaa loput toistolauserungon lauseista.
while (gameOn) {
MovePlayer();
CheckCollision();
if (objectsLeft == 0) continue;
// näitä lauseita ei suoriteta, jos objectsLeft-arvo on 0
MoveObjects();
DoPhysics();
}
return-lause lopettaa metodin suorittamisen ja palauttaa arvon, jos palautin tyyppi on määritelty metodin esittelyssä.
public int PalautaSuurempi(int luku1, int luku2) {
if (luku1 > luku2) return luku1;
else return luku2;
}
Tiettyissä tilanteissa on tärkeää esittää luvut, päivämäärät jne oikeassa muodossa loppukäyttäjälle. Seuraava esimerkki valaisee asiaa kuinka homma voidaan hoitaa .NET:ssä.