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 ?