Objektorientiert Programmieren in .NET


Thematik
Die Programmiersprachen C# und VB.NET sind die beiden wichtigsten Programmiersprachen
für die Entwicklung von .NET-Anwendungen.
Sie ermöglichen die objektorientierte- und typsichere Entwicklung anspruchsvoller Software.

Für wen?
Der Kurs richtet sich an Programmierer, die ein solides objekt-orientiertes Verständnis in einer
der .beiden .NET-Sprachen benötigen.

Das sollten Sie mitbringen!
Es sind keine Vorkenntnisse erforderlich

Was bringt es Ihnen?
Der Kurs vermittelt solide Kenntnisse, um .NET Anwendungen für die .NET Plattform zu entwickeln.
Schwerpunkte liegen in den Bereichen: Struktur,Syntax und Objekt-Orientiertes Programmieren

Dauer 5 Tage
Der Kurs wird wunschgemäß in C# oder VB.NET mit VS 2022 gehalten


Inhalte:

Die Net Plattform
Was stellt uns das Framework zur Verfügung ?
Technologien und die Tools für skalierbare Anwendungen mit Fokus Intranet / Internet, aber nicht nur.
Unterstützt werden plattformneutrale Technologien und Protokolle wie SOAP = HTTP +  XML.
Das .Net-Framework - ist mehr als eine Klassenbibliothek.

Die Common-Language-Runtime,
ihre Aufgaben : Garbage Collection, eingebautes Typsystem, dadurch sprachübergreifend,
einfache Installation, Versionierung vorhanden und vor allem Security.

Managed Code
"Hello World", unsere erste Console Anwendung mit dem Notepad erstellt.
In .Net gibt es keinen Code außerhalb von Klassen.

Bei mehreren tausend Klassen bedarf es einer gewissen Ordnung - der Namespace.
Ein Namespace verschafft uns Zugriff auf Namen, nicht auf Code.
"Using Namespace" erspart Schreibarbeit.
Hat der Compiler Probleme damit, dann bitte voll qualifizieren. 

Wie wird der Code im Managed Execution Environment ausgeführt ? 
Code just in time oder komplett kompilieren mit Ngen ?
 
Wie wird das Programm kompiliert ?
Was bekommen wir ?  Ein Assembly, eine ausführbare Einheit.
Was enthält ein Assembly ? Code und viel Information dazu - die Metadaten.

Der "Missel" (MSIL) Code arbeitet wie eine Stackmaschine.
MSIL ist einfach, es gibt auch gar nicht so viele Befehle.
Den können wir uns anschauen, dafür gibt es den Disassembler ILDASM.

Wir reden auch noch über AppDomains - Miniprozesse, und warum es so etwas gibt

Das Visual Studio
Was bietet uns die IDE ? Eine einzige Oberfläche für alle Projekte. 
Innerhalb einer Solution sind Projekte in unterschiedlichen Programmiersprachen möglich.

Aussehen der IDE und ShortCuts der Tastaturprofile können angepasst werden.
Ein integrierter Internetbrowser ist vorhanden. Features wie die dynamische Hilfe,
der Objekt-Browser etc.

Welche Projekte können erstellt werden ?

Der Server Explorer ermöglicht  den Zugriff auf Server - Ressourcen wie EventLogs
und Services. Problemloser Zugriff auf den SQL Server, ein Umschalten zum 
SQL-Enterprise-Manager ist nicht mehr erforderlich.

Welche Möglichkeiten zum Debuggen stehen zur Verfügung ?

Datentypen
CTS, ein gemeinsames Typsystem für alle Sprachen, die Basis für das 
Sprachübergreifende Programmieren.

Alle Typen in .Net  sind Objekte, auch einfache Typen wie short oder int. Diese
Objekte werden aber als Wert-Typen auf dem Stack und nicht auf dem Heap angelegt.

Auch Strukturen sind Werttypen. Wie werden sie deklariert und verwendet ?

