c# : object-gebaseerd programmeren

  1. Inleiding
  2. Klasse, variabele, constructor, methode
  3. Constructors overloaden
  4. Eigenschappen
  5. Compositie
  6. Garbage collection
  7. Static members
  8. Namespaces
  9. Assemblies
  10. oefeningen

Inleiding

Deze cursus bevat de inhoud en oefeningen voor de lessen c# in het tweede jaar van de graduaatsopleiding. Jullie hebben reeds een basiskennis c# opgedaan in het eerste jaar. Het is de bedoeling de kennis van c# dit jaar uit te breiden. We zullen in deze cursus aandacht hebben voor console-applicaties en Windows Forms toepassingen. C# kan ook gebruikt worden voor ASP.net toepassingen, dit vormt een aparte cursus.

In deze cursus zullen cruciale leerstofonderdelen van vorig jaar hernomen en aangevuld worden. We starten deze cursus met een heropfrissing van de principes van het objectgeoriënteerd programmeren. Daarna gaan we verder met overerving en polymorfisme, twee essentiële onderdelen voor c#-toepassingen.

Jullie hebben in het eerste jaar een voldoende basiskennis verworven om een aantal technieken en principes uit te diepen. Uitermate belangrijk hierbij zijn een voldoende kennis van controlestructuren (if, switch, lussen zoals while en for), operatoren en arrays. Een basiskennis over de begrippen klasse, methode, constructor, eigenschap is ook onontbeerlijk. Eventueel zal je met de cursus van vorig jaar deze kennis moeten opfrissen.

Klasse, variabele, constructor, methode

We maken een klasse die de tijd weergeeft in AM/PM en 24-uren notatie, technische info over deze tijdsnotaties vind je bij Mathisfun.

De klasse Tijd

Herhaling van de vroegere leerstof:
In de klasse Tijd vinden we:

De klasse TijdTest

Hierin vinden we de methode Main, het startpunt van de toepassing.

We declareren een object tijd afgeleid van de klasse Tijd en vullen een variabele uit met een tekstbericht naarmate we de variabele tijd aanpassen.
Uiteindelijk brengen we de inhoud van de variabele uit op het scherm met een MessageBox.

Om de tijd in te stellen moeten we gebruik maken van de methode SetTime. De variabele uur, minuut en seconde uit de klasse Tijd zijn niet rechtstreeks te benaderen vanuit de klasse TijdTest doordat ze privaat gedeclareerd zijn.

Constructors overloaden

Constructors bepalen wat er gebeurt wanneer een object van een bepaalde klasse wordt aangemaakt.
In c# heeft een constructor dezelfde naam als de betreffende klasse. Je kan meerdere constructors voorzien voor een klasse, zodanig dat de gebruiker bij het aanmaken van een object kan aangeven welke informatie van het object reeds gekend is. We spreken van het overloaden van een constructor.

Pas de klasse Tijd aan:

Toon volledige code... Toon nieuwe code...

		public Tijd()	// constructor1
		{
			SetTime(0,0,0);
		}

		public Tijd(int uur)	// constructor2
		{
			SetTime(uur,0,0);
		}

		public Tijd(int uur, int minuut)	// constructor3
		{
			SetTime(uur,minuut,0);
		}

		public Tijd(int uur, int minuut, int seconde)	// constructor4
		{
			SetTime(uur, minuut, seconde);
		}

		public Tijd(Tijd tijd)	// constructor5
		{
			SetTime(tijd.uur, tijd.minuut, tijd.seconde);
		}

We passen ook de klasse TijdTest aan:

using System;
using System.Windows.Forms;

namespace Tijd
{

