De anatomie van een C#-programma – Structuren en klassen

print

Inhoud


Wat vooraf ging

  • U hebt reeds kennis van eenvoudige programmeerstructuren zoals Array’s.

Wat zijn structuren, klassen en objecten?

Klassen en Structuren zijn datastructuren die velden, eigenschappen, methoden en gebeurtenissen bevatten die logisch bij mekaar horen.

Classes and structs are two of the basic constructs of the common type system in the .NET Framework. Each is essentially a data structure that encapsulates a set of data and behaviors that belong together as a logical unit. The data and behaviors are the members of the class or struct, and they include its methods, properties, and events, and so on.

Het aanmaken van een Class:

public class Customer
{
    //Fields, properties, methods and events go here...
}

Het aanmaken van een Struct:

public struct PostalAddress
{
    // Fields, properties, methods and events go here...
}

Klassen en structuren zijn als “blauwdrukken” die gebruikt worden om instanties of objecten aan te maken. Zo bestaat er reeds een class Button (in Xamarin) die alle velden, eigenschappen, methoden en gebeurtenissen (events) bevat die we kunnen gebruiken om een knop toe te voegen aan ons project.

Om deze klasse te kunnen gebruiken creëren we een button-object, een instantie, van deze klasse. We spreken dan van object georiënteerd programmeren (OOP).

A class or struct declaration is like a blueprint that is used to create instances or objects at run time. If you define a class or struct called Person, Person is the name of the type. If you declare and initialize a variable p of type Person, p is said to be an object or instance of Person.

Een object creëren

Een object wordt gecreëerd door een nieuwe instantie van de klasse (of structuur) te nemen. Dit gebeurt, bij een klasse, met het woordje new. Een structuur kan ook zonder new gecreëerd worden.

Customer object1 = new Customer();

The terms class and object are sometimes used interchangeably, but in fact, classes describe the type of objects, while objects are usable instances of classes. So, the act of creating an object is called instantiation. Using the blueprint analogy, a class is a blueprint, and an object is a building made from that blueprint.

Verschil tussen klassen en structuren