Enums gehören ebenfalls dazu, sie können durch beliebige Typen implementiert werden, 
außer durch char. 

Namensgebung: Pascal und camelCasing, wo bleibt die wirklich sinnvolle 
ungarische Notation ?

In C# gibt es 2 Arten Zahlen zu formatieren: mit speziellen Format-Strings 
oder mit der Custom - Formatierung

Der Compiler erzeugt keinen Fehler bei Overflow, noch nicht einmal eine Warnung. 
Dafür gibt es aber ein "checked" - Keyword, das eine Exception erzeugt, 
diese kann man behandeln.


Bedingungen und Schleifen
Scope : Eine Variable in einem Block darf im umschließenden Eltern Block 
nicht erneut definiert werden. 

Bedingte Anweisungen: "if" und "else"
.
"switch-case" vermeidet lange "else-if" Ketten. Als Konstanten sind die folgenden
Typen möglich :int, char, enum und string und implizit konvertierbare.
Es ist kein "Fall Through" erlaubt, dafür gibt es das "goto", aber bitte nur dafür !

Es gibt 4 Anweisungen für Schleifen:  " while", "do", "for" und "foreach".

Es gibt auch noch "break" und "continue".

Exceptions
Warum Exceptions ? Die Realisierung mit try catch.
Vorsicht! Exception Objekte sind nur im Catch Block gültig.

Verschiedene Exception Klassen in .Net.

Mehrfache Catch Blöcke : zuerst spezifische Exceptions dann allgemeine 
Exceptions abfangen. Abgeleitete Exception Klassen dürfen nicht hinter Basis 
Klassen stehen.

Bitte Exceptions auch nur für Ausnahmen verwenden, nicht für den  normalen
Programmablauf !

Mit "throw" Exceptions auslösen.

Wir können auch eigene Exception-Klassen erstellen.

Der Finally Block wird immer angesprungen, ein guter Platz zum Aufräumen.

Mit checked das Overflow Problem behandeln.


Arrays
In C# wird ein Array anders deklariert als in C oder C++.
Die Größe des Arrays ist nicht Bestandteil des Arrays.

Arrays werden immer mit "new" angelegt, auch wenn man es nicht so schreibt.  
Arrays sind Objekte in .Net, sie werden auf dem Heap angelegt.
Arrays haben Methoden.

Mehrdimensionale Arrays.

Array Member enthalten selbst ein Array: Das "Jagged Array".

Der Zugriff bei Arrays erfolgt über den Index.
Nützlich: das Length Property und die GetLength Methode.   

Wie übergebe ich Arrays in Funktionen, wie greife ich darauf zu ?
Der Zugriff auf die Command Line Argumente.  


Collections
Welche gibt es ?

Die ArrayList kann in der Anzahl der enthaltenen Elemente wachsen.
im Gegensatz zum Array : Dies hat eine feste Größe. Der Zugriff  ist einfach
und intuitiv.

Wie werden Queues und Stacks verwendet ?
Queue:  für ein "First-In", "First-Out" Verhalten. Die Methode Enqueue() fügt ein Objekt 
am Ende der Queue an. Dequeue() entfernt ein Element am Anfang der Queue.

Stack : für ein "Last-In", "First-Out" Verhalten mit Push und Pop.

Hash-Tabellen werden für schnelle Lookups verwendet.
Ein Key wird in einer Hash-Funktion in eine ganze Zahl umgewandelt
diese Zahl dient als Index für eine Hash-Tabelle.
Ein Englisch - Deutsch Übersetzungsprogramm mit einer Hash-Tabelle
Was sollte man bei Hash-Tabellen beachten ?

Methoden und Parameter
In .Net werden Parameter in der Regel per Value übergeben, d.h. eine Kopie wird
übergeben, das ist die sicherste Methode der Parameterübergabe. Das passt aber 
nicht immer. Daher gibt es auch "ref" und "out" Keywords. Bei "out" muss die Funktion 
dann auch etwas zurückgeben !