	class TijdTest1
	{
		static void Main(string[] args)
		{
			Tijd tijd1 = new Tijd();
			Tijd tijd2 = new Tijd(10);
			Tijd tijd3 = new Tijd(15,30);
			Tijd tijd4 = new Tijd(7,55,40);
			Tijd tijd5 = new Tijd(tijd2);

			string uit;

			
			uit = "tijd1: " +
				tijd1.ToUniversalString() +
				" - " +
				tijd1.ToStandardString();

			uit += "\n\ntijd2: " +
				tijd2.ToUniversalString() +
				" - " +
				tijd2.ToStandardString();

			uit += "\n\ntijd3: " +
				tijd3.ToUniversalString() +
				" - " +
				tijd3.ToStandardString();

			uit += "\n\ntijd4: " +
				tijd4.ToUniversalString() +
				" - " +
				tijd4.ToStandardString();

			uit += "\n\ntijd5: " +
				tijd5.ToUniversalString() +
				" - " +
				tijd5.ToStandardString();

			MessageBox.Show(uit,"Klasse Tijd testen");
		}
	}
}

Eigenschappen

Met eigenschappen of properties kan je de gebruiker gecontroleerd toegang geven tot private variabelen. Je kan de eigenschappen get (lees) en set (schrijf) mogelijkheden geven.

We passen de klasse Tijd aan voor het gebruik van eigenschappen Uur, Minuut en Seconde.

Deze keer kunnen we de controle voor geldige waarde uitvoeren op eigenschapniveau en hoeven we dit niet in de methode SetTime te doen.

Toon volledige code... Toon nieuwe code...

		public Tijd(Tijd tijd)	// constructor5
		{
			SetTime(tijd.Uur, tijd.Minuut, tijd.Seconde);
		}
		
		//stel de tijd in, controle via de eigenschappen
		public void SetTime(int uurwaarde, int minuutwaarde, int secondewaarde)
		{
			Uur = uurwaarde;
			Minuut = minuutwaarde;
			Seconde = secondewaarde;
		}
		
		//eigenschap Uur
		public int Uur
		{
			get
			{
				return uur;
			}
			set
			{
				uur = ((value >= 0 && value < 24) ? value : 0);
			}
		}
		
		//eigenschap Minuut
		public int Minuut
		{
			get
			{
				return minuut;
			}
			set
			{
				minuut = ((value >= 0 && value < 60) ? value : 0);
			}
		}

		//eigenschap Seconde
		public int Seconde
		{
			get
			{
				return seconde;
			}
			set
			{
				seconde = ((value >= 0 && value < 60) ? value : 0);
			}
		}
		public string ToUniversalString() //24 uur weergave
		{
			return String.Format("{0:D2}:{1:D2}:{2:D2}",Uur,Minuut,Seconde);
		}

		public string ToStandardString() //12 uur weergave
		{
			return String.Format("{0}:{1:D2}:{2:D2} {3}",
				((Uur==12 || Uur == 0) ? 12 : Uur % 12),Minuut,Seconde,(Uur<12?"AM":"PM"));
		}
	}
}

Compositie: objectreferenties als instantievariabele in een andere klasse

We starten een nieuw 'Empty Project' met de naam Werknemers.

In dit project maak je een eerste klasse Datum: Project > add class...

using System;
using System.Collections.Generic;
using System.Text;

namespace Werknemers
{
	public class Datum
	{
		private int dag;
		private int maand;
		private int jaar;

		public Datum(int dag, int maand, int jaar)
		{
			if(maand > 0 && maand < 12) 
				this.maand = maand;
			else
			{
				this.maand = 1;
				Console.WriteLine(
					"Maand is ongeldig, ingesteld op 1");
			}

			this.jaar = jaar; //geen controle op jaar

			this.dag = ControleerDag(dag); //controle op de dag
			
			
		}

		private int ControleerDag(int testDag)
		{
			int[] dagenPerMaand = 
				{ 0,31,28,31,30,31,30,31,31,30,31,30,31};

			//controleer of de dag binnen het bereik van de maand valt
			if(testDag > 0 && testDag <= dagenPerMaand[maand])
				return testDag;

			//controle schrikkeljaar
			if(maand == 2 && testDag == 29 &&
				(jaar % 400 == 0 || 
				(jaar % 4 == 0 && jaar % 100 != 0) ) )
				return testDag;

			Console.WriteLine(
				"Dag ongeldig, ingesteld op 1");

			return 1;
		}

