MS certificering: 70-483, deel 3

Ingediend door Dirk Hornstra op 12-mrt-2018 22:33

We gaan door met het tweede deel van de C# Jump Start video. In totaal zijn er 8 delen. Wederom een uur, wat anders eigenlijk in een dag behandeld wordt. We gaan het deze keer hebben over het maken van complexe types, object interfaces en overerving en generics.

Een class of een struct definieert een sjabloon voor een object. Een class is een reference type, waarbij een struct een value type is. Dit heeft invloed op het geheugengebruik. Een class bevat eigenschappen, methods en functies, events. Je zou een persoon in je code kunnen opslaan als een struct, maar daar moet je dus wel mee oppassen. Een struct is een blok data, een class bevat een referentie naar het object, waarbij je naar dezelfde persoon kunt verwijzen / class.

Een struct gebruiken doe je als het "klein" is, meestal onderdeel van een ander type, representeert meestal een enkele waarde. De waardes wijzigen niet en worden zelden "geboxed". Als je veel berekeningen moet uitvoeren, zou je de extra stappen die reference types hebben kunnen elimineren door structs te gebruiken, wat zo performance voordelen biedt. Blijft wel staan dat als je erg veel structs gebruikt, je dan ook flink wat geheugen nodig hebt. Als je continu de waardes aan het wijzigen bent, zul je dus beter voor een class kiezen. 

Class. Dit is een object, reference type. Je kunt het declareren als: static (kan niet geïnstantieerd worden), abstract: incomplete class, moet in een geërfde class uitgewerkt worden. Of sealed, in dat geval kan er geen andere class van overerven en ook niet uitbreiden.  We krijgen een voorbeeld van code. Het voorbeeld toont een "partial class". Hierdoor kun je over meerdere bestanden dezelfde class specificeren. Omdat ze binnen dezelfde namespace zitten. Hierdoor kunnen developers onafhankelijk van elkaar hun eigen code toe te voegen. Ook wordt Entity Framework genoemd. Deze genereert de code. Als je geen partial classes had, zou je niet je eigen uitbreidingen kunnen toevoegen, omdat dit elke keer overschreven zou worden. Vervolgens wordt "public" toegevoegd. Als iemand de assembly koppelt, kan hij/zij deze class gebruiken. Vervolgens een private void functie, deze kan alleen intern in je class gebruikt worden. Als je het binnen je eigen assembly beschikbaar wilt maken, dan maak je het "internal". 

Hierna wordt een class Poodle : Dog aangemaakt. We willen dat deze ook een functie van Dog kan gebruiken. Vervolgens declareer je daar de functie als "protected". Hierna voegen we de property's toe bij de class Dog, we geven deze een naam. Dat is de welbekende public string Name { get;set; } De lange versie, met een private variabele en een get/set gebruik je als je zaken gaat formatteren of andere acties uit/gaat voeren. Ook krijg je hier te zien dat je bijvoorbeeld private set kunt instellen, zodat je de "setter" voor buitenaf afschermt. Hierna nog wat uitleg over het toevoegen van parameters en de standaard waardes. Ook hoe je een functie met verschillende parameters kun configureren. Ook met default waardes. Degene die ik hier nog niet kende is dat je een default parameter "overslaat". Maar met meerdere implementaties moet dat met een trucje, of eigenlijk geen trucje, maar "named arguments". Je hebt een functie public void Speak(int times, string saywhat = "goedemorgen", bool sit = false); Wil je die zonder saywhat aanroepen (dus de default houden), maar de sit op true instellen, dan doe je dat met: this.Speak(2, sit: true);

Hierna gaan we een event aanmaken, public event EventHandler HasSpoken; Een event kan "terugpraten". Het is een communicatiemiddel om zaken naar jou terug te koppelen. Vervolgens roept hij het aan met HasSpoken (this, EventArgs.Empty). Maar je moet hier wel een if (HasSpoken != null) aan toevoegen om te controleren of er iets naar het event luistert. Anders hoef je het niet te triggeren. Je kunt de eventhandler uitbreiden met generics. De eventhandler is een "delegate". Omdat het zo'n standaard patroon is, is de class EventHandler beschikbaar om te gebruiken. Je meldt je aan voor een event (subscribe). We maken een nieuwe class Trainer. Deze krijgt een dog.HasSpoken +=  dog_HasSpoken. Met tab krijg automatisch een void dog_HasSpoken(object sender, EventArgs e). 

We gaan naar object instances en inheritance. Static, abstract en sealed zijn al benoemd. Virtual methods hebben implementaties, in afgeleide klassen kunnen deze "overridden" worden. We krijgen een voorbeeld met overerving. Class 1 heeft eigenschap Name, Class 2 erft van Class 1 en heeft een eigen eigenschap Age, Class 3 erft van Class 2 en heeft een eigenschap Address. Een object van Class 3 heeft dus al deze 3 eigenschappen. Cast je dat object naar een Class 1, dan heeft hij nog steeds deze eigenschappen (alleen kun je er dan niet bij komen). We gaan naar de voorbeelden. De BaseClass heeft een internal override void Name() die een waarde naar de console schrijft. Een class DerivedOverride heeft internal override void Name() die een waarde naar de console schrijft, dit vervangt de functie uit de base class, je kunt hier nog de base.Name in aanroepen. Nog een class DerivedNew, deze heeft een internal new void Name(). Hiermee kun je de base.Name niet meer aanroepen, je vervangt deze compleet met je eigen implementatie. Dan heb je nog een DerivedOverride. internal void Name() deze "verbergt" het overschrijven, wat je eigenlijk niet zou moeten doen. Want mensen die je code niet kennen weten niet dat je nu eigenlijk wat overschrijft (het is een impliciete new). 

Als je alles naar BaseClass cast krijgt je bij alle de output van de BaseClass, behalve bij de override. 

We gaan naar Generics. Het introduceerde type parameters. Hiermee stel je het type specificatie uit tot het moment dat je class of methode declareert. Meestal gebruik je deze voor strongly-typed collections (lists, hash tables, queue's). Hiervoor moest je alle lijsten op alle plaatsen aanpassen. In een ArrayList kun je alles toevoegen, nummers, teksten omdat het objecten accepteert. Maar je moet dus altijd controleren wat het echte type is. Met een List<int> zorg je dat het type altijd bekend is. We krijgen boxing en unboxing. Het neemt tijd om het om te zetten. Waardes worden gekopieerd. Het voorbeeld wat we zien is een int x = 1, dan object a = x en vervolgens x++, x = 2, maar a is nog steeds 1. Soms moet je wel boxen en unboxen, maar dat moet je strategisch doen, zo weinig mogelijk. Je kunt je eigen generieke classes maken. Als je deze maakt, houdt dan in gedachten: welke types je wilt generaliseren, welke restricties moeten gelden, of je een generieke base class wilt maken of je generieke interfaces wilt maken. Generieke methoden mag je ook met niet-generieke classes maken. 

In het voorbeeld wordt een ontbijt weergegeven.

 

void Breakfast()
{

  var bird = new Animal<Egg>();
  var pig = new Animal<Piglet>();
}

public class Animal<T> where T : Offspring 
{
    public T Offspring { get; set; }
}
public abstract class Offspring {}
public class Egg : Offspring {}
public class Piglet : Offspring {}
 


Het bekijken gaat in 2 delen, 12 maart een half uur en 16 maart een half uur omdat ik deze week een paar avonden weg ben. Het volgende hoofdstuk gaat over itereren, jump, switch, types manipuleren en het werken met strings. Klinkt niet als een heel moeilijk hoofdstuk.