Wann verwende ich was ?

In C# gibt es Methoden mit einer variablen Parameter Anzahl.

Auch rekursive Methoden werden unterstützt.

Methoden können überladen werden, sie müssen sich in der Signatur unterscheiden.
Was zählt zu Signatur ?

Grundlagen der Objekt-Orientierten Programmierung
Überbegriffe bilden, "Klassifizieren" tun wir ständig, warum nicht auch beim
Programmieren.

Die Klasse ist der Bauplan, das Programm arbeitet letztendlich mit Objekten.

Wie finde ich Klassen ? Durch Abstrahieren.
Warum ist Verkapselung so wichtig ? Wie werde ich darin von C# unterstützt ?

Code wieder verwenden durch Vererbung, sie stellt eine "Ist" - Beziehung dar.
Es gibt auch die "Hat" - Beziehung, die Aggregation 

Eine Klasse kann in .Net Code nur von einer Klasse erben.

Über eine Objekt-Liste iterieren und je nach Objekt-Typ reagieren - Der Polymorphismus, 
ein Highlight von OOP.

Abstrakte Klassen liefern Code, fordern aber auch eine Implementierung.

Klassen
Variable sollten grundsätzlich privat sein.

Für den Zugriff verwenden wir dann "public Propertys". Hierin kann man auch den Zugriff
kontrollieren, sie bieten einen intuitiven Zugriff.
Die beiden Zugriffs-Accessoren "get" und "set", wie erfolgt der Zugriff ?
Wir können auch den Schreibzugriff verbieten, das "ReadOnly" Property.

Objekte werden mit "new" auf dem Stack angelegt. Was genau liegt auf dem Heap,
und was liegt auf dem Stack ?

Das Initialisieren der Objekte erfolgt über Konstruktoren.

Damit Objekte immer initialisiert sind, liefert uns der Compiler einen 
Default-Konstruktor, dieser ist unsichtbar außer im ILDASM.
Was macht denn dieser Default-Konstruktor ? Warum ist es oft sinnvoll, den vom
Compiler generierten Default-Konstruktor  ggf. zu überschreiben ?

Objekte sollten vielfältig initialisiert werden können, die Funktionsüberladung
kommt uns hier gerade recht - wir haben ja nur einen Namen, den Klassennamen.

Mehrere Konstruktoren können redundanten Code erzeugen, Abhilfe schafft die
Initialisierungsliste, mit der wir innerhalb eines Konstruktors weitere Konstruktoren
aufrufen können.

Enthält eine Klasse einen Konstruktor mit Parametern stellt der Compiler keinen 
Default-Konstruktor zur Verfügung, wird er gebraucht, müssen wir ihn schreiben.

Konstruktoren können keinen Wert zurückgeben, sie haben keinen Return Wert,
sie können aber Exceptions werfen und die können wir abfangen.

Brauchen alle Objekte einer Klasse die gleiche Information, reicht dafür eine einzige
Variable für alle Objekte, die ist statisch, dafür gibt es auch "static" Propertys.

Vererbung
Vererbung dient zur Wiederverwendung von Code, sie drückt eine "Ist"- Beziehung aus. 

Von was kann sich eine Klasse ableiten ? In C# kann Code nur von einer 
Basisklasse abgeleitet werden, aber von mehreren Interfaces.

Auf Protected Member kann ungehindert von abgeleiteten Klassen zugegriffen werden, 
andere Klassen haben keinen Zugriff.

Jede vererbte Methoden kann in der abgeleiteten Klasse neu implementiert werden,
damit wird die Methode in der Basisklasse verdeckt, sofern die Signatur identisch ist.
Was zählt zur Signatur ? Die neue Methode sollte mit "new" dekoriert werden,
die Verdeckung findet auch ohne "new" statt.

