Een klasse in het object georiënteerd programmeren de basis voor eender welke entiteit.
Voorbeelden:
In het dagelijkse leven zou je ook kunnen zeggen dat de klasse Auto een sjabloon is voor alle reële auto's, en voor elke auto vastlegt dat deze kan accelereren, remmen, wat de topsnelheid is, of er ABS aanwezig is, ...
Je krijgt volgende code te zien:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace WpfKlassen
{
class Circle
{
}
}
Hier maken we een nieuwe klasse met de naam Circle aan, het definiëren van een klasse wordt gekenmerkt door het sleutelwoord class.
Visual Studio maakt een klasse aan met dezelfde naam als de .cs-file. Dit is geen verplichting. Het is zelfs mogelijk om meerdere klassen binnen een .cs-file te herbergen.
In WPF is het perfect mogelijk cirkels te tekenen met de bestaande klassenstructuur, maar om eductieve doeleinden is het aan te raden om de theorie rond klassen te bekijken aan de hand van gekende, eenvoudige voorbeelden.
Vandaar dat we in dit hoofdstuk een eigen klasse Circle maken, iedereen kan zich gemakkelijk voorstellen wat een cirkel is.
Stel je voor dat we de oppervlakte (area) van een cirkel willen berekenen met behulp van de klasse cirkel.
Om de oppervlakte van een cirkel te berekenen hebben we de straal (radius) van de cirkel nodig en het getal pi (vinden we -natuurlijk afgerond- door gebruik te maken van de klasse Math: Math.PI).
class Circle
{
int radius;
}
class Circle
{
int radius;
double Area()
{
return Math.PI * radius * radius;
}
}
Een klasse kan dus fields (variabelen) als methoden bevatten.
Zowel variabelen als methoden kwamen al aan bod tijdens een vorig hoofdstuk.
Een klasse vormt een sjabloon, een blauwdruk voor objecten of instanties.
De klasse Circle kunnen we nu dus gebruiken om Circle-objecten of Circle-instanties aan te maken.
Voorzie een Window_Loaded eventhandler in Window1:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Circle c; //Circle-variabele declareren
c = new Circle(); //Circle-variabele initialiseren
}
Probleem:
Fields en methods binnen een klasse zijn standaard gedeclareerd als private: ze kunnen enkel gebruikt worden binnen de klasse zelf.
class Circle
{
public int radius;
public double Area()
{
return Math.PI * radius * radius;
}
}
Vanuit Window1 kunnen we de public members van Circle benaderen:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Circle c;
c = new Circle();
c.radius = 5;
double opp = c.Area();
MessageBox.Show(opp.ToString());
}
Met een constructor in een klasse is het mogelijk velden van een nieuw object onmiddellijk te initialiseren.
Wanneer je zelf geen constructor voorziet in je klasse, dan genereert de compiler een standaardconstructor voor je klasse: elk veld van de klasse moet nu eenmaal geïnintialiseerd worden.
class Circle
{
public int radius;
public Circle() //standaard parameterloze constructor
{
radius = 0;
}
public double Area()
{
return Math.PI * radius * radius;
}
}
Wanneer een nieuw Circle-object gemaakt wordt, wordt radius op 0 ingesteld (dit is ook zo als we zelf geen defaultconstructor voorzien).
Conclusie:
Daar constructors eigenlijk speciale methoden zijn, kunnen we deze net zoals gewone methoden overloaden.
Een constructor heeft nooit een returnwaarde, overloads van een constructor zullen dus enkel verschillen in de parameterlijst.
public int radius;
public Circle() //standaard parameterloze constructor
{
radius = 0;
}
public Circle(int beginRadius) //constructor waarbij we meteen de radius initialiseren
{
radius = beginRadius;
}
public double Area()
{
return Math.PI * radius * radius;
}
Circle c;
c = new Circle(5);
double opp = c.Area();
MessageBox.Show(opp.ToString());
Opgepast, dikwijls wordt in een constructor een argument ontvangen in een variabele met dezelfde naam als een bestaand field. Nu moet je het sleutelwoord this gebruiken om te verwijzen naar het field:
public Circle(int radius) //constructor waarbij we meteen de radius initialiseren
{
this.radius = radius;
}
Een klasse kan veel methoden, velden, constructors en andere items bevatten.
Een nuttige klasse kan op die manier behoorlijk omvangrijk worden.
Met C# kan je de broncode voor een klasse uitsplitsen in meerdere beheersbare stukken code, en zelfs over meerdere fysieke bestanden.
Dit principe wordt eigenlijk gebruikt in elke WPF-toepassing of ASP.Net Website die je maakt en heet partial classes.
public partial class Window1 : Window
<Window x:Class="WpfKlassen.Window1" ...
Eigenlijk vormt de code die je voorziet in Window1.xaml.cs samen met de code die we vinden in Window1.xaml de klasse Window1.
De twee files vormen dus bij compilatie één klasse!
Vandaar dat wijzigingen die je in de ene file uitvoert onmiddellijk hun weerslag hebben in de andere: een control die je op het Window voorziet is direct bruikbaar in de codebehind-file.
partial class Circle
...
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace WpfKlassen
{
partial class Circle
{
public double Circumference()
{
return 2 * radius * Math.PI;
}
}
}
Circle c;
c = new Circle(5);
double opp = c.Area();
double circ = c.Circumference();
MessageBox.Show(
String.Format
(
"Opp.: \t{0}\nOmtrek:\t{1}"
,opp.ToString()
,circ.ToString()
)
);
Een leuk nieuwigheidje dat hier ook wordt geïntroduceerd is het gebruik van de String.Format methode.
Op deze manier kan je een tekenreeks voorzien van plaatshouders (int waarden tussen accoloades), en de variabele stukken als parameters meegeven.
Toepassing: Point
Maak een klasse Point bestaande uit twee parial klasses in twee files (Point.cs en Point2.cs).
Een Point wordt in onze toepassing gedefiniëerd in een vlak en heeft dus een x- en een y-coördinaat, beide zijn int waarden.
Voorzie een constructor waarmee we een Point kunnen initialiseren en onmiddellijk x en y kunnen aangeven.
Voorzie een methode DistanceTo voor een Point. Deze methode van de Point-klasse ontvangt een Point-variabele en berekent de afstand tussen deze twee punten als double.
Voor deze berekening is wat wiskunde nodig uit het secundair onderwijs, wellicht is Pythagoras geen volstrekt onbekende voor jou.
Constructors en fields komen in Point.cs, methoden maak je in Point2.cs.
Maak een WPF-toepassing waarmee de gebruiker de input kan voorzien en de afstand kan berekenen:
Toon /verberg
Wanneer je hier wat dieper bij nadenkt, dan merk je dat dit in ons voorbeeld met de klasse Point zou neerkomen op de code Point.DistanceTo om de afstand tussen punten te berekenen.
Dit lukt echter niet, DistanceTo is bij ons een instantiemethode: we hebben een instantie van de klasse Point nodig:
Point p = new Point(5,6)
p.DistanceTo(...
Zoals de voorbeelden met de klasse Math aantonen is het ook mogelijk om methoden, maar ook fields te declareren die je rechtstreeks met de naam van de klasse kan aanroepen. Dergelijke methoden of fields noemen we static.
Point2.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace WpfKlassen { partial class Point { public double DistanceTo(Point p) { int xdiff = Math.Abs(x - p.x); int ydiff = Math.Abs(y - p.y); double diff = Math.Sqrt(Math.Pow(xdiff, 2) + Math.Pow(ydiff, 2)); return diff; } public static double DistanceTo(Point p1, Point p2) { return p1.DistanceTo(p2); } } }
Nu kan je in Window1 de afstand tussen twee punten als volgt berekenen:
Point p1 = new Point(Convert.ToInt32(txtP1x.Text),Convert.ToInt32(txtP1y.Text));
Point p2 = new Point(Convert.ToInt32(txtP2x.Text),Convert.ToInt32(txtP2y.Text));
double afstand = Point.DistanceTo(p1, p2);
lblAfstand.Content = afstand.ToString();
Dit heeft grondige gevolgen: de data die een static field bevat is dezelfde voor elke instantie van de klasse.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace WpfKlassen
{
partial class Point
{
public int x;
public int y;
public static int objectCount;
public Point()
{
objectCount++;
}
public Point(int x, int y):this()
{
this.x = x;
this.y = y;
}
}
}
Een static field van het type int met de naam objectCount werd gedeclareerd.
In de parameterloze constructor zorgen we ervoor dat objectCount met 1 wordt opgehoogd als een Point wordt geïnstantieerd.
We willen natuurlijk dat dit ook gebeurt wanneer we de andere constructor gebruiken. Nu zou je de opdracht objectCount++ hier kunnen hernemen, beter is echter de constructors aan elkaar te ketenen (constructor-chaining).
Dit doen we door vanuit een constructor een andere constructor aan te roepen met behulp van :this()-syntax.
private void btnBereken_Click(object sender, RoutedEventArgs e)
{
Point p1 = new Point(Convert.ToInt32(txtP1x.Text),Convert.ToInt32(txtP1y.Text));
ToonAantalPoints();
Point p2 = new Point(Convert.ToInt32(txtP2x.Text),Convert.ToInt32(txtP2y.Text));
ToonAantalPoints();
Point p3 = new Point();
ToonAantalPoints();
double afstand = Point.DistanceTo(p1, p2);
lblAfstand.Content = afstand.ToString();
}
private void ToonAantalPoints()
{
MessageBox.Show("Aantal Point-objecten: " +Point.objectCount.ToString());
}
partial class Point
{
public int x;
public int y;
public static int objectCount;
public Point():this(0,0)
{
}
public Point(int x, int y)
{
this.x = x;
this.y = y;
objectCount++;
}
}
var anoniempje = new { Naam = "William", Leeftijd = 35 };
MessageBox.Show(
String.Format(
"anoniempje heet {0} en is van het type {1}"
,anoniempje.Naam
,anoniempje.GetType().Name
)
);
Hier wordt een anonieme klasse gemaakt met twee eigenschappen Naam en Leeftijd.
In de MessageBox zie je de door de compiler toegekende klassennaam.
Een eerte manier om een klasse te hergebruiken kan zijn om de cs-files heel eenvoudig te kopiëren naar een ander project, eventueel het namespace-argument aan te passen en klaar is Kees.
Wanneer je nu iets verder denkt zie je meteen in dat dit niet 'the way to go' is: wat als we een aanpassing willen doen aan een klasse? Gaan we nu alle projecten openen waarin we die klasse hebben gekopiëerd en doen we zoveel keer die aanpassing? Al vlug zal je in een situatie verzeild raken waar je verschillende versies van dezelfde klasse hebt.
Een veel doordachter en onderhoudsvriendelijker aanpak is het herbergen van je klassen in een Class Library: een klassenbibliotheek.
Een Class Library wordt gecompileerd in een .dll-file.
Maakt het project met de Class Library waarnaar je een referentie wenst te leggen deel uit van de huidige solution, dan is het tabblad projects die eenvoudigste manier. Je kan ook het tabblad browse gebruiken om de benodigde dll te selecteren.
Ivo.Hbo.Geometry.Point en Ivo.Hbo.Geometry.Circle in plaats van Point en Circle, we doen dit niet.
Je ziet onmiddellijk in dat dit voor een grotere toepassing een titanenwerk is, daarom kan je door middel van een using-statement voorzien dat de naam van de klasse voldoende is.
...
using Ivo.Hbo.Geometry;
namespace WpfKlassen
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public partial class Point
Blijkbaar bestaat er reeds een klasse met de naam Point in de namespace System.Windows.
We lossen dit op door het using statement in Window1.xaml.cs aan te passen:
using IHG = Ivo.Hbo.Geometry;
IHG is hier een verkorte schrijfwijze die we kunnen gebruiken voor Ivo.Hbo.Geometry
Je kan dit snel doen door 'zoeken en vervangen' (CTRL + H), opgepast dat je geen verkeerde vervangingen doet, best werken met Replace en Find Next (huidige overslaan) in plaats van 'Replace all'.
private void btnBereken_Click(object sender, RoutedEventArgs e)
{
IHG.Point p1 = new IHG.Point(Convert.ToInt32(txtP1x.Text),Convert.ToInt32(txtP1y.Text));
ToonAantalPoints();
IHG.Point p2 = new IHG.Point(Convert.ToInt32(txtP2x.Text),Convert.ToInt32(txtP2y.Text));
ToonAantalPoints();
IHG.Point p3 = new IHG.Point();
ToonAantalPoints();
double afstand = IHG.Point.DistanceTo(p1, p2);
lblAfstand.Content = afstand.ToString();
}
Je merkt dat je dergelijke acties beter niet te vaak moet uitvoeren, het is beter de gewoonte aan te nemen om een verzameling bij elkaar horende klassen meteen in een Class Library te steken.
| Meer tutorials: |
| leer ook: | html | | xhtml | | css | | asp | | asp.net | | c# | | ado.net | | linq | | ajax | | java | | javascript |