		public string maakDatumString()
		{
			return dag + "/" +maand +"/" +jaar;
		}
	}
}

Maak een klasse Werknemer: Project > add class...

using System;
using System.Collections.Generic;
using System.Text;

namespace Werknemers
{
	
	public class Werknemer
	{
		private string voornaam;
		private string familienaam;
		private Datum geboorteDatum;
		private Datum inDienstDatum;

		public Werknemer(string voor, string achter,
			int gdag, int gmaand, int gjaar,
			int idag, int imaand, int ijaar)
		{
			voornaam = voor;
			familienaam = achter;

			geboorteDatum = new Datum(gdag,gmaand,gjaar);
			inDienstDatum = new Datum(idag,imaand,ijaar);

		}

		public string maakWerknemerString()
		{
			return familienaam +"," +voornaam +
				" In Dienst: " +inDienstDatum.maakDatumString() +
				" Geboortedatum: " +geboorteDatum.maakDatumString();
		}
	}
}

We zien dat de instantievariabelen geboorteDatum en inDienstDatum referenties zijn naar objecten van een andere klasse: de klasse Datum. Dit principe heet compositie.

Maak nog een klasse TestWerknemer: Project > add class...
Leg een referentie naar System.Windows.Forms
Zet via Project > Werknemers Properties het Output type op Windows Application

using System;
using System.Windows.Forms;

namespace Werknemers
{
	public class TestWerknemer
	{

		static void Main(string[] args)
		{
			Werknemer w = new Werknemer("Pietje","Pek",7,4,1970,1,1,2000);
			MessageBox.Show(w.maakWerknemerString(),"Test klasse Werknemer");
		}
	}
}

Garbage Collection

De operator new kent geheugen toe voor een object en roept daarna de constructor van het object aan. Deze constructor kan andere systeembronnen nodig hebben zoals netwerkconnecties, database en bestandstoegang. Objecten moeten dus een gedisciplineerde manier hebben om systeembronnen terug vrij te geven als ze niet langer in gebruik zijn.
Het .net Framework doet zelf aan dergelijke afvalverwerking: 'Garbage Collection'. De Garbage Collector localiseert objecten waarvoor er geen referenties meer bestaan en ruimt deze op.

Het gevaar schuilt erin dat je als programmeur de andere systeembronnen zoals netwerkconnecties, databases en bestanden handmatig moet vrijgeven.
Een techniek om dat te doen is te werken met een destructor: een routine die je schrijft die wordt uitgevoerd wanneer de Garbage Collector het object vernietigd.
Een klasse kan slechts één destructor bevatten. De naam van de destructor is de klassenaam met als prefix een tilde (˜).

Doe volgende aanpassing aan de klasse Werknemer:

Toon volledige code... Toon nieuwe code...

		~Werknemer()
		{
			MessageBox.Show("Werknemer vernietigd: " +
                voornaam + ", " + familienaam, "Destructor");
		}

Pas nu ook de klasse TestWerknemer aan:

using System;
using System.Windows.Forms;

namespace Werknemers
{
	public class TestWerknemer
	{

		static void Main(string[] args)
		{
			Werknemer w = new Werknemer("Pietje","Pek",7,4,1800,1,1,2000);
			MessageBox.Show(w.maakWerknemerString(),"Test klasse Werknemer");
			w = null;

			System.GC.Collect();
			System.GC.WaitForPendingFinalizers();

			Werknemer a = new Werknemer("Guy","Taar",2,5,1950,3,5,1990);
			MessageBox.Show(a.maakWerknemerString(),"Test klasse Werknemer");
			
		}
	}
}

Normaal weet je in een programma niet wanneer de Garbage Collector in actie zal schieten. Je kan dit proces echter forceren met de methode Collect van de Garbage Collector (GC).
De methode WaitForPendingFinalizers zorgt ervoor dat ook alle ongebruikte objecten vernietigd worden die in een parallelle thread met de Garbage Collector draaien.