Die abgeleitete Klasse ruft per Default den Basisklassen-Konstruktor auf, sofern wir
Default-Konstruktoren verwenden. Vorsicht bei Basisklassen, die Konstruktoren
mit Parametern enthalten. Sie müssen dann über die Konstruktoren der abgeleiteten
Klasse aufgerufen werden, auch der Aufruf von der Client Seite muss angepasst werden.
  
Der Polymorphismus fängt beim Casten an.
Cast bei Objekten, der Upcast gelingt immer. Beim Downcast muß grundsätzlich
gecastet werden, dafür gibt es auch "is" und "as"

Virtuell überschreiben : Der Polymorphismus
Die Basisklasse enthält eine Minimal Implementierung, diese kann auch 
nur aus { } bestehen. Die abgeleiteten Klassen haben die Möglichkeit eine eigene 
geeignetere Implementierung anzugeben, ist sie vorhanden, wird sie verwendet. 

Normale Funktionsaufrufe werden zur "Compile-Time" aufgelöst und im Code
eingetragen. Wird eine Methode polymorph aufgerufen über eine Basisklasse mit
virtuellen Methoden oder über ein Interface, wird der Aufruf erst zur Laufzeit ermittelt.

Möchte man abgeleitete Klassen dazu bringen, eigene Methoden zu implementieren
definiert man die Methode in der Basisklasse als abstrakt. Die abgeleitete Klasse
muss diese Methode selbst implementieren sonst können keine Instanzen
gebildet werden. Abstrakte Klassen enthalten in der Regel auch eine Implementierung,
diese wird normal vererbt.

Interfaces
Ein Interface hat eine "Feature Charakteristik", implementiert eine Klasse ein Interface
so kann es z.B. Sortieren, Enumerieren, ein Kopie von sich erstellen etc.

Ein "Hello World" Interface : Das Interface legt den Prototyp der Methoden fest,
die davon abgeleitete Klasse muss alle Methoden des Interfaces implementieren.

In .Net gibt es keine Mehrfachvererbung wie in C++, abgeleitete Klassen können
nur von einer Basis Klasse Code erben, sie können aber von beliebig vielen
Interfaces abgeleitet sein. Das funktioniert, da hier kein Code sondern nur Prototypen
von Funktionen vererbt werden, und die enthalten keinen Code.

Es sollte vor der Verwendung des Interfaces grundsätzlich geprüft werden, ob die
Klasse dieses auch implementiert hat, dafür gibt es Operatoren. 

Neben dem Polymorhismus über die Vererbung gibt es auch den Polymorhismus
über Interfaces. Dieser ist oft passender, da er unabhängig von einer
Vererbungshierarchie arbeitet: Auf diese Weise können eigene Klassen von der
Klassenbibliothek profitieren, wenn sie bestimmte Interfaces implementieren.
Implementiert z.B. die eigene Klasse die beiden Enumerations-Interfaces so kann man
eine "foreach" - Anweisung über die eigenen Klassen machen.

Die Implementierung des Interfaces kann sich auch in einer Basisklasse befinden.
Vorsicht bei Vererbung : Eine abgeleitete Klasse enthält eine Methode, die den 
gleichen Namen hat wie eine vererbte Interface Methode.
Das ist auch der Grund, warum man bei Interfaces stets über das Interface und
nicht über das Objekt auf die Interface Methoden zugreifen sollte. 

Boxing

Das Arbeiten mit Wert-Typen (wie short, int, long etc.) ist am effizientesten.

Es besteht daher oft der Wunsch mit Wert Typen zu arbeiten und sie bei Bedarf
in Objekte umzuwandeln. Dies ermöglicht die Verwendung von Methoden, die
die Basisklasse System.Object als Parameter verwenden.

Vorsicht beim Boxing in Zählschleifen ! Hier kann optimiert werden.

Die Klasse String und StringBuilder
Enthält Unicode Zeichen ( 2 Byte ). String ist ein Alias für "System.String"