Er zijn een aantal verschillen tussen klassen en structuren (waaronder een aantal meer technische. Het belangrijkste is wellicht dat:

Een klasse werkt op basis van een referentie.

A class is a reference type. When an object of the class is created, the variable to which the object is assigned holds only a reference to that memory. When the object reference is assigned to a new variable, the new variable refers to the original object. Changes made through one variable are reflected in the other variable because they both refer to the same data.

Een structuur werkt op basis van een kopie.

A struct is a value type. When a struct is created, the variable to which the struct is assigned holds the struct’s actual data. When the struct is assigned to a new variable, it is copied. The new variable and the original variable therefore contain two separate copies of the same data. Changes made to one copy do not affect the other copy.

Praktisch zullen structuren gebruikt worden om eenvoudig eigenschappen samen te nemen. Bijvoorbeeld, coördinaten bestaan meestal uit een x-coördinaat en een y-coördinaat. Beiden coördinaten kunnen worden samengenomen in een structuur (hieronder volgt een voorbeeld).

Klassen zijn vaak complexere en bevatten meerdere velden, eigenschappen, methoden en gebeurtenissen die eventueel later worden toegevoegd.

In general, classes are used to model more complex behavior, or data that is intended to be modified after a class object is created. Structs are best suited for small data structures that contain primarily data that is not intended to be modified after the struct is created.


Structuren

Structuren kan je het best zien als waarden die samen een logisch geheel vormen. Bijvoorbeeld een coördinatenstelsel, een (post)adres,…

Structuren worden gedeclareerd met het woordje struct gevolgd door de naam van de structuur (hier CoOrds). De structuur moet overal beschikbaar zijn en is dus public.

public struct CoOrds
{
    public int x, y;

    public CoOrds(int p1, int p2)
    {
        x = p1;
        y = p2;
    }
}

Bovenstaande structuur bevat twee eigenschappen:

public int x, y;

en een constructor die kan gebruikt worden om de eigenschappen waarden toe te kennen.

public CoOrds(int p1, int p2)
{
   x = p1;
   y = p2;
}

In tegenstelling tot een klasse kan een structuur geen “default constructor” bevatten, een “default constructor” is een constructor zonder parameters.

Instanties kunnen genomen worden via new.

// Initialize:
CoOrds coords1 = new CoOrds();
CoOrds coords2 = new CoOrds(10, 10);

of rechtstreek:

// Declare an object:
CoOrds coords1;

// Initialize:
coords1.x = 10;
coords1.y = 20;

De volledige code:

met new

using System;
					
public class Program
{
	
	public struct CoOrds
	{
		public int x, y;
	
		public CoOrds(int p1, int p2)
		{
			x = p1;
			y = p2;
		}
	}
		
	public static void Main()
	{
		// Initialize:   
        CoOrds coords1 = new CoOrds();
        CoOrds coords2 = new CoOrds(10, 10);

        // Display results:
        Console.Write("CoOrds 1: ");
        Console.WriteLine("x = {0}, y = {1}", coords1.x, coords1.y);

        Console.Write("CoOrds 2: ");
        Console.WriteLine("x = {0}, y = {1}", coords2.x, coords2.y);
	}
}

zonder new

using System;
					
public class Program
{
	
	public struct CoOrds
	{
		public int x, y;
	
		public CoOrds(int p1, int p2)
		{
			x = p1;
			y = p2;
		}
	}
		
	public static void Main()
	{
        // Declare an object:
        CoOrds coords1;

        // Initialize:
        coords1.x = 10;
        coords1.y = 20;

        // Display results:
        Console.Write("CoOrds 1: ");
        Console.WriteLine("x = {0}, y = {1}", coords1.x, coords1.y);

	}
}


Klassen

Declaratie

Klassen worden gedeclareerd met het woordje class gevolgd door de naam van de klasse (hier Persoon). Een klasse moet, normaal, overal beschikbaar zijn en is dus public.

public class Persoon
{
}

Velden en eigenschappen

Een object, u weet wel, de instantie genomen van een klasse, heeft meestal een aantal eigenschappen. Neem nu onze persoon, een persoon heeft een achternaam, een voornaam en een geboortedatum.

Achternaam, voornaam en geboortedatum worden dus velden, of eigenschappen/properties, binnen de klasse Persoon. Het verschil tussen een veld en een eigenschap/property is dat een eigenschap/property uitgebreid wordt met een set om waarden toe te kennen, en een get om waarden op te vragen.

Fields and properties represent information that an object contains. Fields are like variables because they can be read or set directly. Properties have get and set procedures, which provide more control on how values are set or returned.

We breiden onze klasse uit met 3 eigenschappen/properties: Achternaam, Voornaam en Geboortedatum. Al deze eigenschappen zijn public en hebben een standaard get; set;

public string Achternaam { get; set; }
public string Voornaam { get; set; }     
public DateTime Geboortedatum { get; set; }

Onze klasse wordt nu:

public class Persoon
{
    public string Achternaam { get; set; }
    public string Voornaam { get; set; }     
    public DateTime Geboortedatum { get; set; }
}

Een klasse bevat enkel eigenschappen (en bij uitbreiden methoden en events) die samen een geheel vormen. Bv. een eigenschap inschrijvingsdatum zou hier niet op zijn plaats zijn omdat deze niet verwijst naar de Persoon maar bv. naar de datum waarop deze persoon zich ingeschreven heeft in bv. een cursus. Dit wordt het Single Responsibility Principle genoemd:

The Single Responsibility Principle (SRP) states that a class should have only one reason to change.

Constructor

Een constructor stelt standaard waarden in die worden toegekend wanneer een instantie wordt genomen van de klasse. De default constructor heeft dezelfde naam van de klasse en geen parameters. De default constructor is altijd public. De default constructor stelt standaardwaarden in voor de eigenschappen.

public Persoon()
{
   Achternaam = "";
   Voornaam = "";
   Geboortedatum = DateTime.Today;
}

Onze klasse wordt nu:

public class Persoon
{

    public string Achternaam { get; set; }
    public string Voornaam { get; set; }     
    public DateTime Geboortedatum { get; set; }

    public Persoon()
    {
	Achternaam = "";
	Voornaam = "";
	Geboortedatum = DateTime.Today;
    }
}

Instantiation – Objecten creëren

Nu de klasse Persoon aangemaakt is kunnen we een instantie nemen van deze klasse. Dit gebeurt via new.

Persoon Ik = new Persoon();

Vervolgens kunnen we de standaardwaarden weergeven.

Console.WriteLine(Ik.Achternaam + ' ' + Ik.Voornaam + ' ' + Ik.Geboortedatum.ToShortDateString());

De volledige code is nu:

using System;

public class Persoon
{
    public string Achternaam { get; set; }
    public string Voornaam { get; set; }     
    public DateTime Geboortedatum { get; set; }

        public Persoon()
        {
	        Achternaam = "";
	        Voornaam = "";
	        Geboortedatum = DateTime.Today;
        }
}
				
public class Program
{
	public static void Main()
	{
		Persoon Ik = new Persoon();
		
		Console.WriteLine(Ik.Achternaam + ' ' + Ik.Voornaam + ' ' + Ik.Geboortedatum.ToShortDateString());
	}
}

Encapsulation

Encapsulation is een belangrijk OOP-principe. Het bepaalt dat de klasse zelf de status van de velden, eigenschappen en de werking van de methoden en events voor zich neemt.

Encapsulation refers to the idea that objects should manage their own behavior and state, so that their collaborators need not concern themselves with the object’s inner workings.

Laten we dit verduidelijken met een voorbeeld. De geboortedatum kan uiteraard niet in de toekomst liggen. De ingevoerde geboortedatum zal dus moeten gecontroleerd. Het encapsulation-principe zegt nu dat deze controle dient te gebeuren binnen de klassen en dus niet mag worden overgelaten aan de programmeur die de klasse gebruikt.

Deze controle dient dus te gebeuren bij het instellen van de eigenschap Geboortedatum. Tot nu toe hebben we een verkorte schrijfwijze gebruikt voor de eigenschappen namelijk:

public DateTime Geboortedatum { get; set; }

Aangezien we de set nu volledig moeten uitwerken om de geboortedatum te kunnen controleren moeten we ook de eigenschap volledig uitwerken. De basisstructuur voor de eigenschap Geboortedatum is de volgende:

private DateTime _GeboorteDatum; // Backing store
public DateTime Geboortedatum
{
   get { return _GeboorteDatum; }
   set {_GeboorteDatum = value;}
}

Merk het gebruik op van een private variabele (_GeboorteDatum), de zogenaamde Backing store het eigenlijke veld dat de waarde bevat. _GeboorteDatum is gewoon een naam, de _ is niet verplicht, vaak wordt ook gewoon een variant gemaakt met kleine letters voor het private veld en hoofdletters voor de publieke eigenschap.

value bevat de waarde die wordt toegekend aan de eigenschap. Het is deze value die we gaan moeten controleren. Dit kan gebeuren via de functie if (DateTime.Compare(value, DateTime.Today) != 1) (DateTime.Compare() vergelijkt 2 datums en geeft de waarde 1 terug als de eerste datum na de tweede datum valt). Indien de datum in de toekomst ligt wordt een foutmelding/exception “gegooid” throw new Exception("De geboortedatum mag niet in de toekomst liggen!");.

Deze foutmelding wordt nadien opgevangen met een try catch.

De volledige code wordt nu:

using System;

public class Persoon
{

    public string Achternaam { get; set; }
    public string Voornaam { get; set; }     
    private DateTime _GeboorteDatum;

	public DateTime Geboortedatum
	{
		get { return _GeboorteDatum; }
		set
		{
			if (DateTime.Compare(value, DateTime.Today) != 1)
			{
				_GeboorteDatum = value;
			}
			else
			{
				throw new Exception("De geboortedatum mag niet in de toekomst liggen!");
			}
		}
	}
		
	public Persoon()
	{
		Achternaam = "";
		Voornaam = "";
		Geboortedatum = DateTime.Today;
	}
}
				
public class Program
{
	public static void Main()
	{
		try{
			Persoon Ik = new Persoon();
			
			Ik.Achternaam = "Linthoudt";
			Ik.Voornaam = "Geert";
			Ik.Geboortedatum = new DateTime(2969,10,1);
				
			Console.WriteLine(Ik.Achternaam + ' ' + Ik.Voornaam + ' ' + Ik.Geboortedatum.ToShortDateString());
		}
		catch(Exception ex)
		{
			Console.WriteLine(ex.Message);
		}
	}
}

Overloading/Polymorfie

Polymorfie staat voor veelvormigheid. In het geval van de informatica wordt hiermee bedoeld het gelijkvormig zijn van de interface van klassen en objecten, maar met verschillende implementaties. De gelijkvormigheid betreft dan voornamelijk het gebruik en de naamgeving van methodes. Een methode, met een specifieke naam, kan dankzij polymorfisme op verschillende manieren ingevuld worden maar toch die specifieke naam behouden.

Overloading (of polymorfie) wilt dus zeggen: een variant maken op, met dezelfde naam maar andere parameters.

Constructor overload

U kunt de constructor New overloaden zodat direct bij het initialiseren de eigenschappen kunnen worden meegegeven.

public Persoon(string Achter, string Voor, DateTime Datum)
{
   Achternaam = Achter;
   Voornaam = Voor;
   Geboortedatum = Datum;
}

Function overload

Niet enkel de constructor kan overload worden, ook functies kunnen overload worden. Iedere klasse heeft standaard een functie ToString(). Deze functie wordt gebruikt om te converteren naar tekst. U kunt deze functie overschrijven Overrides zodanig dat deze de eigenschappen van onze klasse weergeeft op een door ons gekozen wijze.

public override string ToString()
{
   return Voornaam + " " + Achternaam + " - " + Geboortedatum.ToShortDateString();
}

De volledige code wordt nu:

using System;

public class Persoon
{
    public string Achternaam { get; set; }
    public string Voornaam { get; set; }     
    private DateTime _GeboorteDatum;

	public DateTime Geboortedatum
	{
		get { return _GeboorteDatum; }
		set
		{
			if (DateTime.Compare(value, DateTime.Today) != 1)
			{
				_GeboorteDatum = value;
			}
			else
			{
				throw new Exception("De geboortedatum mag niet in de toekomst liggen!");
			}
		}
	}
	
	
	public Persoon()
	{
		Achternaam = "";
		Voornaam = "";
		Geboortedatum = DateTime.Today;
	}
	
	public Persoon(string Achter, string Voor, DateTime Datum)
	{
		Achternaam = Achter;
		Voornaam = Voor;
		Geboortedatum = Datum;
	}
	
	public override string ToString()
	{
	   return Voornaam + " " + Achternaam + " - " + Geboortedatum.ToShortDateString();
	}
}
				
public class Program
{
	public static void Main()
	{
		try{
			Persoon Ik = new Persoon("Linthoudt","Geert",  new DateTime(1969,10,1));
							
			Console.WriteLine(Ik.ToString());
		}
		catch(Exception ex)
		{
			Console.WriteLine(ex.Message);
		}
	}
}

Inheritance/Overerving

Overerving laat toe nieuwe klassen te maken gebaseerd op een bestaande klasse. We spreken dan van basis- en specifiekere/afgeleide klassen. De meer specifieke klasse kan de bestaande klasse uitbreiden met nieuwe velden, eigenschappen, methoden of events of de bestaande methoden en events herschrijven specifiek voor deze specifieke, afgeleide klasse.

Inheritance, together with encapsulation and polymorphism, is one of the three primary characteristics of object-oriented programming. Inheritance enables you to create new classes that reuse, extend, and modify the behavior that is defined in other classes. The class whose members are inherited is called the base class, and the class that inherits those members is called the derived class.

Onderstaande nieuwe klasse Cursist is een meer specifieke klasse van de klasse Persoon, een cursist is tenslotte ook een persoon met specifieke kenmerken. De overerving gebeurt als volgt:

public class Cursist: Persoon
{
}

Merk de dubbelpunt op gevolgd door de naam van de basisklasse. De klasse Cursist erft dus alle velden, eigenschappen, methoden en events van de klasse Persoon en voegt er bv. de eigenschap Cursistnummer aan toe. Onderstaand voorbeeld werkt dit volledig uit:

using System;

public class Persoon
{
    public string Achternaam { get; set; }
    public string Voornaam { get; set; }     
    private DateTime _GeboorteDatum;

	public DateTime Geboortedatum
	{
		get { return _GeboorteDatum; }
		set
		{
			if (DateTime.Compare(value, DateTime.Today) != 1)
			{
				_GeboorteDatum = value;
			}
			else
			{
				throw new Exception("De geboortedatum mag niet in de toekomst liggen!");
			}
		}
	}
	
	
	public Persoon()
	{
		Achternaam = "";
		Voornaam = "";
		Geboortedatum = DateTime.Today;
	}
	
	public Persoon(string Achter, string Voor, DateTime Datum)
	{
		Achternaam = Achter;
		Voornaam = Voor;
		Geboortedatum = Datum;
	}
	
	public override string ToString()
	{
	   return Voornaam + " " + Achternaam + " - " + Geboortedatum.ToShortDateString();
	}
}

public class Cursist: Persoon
{
	private int _Cursistnummer;
	public int Cursistnummer
	{
		get { return _Cursistnummer; }
		set
		{
			if (value >= 0)
			{
				_Cursistnummer = value;
			}
			else
			{
				throw new Exception("Geef een positieve waarde in voor het cursistnummer!");
			}
		}
	}
	
	public Cursist()
	{
		Cursistnummer = 0;

	}
	
	public Cursist(int Nummer, string Achter, string Voor, DateTime Datum)
	{
		Cursistnummer = Nummer;
		Achternaam = Achter;
		Voornaam = Voor;
		Geboortedatum = Datum;
	}

	public override string ToString()
	{
	   return Cursistnummer + ": " + Voornaam + " " + Achternaam + " - " + Geboortedatum.ToShortDateString();
	}

}
					
public class Program
{
	public static void Main()
	{
		try{
			Cursist Ik = new Cursist(1, "Linthoudt", "Geert", new DateTime(1969,10,1));
							
			Console.WriteLine(Ik.ToString());
		}
		catch(Exception ex)
		{
			Console.WriteLine(ex.Message);
		}
	}
}

Methoden

Zou het niet handig zijn moesten we een methode hebben die de achternaam en voornaam van de cursist samen toont?

Aangezien dit niet specifiek is voor een cursist maar voor alle personen kunnen we deze methode het best toevoegen aan de klasse Persoon, door overerving zal ook de cursist deze methode kennen. De methode zelf is simpel:

public string Naam(){
   return Achternaam + " " + Voornaam;	
}

De volledig code wordt nu:

using System;

public class Persoon
{
    public string Achternaam { get; set; }
    public string Voornaam { get; set; }     
    private DateTime _GeboorteDatum;

	public DateTime Geboortedatum
	{
		get { return _GeboorteDatum; }
		set
		{
			if (DateTime.Compare(value, DateTime.Today) != 1)
			{
				_GeboorteDatum = value;
			}
			else
			{
				throw new Exception("De geboortedatum mag niet in de toekomst liggen!");
			}
		}
	}
	
	
	public Persoon()
	{
		Achternaam = "";
		Voornaam = "";
		Geboortedatum = DateTime.Today;
	}
	
	public Persoon(string Achter, string Voor, DateTime Datum)
	{
		Achternaam = Achter;
		Voornaam = Voor;
		Geboortedatum = Datum;
	}
	
	public override string ToString()
	{
	   return Voornaam + " " + Achternaam + " - " + Geboortedatum.ToShortDateString();
	}

	public string Naam(){
		return Achternaam + " " + Voornaam;	
	}
}

public class Cursist: Persoon
{
	private int _Cursistnummer;
	public int Cursistnummer
	{
		get { return _Cursistnummer; }
		set
		{
			if (value >= 0)
			{
				_Cursistnummer = value;
			}
			else
			{
				throw new Exception("Geef een positieve waarde in voor het cursistnummer!");
			}
		}
	}
	
	public Cursist()
	{
		Cursistnummer = 0;

	}
	
	public Cursist(int Nummer, string Achter, string Voor, DateTime Datum)
	{
		Cursistnummer = Nummer;
		Achternaam = Achter;
		Voornaam = Voor;
		Geboortedatum = Datum;
	}

	public override string ToString()
	{
	   return Cursistnummer + ": " + Voornaam + " " + Achternaam + " - " + Geboortedatum.ToShortDateString();
	}

}
					
public class Program
{
	public static void Main()
	{
		try{
			Cursist Ik = new Cursist(1, "Linthoudt", "Geert", new DateTime(1969,10,1));

			Console.WriteLine(Ik.Naam());							
			Console.WriteLine(Ik.ToString());
		}
		catch(Exception ex)
		{
			Console.WriteLine(ex.Message);
		}
	}
}

Overerving is een voorbeeld van het in de praktijk brengen van het Open-Closed Principle.

The Open-Closed Principle (OCP) states that software entities (classes, modules, methods, etc.) should be open for extension, but closed for modification.


Hoor het eens van iemand anders

Laten we nog eens horen wat Bob Tabor te vertellen heeft over klassen.


Behandelde Basiscompetenties uit de module ICT Programmeren – Specifieke ontwikkelomgeving: complexe functionaliteiten

  • IC BC235 – kan gevorderde principes van programmeren in een specifieke ontwikkelomgeving toepassen

Geef een reactie

  • Abonneer je op deze website d.m.v. e-mail

    Voer je e-mailadres in om je in te schrijven op deze website en e-mailmeldingen te ontvangen van nieuwe berichten.