Link auf die aktuelle Vorlesung im Versionsmanagementsystem GitHub
https://github.com/liaScript/CsharpCourse/blob/master/08_OOP_CsharpI.md
Die interaktive Form ist unter diese Link zu finden -> LiaScript Vorlesung 8
Wie weit sind wir schon gekommen?
c# Schlüsselwörter:
| abstract | as | base |bool |break |byte |
|case |catch |char |checked |class |const |
|continue |decimal | default | delegate |do |double |
|else |enum | event | explicit | extern |false |
|finally | fixed |float |for |foreach |goto |
|if | implicit | in |int | interface |internal |
| is | lock |long |namespace |new |null |
| object | operator |out | override |params |private |
| protected |public | readonly |ref |return |sbyte |
| sealed |short | sizeof | stackalloc |static |string |
|struct |switch |this |throw |true |try |
| typeof |uint |ulong |unchecked | unsafe |ushort |
|using | virtual |void | volatile |while | |
Auf die Auführung der kontextabhängigen Schlüsselwörter wie where oder
ascending wurde hier verzichtet.
1. Hier stehen jetzt Ihre Fragen ...
Motivation
- Set-/Get-Methoden kapseln die private-Felder und geben beschränkten Zugriff
- Get-Methoden können Daten on-the-fly berechnen
- Set-Methoden können Plausibilität der Daten prüfen
// Individuelle Eigenschaft
private string name;
public string Name
{ get {return name;}
set {name = value;}
}// Automatische Properties wirken direkt auf einer public Variablen
public string Name
{ get; set; }using System;
namespace Rextester
{
class Produkt
{
double NettoPreis;
public const double Mehrwertsteuer = 0.19;
public double Preis {
get {return NettoPreis * (1+Mehrwertsteuer);}
}
public Produkt(double nettoPreis){
NettoPreis = nettoPreis;
}
//Analoge Anweisung
//public Produkt(double nettoPreis) => NettoPreis = nettoPreis; //
}
public class Program
{
public static void Main(string[] args)
{
{
Produkt prod1 = new Produkt(8.00);
Console.WriteLine("Preis: " + prod1.Preis.ToString());
}
}
}
}@Rextester.eval(@CSharp)
- Properties können nicht mittels ref/out an Methoden übergeben werden
- Readonly Properties müssen einen Initialwert haben oder im Konstruktor definiert werden
- automatische Properties sind nicht immer die beste Lösung
{{0-1}}
Vererbung bildet neben Kapselung und Polymorphie die zentrale Säule des objektorientierten Programmierens. Die Vererbung ermöglicht die Erstellung neuer Klassen, die ein in exisitierenden Klassen definiertes Verhalten wieder verwenden, erweitern und ändern. [MS.NET Programmierhandbuch]
Beispiele
Die Klasse, deren Member vererbt werden, wird Basisklasse genannt, die erbende Klasse als abgeleitete Klasse bezeichnet.
| Basisklasse | abgeleitete Klassen | Gemeinsamkeiten |
|---|---|---|
| Fahrzeug | Flugzeug, Boot, Automobil | Position, Geschwindigkeit, Zulassungsnummer, Führerscheinpflicht |
| Datei | Foto, Textdokument, Datenbankauszug | Dateiname, Dateigröße, Speicherort |
| Nachricht | Email, SMS, Chatmessage | Adressat, Inhalt, Datum der Versendung |
{{1-2}}
Umsetzung in C#
using System;
using System.Reflection;
using System.ComponentModel.Design;
namespace Rextester
{
public class Person {
public int geburtsjahr;
public string name;
}
public class Fußballspieler : Person {
public byte rückennumemr;
}
public class Schiedsrichter : Person {
public bool assistent = true;
}
public class Program
{
public static void Main(string[] args){
Person Mensch = new Person {geburtsjahr = 1956, name = "Löw"};
Console.WriteLine("{0,4} - {1}", Mensch.geburtsjahr, Mensch.name );
Console.WriteLine("Felder in der Instanz '{0}' von '{1}'", Mensch.name, Mensch);
var fields = Mensch.GetType().GetFields();
foreach (FieldInfo field in fields){
Console.WriteLine(" x " + field.Name);
}
}
}
}@Rextester.eval(@CSharp)
Merke: Im Unterschied zu Klassen ist für Structs unter C# keine Vererbung möglich!
In C# kann jede Klassendefinition nur eine Basisklasse referenzieren. Im Sinne einer realitätsnahen Modellierung wären Mehrfachvererbungen aber durchaus zielführend. Ein Amphibienfahrzeug leitet sich aus den Basisklassen Wasserfahrzeug und Landfahrzeug ab, ein Touchpad integriert die Member von Eingabegerät und Ausgabegerät. C# verzichtet drauf um Mehrdeutigkeiten und Fehler ausschließen zu können, die aus gleichnamige Membern hervorgehen.
{{2-3}}
** ... und wie erfolgt die Initialisierung?**
Konstruktoren werden nicht vererbt, jedoch
- kann mit dem Schlüsselwort
baseauf die Konstruktoren der Basisklasse zurückgegriffen werden. - wird sofern aus der abgeleiteten Klasse kein expliziter Aufruf erfolgt, der Standardkonstruktor der Basisklasse aufgerufen.
Ein Beispiel für den impliziten Aufruf des Standardkonstruktors:
using System;
using System.Reflection;
using System.ComponentModel.Design;
namespace Rextester
{
public class Person {
public int geburtsjahr;
public string name;
public Person(){
geburtsjahr = 1984;
name = "Orwell";
Console.WriteLine("ctor of Person");
}
public Person(int auswahl){
if (auswahl == 1) {name = "Micky Maus";}
else {name = "Donald Duck";}
}
}
public class Fußballspieler : Person {
public byte rückennummer;
}
public class Program
{
public static void Main(string[] args){
Fußballspieler champ = new Fußballspieler();
Console.WriteLine("{0,4} - {1}", champ.geburtsjahr, champ.name );
}
}
}@Rextester.eval(@CSharp)
Wer darf auf welche Methoden, Properties, Variablen usw. zurückgreifen? Mit der Einführung der Vererbung steigt die Komplexität der Sichtbarkeitsregeln nochmals an.
| Zugriffsmodifizierer | Innerhalb eines Assemblys || Außerhalb eines Assemblys |
| | Vererbung | Instanzierung || Vererbung | Instanzierung |
| -------------------- | -------------- | -------------- || ------------- | -------------- |
| `public` | ja | ja || ja | ja |
| `private` | nein | nein || nein | nein |
| `protected` | ja | nein || ja | nein |
| `internal` | ja | ja || nein | nein |
| `ìnternal protected` | ja | ja || ja | nein |
protected definiert eine differenzierten Zugriff für geerbte und Instanz-Methoden. Während
bei geerbten Elementen uneingeschränkt zugegriffen werden kann, bleiben diese bei der
bloßenn Anwendung geschützt.
Die Konzepte von internal setzen diese Überlegung fort und kontrollieren den Zugriff über Assemblygrenzen.
: Variante I Variante II
: Übergreifendes gemeinsames Separate Assemblies via
: Assembly dll-Referenz
:
+------------------------------+ : -.
| Person | : |
+------------------------------+ : |
| ✛ Geburtsjahr : int | : | ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓
| ✛ Name : string | ---:--|-------------------------------------| Person.dll |
| - email : string | : | ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛
+------------------------------+ : | |
| ✛ BerechneAlter() | : . ╔════════════════════════╗ |
| # SendEmail() | : \ ║ Assembly - Programm ║ |
+------------------------------+ : / ╚════════════════════════╝ |
∆ : ' |
| : | |
| : | |
+------------------------------+ : | -. |
| Fußballspieler | : | | |
+------------------------------+ : | | |
| - rückennummer: int | : | | |
| # geschosseneTore : int | : | | |
+------------------------------+ : | | |
| «property» Rückennummer: int | : | | |
| - SendMessage() | : | | |
+------------------------------+ : | . |
^ : | \ ╔════════════════════════╗
┊ : | / ║ Assembly - Programm ║
┊ : | ' ╚════════════════════════╝
+------------------------------+ : | |
| Programm | : | |
+------------------------------+ : | |
| ✛ Maier: Fußballspieler | : | |
+------------------------------+ : | |
| ✛ Main() | : | |
+------------------------------+ : | |
: -' -'
using System;
namespace Rextester
{
public class Person
{
public int Geburtsjahr = 1972;
public string Name = "Lukas Podolski";
string email = "LukasPodolski@gmx.de";
public int BerechneAlter(){
return DateTime.Now.Year - this.Geburtsjahr;
}
protected void SendEmail(string text){
Console.WriteLine("MailTo - {0} - {1}", email, text);
}
}
public class Fußballspieler : Person
{
private int rückennummer;
protected int GeschosseneTore = 0;
public int Rückennummer{
set {if (value < 100) rückennummer = value;
else Console.WriteLine("Fehler, Rückennummer ungültig");}
get {return rückennummer;}
}
internal void SendMessage(){
if (this.GeschosseneTore == 0) {this.SendEmail("Wohl nicht Dein Tag?");}
else {this.SendEmail("Super gemacht!");}
}
}
public class Program
{
public static void Main(string[] args){
Fußballspieler Stürmer = new Fußballspieler();
Stürmer.Geburtsjahr = 1982;
//Stürmer.GeschosseneTore = 12; // Compilerfehler
Stürmer.SendMessage();
}
}
}@Rextester.eval(@CSharp)
{{2-3}}
Kriterien der Zugriffsattribute:
- innerhalb/außerhalb einer Klasse
- innerhalb der Vererbungshierachie einer Klasse / außerhalb ("nutzt")
- innerhalb des Assemblys / außerhalb
Für Methoden, Membervariablen etc. ist das klar, aber macht es Sinn geschützte private Konstruktoren zu definieren?
Private Konstruktoren werden verwendet, um die Instanziierung einer Klasse zu
verhindern, die ausschließlich statische Elemente hat. Ein Beispiel dafür ist
die Math Klasse, die Methoden definiert, die ohne eine Instanz der Klasse
aufgerufen werden. Wenn alle Methoden in der Klasse statisch sind, wäre es ggf.
sinnvoll die gesamte Klasse statisch anzulegen.
using System;
namespace Rextester
{
public class Counter
{
private Counter() { }
public static int currentCount;
public static int IncrementCount()
{
return ++currentCount;
}
}
public class Program
{
public static void Main(string[] args){
Counter myCounter = new Counter();
//Console.WriteLine()
}
}
}@Rextester.eval(@CSharp)
Auch für Klassen selbst können Zugriffsattribute das Verhalten bestimmen:
- Jede Klasse kann entweder als
publicoderinternaldeklariert sein (Standard:internal) - Klassen können mit
sealedversiegelt werden. Damit ist das Erben davon ausgeschlossen (Bsp.: System.String)
Merke Polymorphie bezeichnet die Tatsache, dass Klassenmember ausgehend von Ihrer Nutzung ein unterschiedliches Verhalten erzeugen.
Das heißt, die Methoden der Klassen einer Vererbungshierarchie können auf verschiedenen Ebenen gleiche Signatur, aber unterschiedliche Implementierungen haben. Welche der Methoden für ein gegebenes Objekt aufgerufen wird, wird erst zur Laufzeit bestimmt (dynamische Bindung).
Merke Dynamische Bindung bezeichnet die Tatsache, dass bei Aufruf einer überschriebenen Methode über eine Basisklassenreferenz oder ein Interface trotzdem die Implementierung der abgeleiteten Klasse zum Einsatz kommt.
Abgeleitete Klassen können aus der Basisklasse geerbte Methoden neu deklarieren. Dabei kann gewählt werden, ob die Methode verdeckt oder überschrieben werden soll. In beiden Fällen wird die ursprüngliche Methode durch eine neue ersetzt.
Dynamische Bindung erlaubt den Aufruf von überschriebenen Methoden aus der Basisklasse heraus, wobei das Überschreiben muss in der Basisklasse explizit erlaubt werden muss.
Siehe Beispielcode Polymorphie
In C# können abgeleitete Klassen Methoden mit dem gleichen Namen wie
Basisklassen-Methoden enthalten. Diese Methoden müssen dann in der Basisklasse
mittels virtual als explizit überschreibbar deklariert werden:
public virtual void makeSound() => Console.WriteLine("I'm an Animal");Zum Überschreiben wird das Schlüsselwort override genutzt, welches ein
erneutes Deklarieren ermöglicht:
public override void makeSound() => Console.WriteLine("Quack!");Dabei müssen beide Methoden die gleiche Signatur haben, d.h. sie sollen die den gleichen Namen und eine identische Parameterliste besitzen. Ansonsten ist es nur Überladung!
using System;
namespace Rextester
{
class Animal
{
public string Name;
public Animal(string name){
Name = name;
}
public virtual void makeSound(){
Console.WriteLine("I'm an Animal");
}
}
class Duck : Animal
{
public Duck(string name) : base(name) { }
public override void makeSound(){
Console.WriteLine("{0} - Quack ({1})", Name, this.GetType().Name);
}
}
class Cow : Animal
{
public Cow(string name) : base(name) { }
public override void makeSound(){
Console.WriteLine("{0} - Muh ({1})", Name, this.GetType().Name);
}
}
public class Program
{
public static void Main(string[] args){
Animal[] animals = new Animal[3];
animals[0] = new Duck("Alfred");
animals[1] = new Cow("Hilde");
animals[2] = new Animal("Bernd");
foreach (Animal anim in animals)
anim.makeSound();
}
}
}@Rextester.eval(@CSharp)
Die verschiedenen Tierklassen werden auf ihre Basisklasse gecastet, trotzdem aber die individuelle Implementierung von Sound ausgeführt. Damit erlaubt die Polymorphie ein gleichartiges Handling unterschiedlicher Klassen, die über die Vererbung miteinander verknüpft sind.
Interessant ist die Möglichkeit die ursprüngliche Implementierung der Methode aus der Basisklasse weiterhin zu nutzt und zu erweitern:
class Horse : Animal
{
public Horse(string name) : base(name) { }
public override void makeSound()
{
base.Speak();
Console.WriteLine("Ich ziehe Kutschen");
}
}
Dazu kann die Methode aus der Basisklasse über base.<Methodenname> aufgerufen
werden
Sollen die spezifischen Methoden aber nur im Kontext der Klasse realisierbar
sein, so werden sie vor der Basisklasse "verdeckt". Dazu ist das Schlüsselwort
new erforderlich. In diesem Fall wird keine dynamische Bindung realisiert,
sondern die Methode der Basisklasse aufgerufen.
using System;
namespace Rextester
{
class Animal
{
public string Name;
public Animal(string name){
Name = name;
}
public virtual void makeSound(){
Console.WriteLine("I'm an Animal");
}
}
class Cat : Animal
{
public Cat(string name) : base(name) { }
public new void makeSound(){
Console.WriteLine("{0} - Miau ({1})", Name, this.GetType().Name);
}
}
public class Program
{
public static void Main(string[] args){
Cat myCat = new Cat("Kity");
myCat.makeSound();
Animal myCatAsAnimal = new Cat("KatziTatzi");
myCatAsAnimal.makeSound();
}
}
}@Rextester.eval(@CSharp)
Verdeckt werden können alle Klassenmember einer Basisklasse:
- Felder
- Properties und Indexer
- Methoden usw.
Wenn kein Schlüsselwort angegeben ist, wird implizit new angenommen. Allerdings
ist das explizite Verdecken hat nur äußerst selten eine sinnvolle Anwendung.
Das folgende Beispiel entstammt dem C# Programmierhandbuch und kann unter Link nachgelesen werden.
Nehmen wir an, dass Ihre Software eine Grafikbibliothek nutzt, die folgende Funktionen bietet:
class GraphicsClass
{
public virtual void DrawLine() { }
public virtual void DrawPoint() { }
}Sie haben darauf aufbauend eine umfangreiches Framework geschieben und in einer
Klasse, die von GraphicsClass erbt eine Methode DrawRectangle implementiert.
class YourDerivedGraphicsClass : GraphicsClass
{
public void DrawRectangle() { }
}Nun eintwickelt der Hersteller eine neue Version von GraphicsClass und
integriert eine eigene Realisierung von DrawRectangle. Sobald Sie Ihre
Anwendung neu gegen die Bibliothek kompilieren, erhalten Sie vom Compiler eine
Warnung. Diese Warnung informiert Sie darüber, dass Sie das gewünschte Verhalten
der DrawRectangle-Methode in Ihrer Anwendung bestimmen müssen. Welche
Möglichkeiten haben Sie - override oder new oder umbenennen? Welche Konsequenzen
ergeben sich daraus?
Die Mechanismen der Vererbung und Polymorphie können aber auch aufgehoben werden,
wenn ein Schutz notwendig ist. Das Schlüsselwort sealed ermöglicht es sowohl
Klassen von der Rolle als Basisklasse auszuschließen als auch das Überschreiben
von Methoden zu verhindern.
class A {}
sealed class B : A {}Im Beispiel erbt die Klasse B von der Klasse A, allerdings kann keine Klasse von der Klasse B erben.
Da Strukturen implizit versiegelt sind, können sie nicht geerbt werden.
using System;
namespace Rextester
{
sealed public class Animal
{
public string Name;
public Animal(string name){
Name = name;
}
public virtual void makeSound(){
Console.WriteLine("I'm a Crocodile");
}
}
class Cat : Animal
{
public Cat(string name) : base(name) { }
public sealed override void makeSound(){ // sealed schützt die Cat.makeSound methode
Console.WriteLine("{0} - Miau ({1})", Name, this.GetType().Name);
}
}
class Tiger : Cat
{
public Tiger(string name) : base(name) { }
public override void makeSound(){
Console.WriteLine("{0} - Grrrr ({1})", Name, this.GetType().Name);
}
}
public class Program
{
public static void Main(string[] args){
Tiger evilTiger = new Tiger("Shir Khan");
evilTiger.makeSound();
}
}
}@Rextester.eval(@CSharp)
Mit virtual werden einzelne Methoden spezifiziert, die durch die abgeleiteten
Klassen implmentiert werden. Die Basisklasse hält aber eine "default" Implementierung
bereit. Letztendich kann man diesen Gedanken konsequent weiter treiben und die
Methoden der Basisklasse auf ein reines Muster reduzieren, dass keine eigenen Implementierungen
umfasst.
Diese Aufgabe übernehmen abstrakte Klassen und abstrakte Methoden. Eine abstrakte Klasse:
- kann nicht instanziiert werden
- kann abstrakte Methoden umfassen
- ist oft als Startpunkt(e) einer Vererbungshierarchie gedacht sind.
Innerhalb der Klasse können abstrakte Methoden integriert werden, die
- implizit als virtuelle Methode implementiert angelegt werden
- entsprechend keinen Methodenkörper umfassen
Eine nicht abstrakte Klasse, die von einer abstrakten Klasse abgeleitet wurde, muss Implementierungen aller geerbten abstrakten Methoden und Accessoren enthalten.
using System;
namespace Rextester
{
public abstract class Animal
{
public string Name;
public Animal(string name){
Name = name;
}
// public virtual void makeSound(){
// Console.WriteLine("I'm an Animal");
// }
public abstract void makeSound();
}
public class Corcodile : Animal{
public Corcodile(string name) : base(name){
Name = name;
}
public override void makeSound(){
Console.WriteLine("I'm a Crocodile");
}
}
public class Program
{
public static void Main(string[] args){
Corcodile A = new Corcodile("Tuffy");
A.makeSound();
}
}
}@Rextester.eval(@CSharp)
Warum macht es keinen Sinn eine abstrakte Klasse als sealed zu deklarieren?
Das Beispiel der Woche findet sich unter folgendem Link
https://github.com/liaScript/CsharpCourse/tree/master/code/08_OOP_CsharpII/AbstractClasses
Referenzen
[WikiInheri] Wikipedia, "Vererbung", Autor "cactus26", Link
Autoren
Sebastian Zug, André Dietrich