Strings sind nicht veränderbar, daher "Thread-Save", Methoden, die einen String 
zurückgeben, erzeugen einen neuen String.

Für den Referenz Typ String, sie werden auf dem Heap angelegt, ist die Equals-Methode
innerhalb der String Klasse auf  Wert-Vergleich überschrieben worden.

Die Klasse StringBuilder erzeugt bei Änderungen im String nicht jedes Mal ein
neues Objekt,  sie ist gerade in Schleifen der Klasse String vorzuziehen.

System.Object
Alle Klassen in .Net sind von der Klasse "System.Object" abgeleitet.
Die Klasse "System.Object" liefert eine gewisse Grundfunktionalität, einige Methoden
sollten überschrieben werden.

ToString () liefert per Default den Namen der Klasse, sie sollte für einen
Werte-Vergleich überschrieben werden.

GetHashCode() liefert per Default eine fortlaufende Nummer und sollte eine
eindeutige Zahl  liefern.

Wenn Methoden wie Equals überschrieben werden, so müssen auch Operatoren
wie  "==" und "!=" überschrieben werden.<

Wie schaut die Implementierung einer Klasse, aus wenn diese Methoden und die
Operatoren entsprechend überschrieben werden ? 


Readonly Variable & weitere Konstruktoren
Da "Readonly" - Variable später nicht mehr geändert werden können, müssen sie
initialisiert werden, das macht man am besten im Konstruktor.

Auch Strukturen haben Konstruktoren. Es bestehen jedoch Unterschiede zu 
Klassen-Konstruktoren. Die Strukturvariablen können mit einem Konstruktor initialisiert
werden. Die Struktur wird dann wie ein Objekt mit new instantiiert, die Struktur wird
aber auf dem User Stack und nicht auf dem Heap angelegt.

Private Konstruktoren : Sie verhindern, daß Objekte angelegt werden können,
dort wo keine Objekte gebraucht werden z.B. bei Klassen, die statische Funktionen
enthalten.

Der Garbage Collector & IDisposable

In .Net entsorgt ein intelligenter Garbage - Collector den Speicher auf dem Heap.

Ist der Speicher knapp, sucht er nach Objekten, die nicht mehr referenziert werden.
Diese wandelt er wieder in Raw - Memory um und stellt zusammenhängenden
Speicher zur Verfügung.

Der Zeitpunkt der Entsorgung ist unbestimmt und erfolgt ggf. nie, sofern der
Speicher ausreicht. Was ist aber mit Resourcen wie z.B. File-Handles, die nach
Gebrauch sofort wieder freigegeben werden sollten, weil andere darauf warten ?

Man hat zwar keinen Einfluss darauf, wann ein Objekt zerstört wird, kann aber
dafür sorgen, dass vorher noch Code ausgeführt wird, bevor es entsorgt wird.

Erster Lösungsansatz: Objekte im .Net Framework haben einen virtuelle
Finalize Methode, die im eigenen Objekt überschrieben wird. In C# ist die
Methode über einen Destruktor aufrufbar.

Die bessere Lösung: Die Klasse implementiert zusätzlich das IDisposable Interface
mit seiner Dispose() Methode. In der Dispose Methode wird die Ressource freigegeben. 
Diese Methode sollte der Benutzer der Ressource aufrufen, vergißt er den Aufruf,
greift immerhin noch die überschriebene Finalize Methode.


Indexer

Ein Indexer ermöglicht einen indizierten Zugriff auf die Klasse, ähnlich dem Zugriff
auf Arrays. Die Klasse enthält dazu ein Array auf das letztendlich zugegriffen wird.

Wie werden Indexer erstellt und verwendet ?
Indexer können auch überladen werden.

Delegates und Events
Wozu eignen sich Delegates ?  Wie werden sie verwendet ? 
Wie funktionieren Delegates ?

Das Keyword "delegate" erzeugt eine Klasse.