Static members

Statische variabelen zijn variabelen die door elk object van de klasse gedeeld worden. In tegenstelling tot instantievariabelen hebben ze dus voor elke object van deze klasse dezelfde waarde.

Aanpassingen aan Werknemer:

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;

namespace Werknemers
{
    public class Werknemer
    {
        private string voornaam;
        private string familienaam;
        private Datum geboorteDatum;
        private Datum inDienstDatum;
        private static int aantal;


        public Werknemer(string voor, string achter,
            int gdag, int gmaand, int gjaar,
            int idag, int imaand, int ijaar)
        {
            voornaam = voor;
            familienaam = achter;

            geboorteDatum = new Datum(gdag, gmaand, gjaar);
            inDienstDatum = new Datum(idag, imaand, ijaar);

            ++aantal;

            MessageBox.Show("Werknemer constructor: " +
                this.maakWerknemerString());
            MessageBox.Show("Aantal = " + Aantal);


        }

        public string maakWerknemerString()
        {
            return familienaam + "," + voornaam +
                " In Dienst: " + inDienstDatum.maakDatumString() +
                " Geboortedatum: " + geboorteDatum.maakDatumString();
        }

        ~Werknemer()
        {
            --aantal;

            MessageBox.Show("Werknemer destructor: " +
                this.maakWerknemerString());
            MessageBox.Show("Aantal = " + Aantal);

        }

        public static int Aantal
        {
            get
            {
                return aantal;
            }
        }

    }

}

Aanpassingen aan TestWerknemer:

using System;
using System.Windows.Forms;

namespace Werknemers
{
	public class TestWerknemer
	{

		static void Main(string[] args)
		{
			Console.WriteLine("Aantal werknemers: " +Werknemer.Aantal);
			Werknemer w = new Werknemer("Pietje","Pek",7,4,1800,1,1,2000);
			Werknemer a = new Werknemer("Guy","Taar",2,5,1950,3,5,1990);

			a = null;
			System.GC.Collect();
			System.GC.WaitForPendingFinalizers();
			w = null;
			
		}
	}
}

Namespaces

Elke klasse uit de .net Framework klassenbibliotheek behoort tot een specifieke namespace. Programmeurs moeten zich erop toeleggen om code die ze schrijven herbruikbaar te maken. Echter, op deze manier ontstaan soms conflicten in het naamgeven van klassen: verschillende programmeurs die hun klassen dezelfde naam hebben gegeven.
Namespaces helpen om dit probleem te vermijden: klassen binnen eenzelfde namespace mogen niet dezelfde naam hebben, maar klassen binnen verschillende namespaces kunnen zonder enig probleem dezelfde naam dragen.

Assemblies

Om klassen herbruikbaar te maken kan je werken met een klassenbibliotheek (class library) maken van je klassen, en deze importeren in een ander programma met behulp van het using statement. Enkel klassen gedefiniëerd als public kunnen uit klassenbibliotheken herbruikt worden. Niet publieke klassen in een assembly kunnen enkel door de klassen binnen het assembly gebruikt worden.
Een klassenbibliotheek heeft geen methode Main: de assembly kan niet op zichzelf worden gestart, maar wordt gebruikt binnen een ander programma.

We maken een herbruikbare assembly van de klasse Tijd uit de vorige toepassing.

using System;
using TijdBib;

namespace TijdBibTest
{
	
	class TijdBibTest
	{
	
		static void Main(string[] args)
		{
			Tijd tijd = new Tijd(13,12,6);
			Console.WriteLine("Standaard tijd: {0}\nUniversele tijd: {1}" ,
				tijd.ToStandardString(), tijd.ToUniversalString());

		}
	}
}

Voer uit met CTRL-F5 (Build without debuging) dan heb je tijd om naar de console te kijken.

Oefeningen

oefeningen

Meer tutorials:
leer ook: html | xhtml | css | asp | asp.net | c# | ado.net | linq | ajax | java | javascript
Valid HTML 4.01! Valid CSS! © - Cursusweb