Hello World mit Delegates.

Anonyme Aufrufe mit Delegates, Klassen arbeiten zusammen ohne sich zu kennen.

Das Eventhandling basiert auf dem "Publisher-Subscriber" -  Pattern.
Das Eventhandling verwendet Delegates.

Wie werden mehrere Eventhandler registriert ?

Delegates ermöglichen Multicasting.

Operator - Überladungen
Operatoren machen Ausdrücke verständlicher und reduzieren Schreibfehler.

Es können implizite und explizite Konvertierungs-Operatoren definiert werden.

Operatoren können mehrfach überladen werden.

Relationale Operatoren müssen paarweise überschrieben werden
<   und >, <= und >= und= = und ! =   .

Auch die Equals(..) Methode sollte überschrieben werden, sofern die
Operatoren == und ! = überschrieben werden.

Equals(..) vergleicht bei Objekten die Referenzen. 

Die GetHashCode() Methode sollte dann ebenfalls überschrieben werden :
2 Objekte mit gleichen Inhalt sollten den gleichen Hashcode erzeugen,
per Default erhalten sie aber eine fortlaufende unterschiedliche Nummer.

Geschachtelte Klassen, Internal und Factorys
Welche Vorteile bieten "Nested" (verschachtelte) Klassen ?

Nested Klassen sind per Default "private", dadurch ist der Zugriff von außen gesperrt. 
Sie können einen eigenen Zugriffs-Spezifizierer haben, somit reduziert sich die
Anzahl der Namen im Global Scope oder im umschließenden Scope.
Wie erfolgt der Zugriff ? 
  
Der projektbezogene Zugriff  “internal” erleichtert die Kommunikation von Objekten 
innerhalb eines Projekts. Alle Klassen innerhalb des Assemblys haben einen
ungehinderten Zugriff. Klassen  außerhalb des Assemblys haben keinen Zugriff.
Wie wird das am besten realisiert ?

Die direkte Erzeugung der Objekte ist oft komplex oder nicht erlaubt. Beispielsweise 
sollte ein Bankkonto nur von der Bank und nicht von jedermann angelegt werden
können. Solche Aufgaben übernehmen dann so genannte "Factories", sie sind
in den Design-Patterns aufgeführt.
Wie werden sie in C# realisiert ?


Namespaces
Namespaces lösen Namenskonflikte, sie haben einen offenen Gültigkeitsbereich.

Namespaces haben einen offenen Scope, sie können immer wieder geöffnet werden.
Der offene Namespace hat dadurch bedeutende Vorteile.
  
Klassen die zusammenarbeiten sind in einem gemeinsamen Namespace
untergebracht, können aber in separaten Files codiert werden, ansonsten
wären hier nestet Klassen erforderlich.

Module sind allein nicht lauffähig, sie können aber bei Bedarf zur Laufzeit
nachgeladen werden.

Assemblys sind ausführbar und enthalten ein Manifest, ein Inhaltsverzeichnis über
alle referenzierten Dateien und Typen.

Assemblys können aus einer oder auch aus mehreren Dateien bestehen.
Sie sind versionierbar, sofern sie einen “Strong Name” haben.

Welche Arten von Anwendungen gibt es in .NET ?


Attribute
C# ermöglicht auf eine einfache Art den Sprachumfang zu erweitern.

Über Attribute wird die Runtime aufgefordert beispielsweise Objekte in eine
Transaktion einzubinden.

Attribute sind deklarative Tags, die der Runtime Informationen übermitteln.
Sie werden mit den Metadaten der Elemente abgespeichert.

Das .NET Framework liefert eine Menge vordefinierter Attribute.
Die Runtime enthält Code, um die Werte der Attribute zu lesen und auf sie zu reagieren.

Wie werden die Attribute ausgewertet ?

Man kann auch eigene Attribute erstellen.  Wie werden sie erstellt und verwendet ?