Link auf die aktuelle Vorlesung im Versionsmanagementsystem GitHub
https://github.com/liaScript/CsharpCourse/blob/master/07_OOP_Csharp.md
Die interaktive Form ist unter diese Link zu finden -> LiaScript Vorlesung 7
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. Was sind Felder in der C# Welt?
Variablen beliebigen Typs, die einer Klasse unmittelbar zugeordnet sind. In Feldern werden Daten abgelegt, die klassenweit Verwendung finden. Als Felder werden nur methodenrelevante Variablen integriert.
2. Erklären Sie die Unterschied zwischen Instanzfeldern und statischen Feldern.
Instanzfelder beziehen sich auf eine individuelle Instanz der Klasse, während statische Felder über allen Instanzen wirken.
3. Was passiert, wenn für eine Klasse kein Konstruktor vorgegeben wird?
Der Kompiler erstellt automatisch einen Standardkonstruktor, sobald ein einziger Konstruktor definiert wird, entfällt dieser Schritt.
4. Welche Wirkung haben fehlende Set und Get Implmentierungen bei Properties?
ohne get ... lesegeschützt, ohne set ... schreibgeschützt. Properties können aber auch durch Zugriffsattribute feingranulare Regeln definieren.
5. Wozu verwendet man Indexer?
6. Welche Varianten stehen unter C# zur Verfügung, um konstante Werte zu definieren?
Klassen [und Strukturen] sind zwei der grundlegenden Konstrukte des allgemeinen Typsystems in .NET Framework. Bei beiden handelt es sich um eine Datenstruktur, die einen als logische Einheit zusammengehörenden Satz von Daten und Verhalten kapselt.
{{0-1}}
Entsprechend können Klassenspezifikationen folgende Elemente umfassen:
| Member / Elemente | englische Bezeichnung | Funktion |
|---|---|---|
| Felder | fields | Daten |
| Konstanten | constant | konstante Daten |
| Eigenschaften | property | Daten und Zugriffsmethoden |
| Methoden | method | Funktionen / Prozeduren |
| Konstruktoren | constructor | Instanziierung einer Klasse |
| Ereignisse | event | Informationsaustausch zwischen Klassen |
| Finalizer | finalizer | "Destruktoren" |
| Indexer | indexer | Ähnlich Eigenschaften, Adressierung über Indizes |
| Operatoren | operators | Set von '==', '+' etc. mit eigener Bedeutung |
| Geschachtelte Typen | embedded types | Integrierte Klassen oder Structs, die nur innerhalb einer Klasse/ Structs angewendet werden |
{{1-2}}
class Person{
// *************** Felder ************************************************
string name; // beachte Groß/ Kleinschreibung
public int Geburtsjahr; // der Variablennamen!
// ************** Konstruktoren ******************************************
public Person(string name, int geburtsjahr){
this.name = name;
Geburtsjahr = geburtsjahr;
}
// ************** Methoden ***********************************************
int AktuellesAlter () => DateTime.Today.Year - Geburtsjahr;
public override string ToString(){
return name + " ist " + AktuellesAlter().ToString() + "Jahre alt."
}
// ************* Operatoren **********************************************
public static bool operator< (Person person1, Person Person2){
// TODO Hausaufgabe
}
}Und wie legen wir eine Instanz an? Dazu sind mehrere Schritte notwendig:
Person p; // Generierung einer Referenzvariablen p auf dem Stack
p = new Person(); // Generierung einer Instanz im Heap
// alles zusammen
// Person p = new Person();Als Operanden erwartet der new-Operator einen Klassennamen und eine Parameterliste, die an den entsprechenden Konstruktor übergeben wird.
Vgl. obriges Beispiel mit Fehlern unter Link. Identifizieren Sie die Fehler und korrigieren Sie diese.
{{2-3}}
| Fields | Methods | |
|---|---|---|
| Statisches Attribut | static |
static |
| Zugriffsattribute | public, internal, private, protected |
public, internal, private, protected |
| Vererbungsattribut | new |
new, virtual, abstract, override, sealed |
| Unsafe Attribute | unsafe |
|
| Attribut Teilimplementierung | partitial |
|
| Unmanaged Code Attribute | unsafe extern |
|
| Read-only Attribute | readonly |
|
| Threading Attribute | volatile |
{{0-1}}
Felder sind Variablen eines beliebigen Typs, die einer Klasse unmittelbar zugeordnet sind. In Feldern werden die Daten abgelegt, die übergreifend Verwendung finden.
Der Idee der Kapselung folgend, sollten nur methodenlokal relevante Variablen auch dort deklariert werden.
Eine Klasse oder Struktur kann Instanzenfelder, statische Felder oder beides gemischt verfügen.
+-----------------+ +-----------------+ +-----------------+
| Instanz 0 | | Instanz 1 | | Instanz 2 |
+-----------------+ +-----------------+ +-----------------+
| - Intanzfeld0 | | - Intanzfeld0 | | - Intanzfeld0 |
| - Intanzfeld1 | | - Intanzfeld1 | | - Intanzfeld1 |
| | | | | |
| ..................... ✛ StatischesFeld0 .................. |
| ..................... ✛ StatischesFeld1 .................. |
| | | | | |
+-----------------+ +-----------------+ +-----------------+
| ✛ Method1() | | ✛ Method1() | | ✛ Method1() |
| ✛ Method2() | | ✛ Method2() | | ✛ Method2() |
+-----------------+ +-----------------+ +-----------------+
Instanzenfelder beziehen sich als Datensat individuell auf die "eigene" Instanz, statisches Felder gehören zur Klasse selbst und werden von allen Instanzen einer Klasse gemeinsam verwendet. "Lokale" Änderungen, werden somit übergreifend sichtbar.
using System;
using System.Collections;
using System.Collections.Generic;
namespace Rextester
{
public class Person{
string name;
int index;
public int Geburtsjahr;
public static int Count; // <- Statische Variable Count
public Person(string name, int geburtsjahr){
this.name = name;
Geburtsjahr = geburtsjahr;
index = Count;
Count = Count + 1;
}
public override string ToString(){
return name + " ist die " + index.ToString() + " von " + Count.ToString() + " Personen";
}
}
public class Program
{
public static void Main(string[] args){
Person Student1 = new Person("Mickey", 1935);
Console.WriteLine(Student1);
Person Student2 = new Person("Donald", 1927);
Console.WriteLine(Student1);
Console.WriteLine(Student2);
}
}
}@Rextester.eval(@CSharp)
{{1-2}}
Felder können mit der Deklaration oder im Konstruktor initialisiert werden.
Desweiteren kann mit readonly der Wert nach dem Ende des
Konstruktorabarbeitung geschützt werden. Eine solche Variable kann als static
deklariert werden, um zu vermeiden, dass eine entsprechende Zahl von Kopien
erstellt wird.
public class Person{
string name;
int index = 0;
readonly string Kategorie = "Student";
readonly string Hochschule;
public Person(){
//...
Hochschule = "TU Freiberg";
//...
}Konstanten sind unveränderliche Datensätze, die zur Kompilierzeit(!) bekannt
sind und sich danach nicht mehr verändern lassen. Nur die in C# integrierten
Typen - einfache Datentypen und System.Object - können als const deklariert
werden.
Varianten "konstanter" Variablen in C#
| Konstante | Readonly | Readonly statisch | |
|---|---|---|---|
| Attribute | const |
readonly |
readonly static |
| Veränderbar bis ... | Kompilierung | Ende des Konstruktors | Ende des Konstruktors |
| Statisch | Standard, ja | Nein | Ja |
| Individuelle Kopien | Ja | Ja | Nein |
| Zugriff | Klasse | Instanz | Instanz |
using System;
namespace Rextester
{
public class Person{
public readonly string name;
public Person(string name){
this.name = name;
}
}
public class Program
{
public static void Main(string[] args){
Person Student1 = new Person("Mickey");
Console.WriteLine(Student1.name);
}
}
}@Rextester.eval(@CSharp)
{{0-1}}
Beim Erzeugen einer Instanz einer class oder eines structs wird deren
Konstruktor aufgerufen. Dieser ist für die Initialisierung der Instanz auf der
Zustandsebene verantwortlich. Konstruktoren können überladen werden und
verschiedene Signaturen abbilden.
Wenn für eine Klasse kein Konstuktor vorgegeben wird, erstellt der Kompiler standardmäßig einen, der das Objekt instanziiert und Membervariablen auf die Standardwerte festlegt.
public class Wine
{
public decimal Price;
public int Year;
// public Wine() // <- Implzit vorhanden, kann aber überschrieben werden
// Standardkonstruktor
public Wine (decimal price){Price = price;}
public Wine (decimal price, int year) : this (price) {Year = year;}
}Der Standardkonstruktor wird implizit generiert, wenn kein anderer Konstruktor durch den Entwickler spezifiziert wurde. Sofern das geschieht, steht dieser auch nicht mehr bereit.
{{1-2}}
Ein Konstruktor kann einen anderen Konstruktor der gleichen Klasse über das
Schlüsselwort this aufrufen. Dabei kann der Aufruf mit oder ohne Parameter
erfolgen.
using System;
namespace Rextester
{
class Car
{
public readonly int NumberOfSeats;
public readonly int MaxSpeed;
private int CurrentSpeed;
public Car(int maxSpeed, int numberOfSeats)
{
Console.WriteLine("2 arg ctor");
this.MaxSpeed = maxSpeed;
this.NumberOfSeats = numberOfSeats;
}
public Car(int maxSpeed) : this(maxSpeed, 5)
{
Console.WriteLine("1 arg ctor");
}
public Car() : this(100)
{
Console.WriteLine("0 arg ctor");
}
}
public class Program
{
public static void Main(string[] args){
Car myVehicle = new Car(5);
}
}
}@Rextester.eval(@CSharp)
{{2-3}}
Statische Konstruktoren
-
... können nicht über Zugriffsmodifizierer oder Parameter verfügen.
-
... werden automatisch vor dem Erzeugen der ersten Instanz ausgeführt und können nicht direkt aufgerufen werden. Damit hat der Nutzer keine Kontrolle, wann der Konstruktor ausgeführt wird.
-
... werden kein zweites mal aufgerufen, wenn eine Ausnahme ausgelöst wird.
using System;
namespace Rextester
{
public class BAFStudent
{
public static string Universität;
public string NameStudent;
static BAFStudent(){
Console.WriteLine("Universität wird initialisiert");
Universität = "TU BAF Freiberg";
}
public BAFStudent(string name){
Console.WriteLine("Name wird initialisiert");
NameStudent = name;
}
}
public class Program
{
public static void Main(string[] args){
BAFStudent student0 = new BAFStudent("Humboldt");
Console.WriteLine("{0,20} - {1,-10}", BAFStudent.Universität, student0.NameStudent);
BAFStudent student1 = new BAFStudent("Winkler");
Console.WriteLine("{0,20} - {1,-10}", BAFStudent.Universität, student1.NameStudent);
}
}
}@Rextester.eval(@CSharp)
{{3-4}}
Für die Objektinitialisierung besteht neben den Konstruktoren und dem unmittelbaren Zugriff auf die Membervariablen (vermeiden!) die Möglichkeit direkt nach dem Konstruktoraufruf die Belegung abzubilden.
using System;
namespace Rextester
{
public class Wine
{
public decimal Price;
public int Year;
public string Vinyard;
public Wine () {}
public Wine (decimal price){Price = price;}
public Wine (decimal price, int year, string vinyard = "Chateau Lafite" ){
Price = price;
Year = year;
Vinyard = vinyard;
}
public override string ToString()
{
return String.Format("| {0,5} Euro | {1,5} | {2,-18}|", Price, Year, Vinyard );
}
}
public class Program
{
public static void Main(string[] args){
// Initalisierung über Standardkonstruktor und direkten Feldzugriff
Wine bottle0 = new Wine();
bottle0.Vinyard = "Chateau Latour";
Console.WriteLine(bottle0);
// Initialisierung über die Konstruktoren
Wine bottle1 = new Wine(23);
Console.WriteLine(bottle1);
Wine bottle2 = new Wine(3432, 1956);
Console.WriteLine(bottle2);
// Initialisierung über Initalizer
Wine bottle3 = new Wine() {Price = 19, Year = 1910};
Console.WriteLine(bottle3);
}
}
}@Rextester.eval(@CSharp)
3 Varianten, und was ist nun besser? Der Aufruf über den Konstruktor ermöglicht
die Initialisierung von readonly Variablen.
Initalizer werden als atomare Funktion realisiert, sind damit Thread-sicher,
sind damit aber auch schwieriger zu debuggen. Zudem können nur public
Member damit adressiert werden. An dieser Stelle wird deutlich, dass
Initializier ggf. beim schnellen Testen Tipparbeit sparen, in realen Anwendungen
aber nicht zum Einsatz kommen sollten.
Mit Finalizern (die auch als Destruktoren bezeichnet werden) werden alle erforderlichen endgültigen Bereinigungen durchgeführt, wenn eine Klasseninstanz vom Garbage Collector gesammelt wird.
using System;
namespace Rextester
{
public class Person{
public string name;
public Person(string name){this.name = name;}
~Person() {Console.WriteLine("The {0} destructor is executing.", ToString());}
}
public class Program
{
public static void Main(string[] args){
Person Student1 = new Person("Mickey");
Console.WriteLine(Student1.name);
Console.WriteLine("Aus die Maus!");
}
}
}@Rextester.eval(@CSharp)
Der Finalizer ruft implizit die Methode Finalize aus der Basisklasse des Typs
Objekt auf.
Eigenschaft (Properties) organisieren den Zugriff auf private Felder über einen flexiblen Mechanismus zum Lesen, Schreiben oder Berechnen des Wertes. Entsprechend können Eigenschaften können wie öffentliche Datenmember verwendet werden. Damit wird das Konzept der Kapselung auf effiziente Zugriffsmethoden abgebildet.
Ausgangspunkt:
using System;
namespace Rextester
{
public class Vorlesung{
private byte wochentag;
}
public class Program
{
public static void Main(string[] args){
Vorlesung SoWi = new Vorlesung();
SoWi.wochentag = 4;
}
}
}@Rextester.eval(@CSharp)
C# hält, wie andere OOP Sprachen auch dafür eine eigene kompakte Syntax bereit, die Aspekte der Felder und der Methoden kombiniert. Der aufrufende Nutzer sieht eine Feld, der Zugriff kann aber über eine Methode konfiguriert werden. Dabei können durchaus mehrere Eigenschaften auf eine private Variable verweisen.
Für den Benutzer eines Objekts erscheint eine Eigenschaft wie ein Feld; der Zugriff auf die Eigenschaft erfordert dieselbe Syntax.
using System;
namespace Rextester
{
public class Vorlesung
{
private byte wochentag; // Private Variable
public byte Wochentag // Öffentliche Variable
{
get { return wochentag; } // Property accessors
set {
if ((value < 7) & (value >= 0))
wochentag = value;
else
Console.WriteLine("Fehlerhafte Eingabe!");
}
}
}
public class Program
{
public static void Main(string[] args)
{
Vorlesung SoWi = new Vorlesung();
SoWi.Wochentag = 4;
Console.WriteLine(SoWi.Wochentag);
}
}
}@Rextester.eval(@CSharp)
Die Assessoren können beliebig kombiniert werden. Eine Eigenschaft ohne einen
set-Accessor ist schreibgeschützt. Eine Eigenschaft ohne einen get-Accessor
ist lesegeschützt.
Zudem lassen sich mit der Fat Arrow Notation die Darstellungen wiederum verkürzen. Beispielhaft ist an folgendem Beispiel auch, dass sich die Properties eine vollkommen andere Informationsstruktur bedienen als die eigentlichen privaten Variablen abbilden (= Kapselung).
decimal currentPrice, sharesOwned;
public decimal Worth
{
get => currentPrice * SharesOwned;
set => sharesOwned = value / currentPrice
}
// Kompakt für
public decimal Worth
{
get { return currentPrice * SharesOwned; }
set { sharesOwned = value / currentPrice; }
}set verwendet dabei einen impliziten Parameter mit dem Namen value, dessen Typ der Typ der Eigenschaft ist.
Und wie sieht es mit dem Zugriffsschutz der Eigenschaften aus? Insbesondere
set sollte soweit wie möglich eingeschränkt werden. Dafür können internal,
private und protected genutzt werden.
Wenn in den Eigenschaftenzugriffsmethoden keine zusätzliche Logik erforderlich ist, bietet sich die Verwendung von automatisch implementierten Eigenschaften.
public int CustomerID { get; set; }In diesem Fall erstellt der Compiler ein privates, anonymes, dahinter liegendes Feld, auf das nur über get und set-Accessoren zugegriffen werden kann.
Indexer bilden die Zugriffsmethodik für Arrays MyArray[3] auf Klassen ab, um
den Zugriff auf Arrays, Listen oder andere Container zu kapseln. Dabei wird
folgende Notation benutzt:
string [] words = "Das ist ein beispielhafter Text".Split();
// Typ der Rückgabevariablen
// | this Referenz auf das eigene Objekt
// | | Typ der Indexvariable
// | | | Bezeichner der Variable
// v v v v
public string this [int index]{
get {return words[index]; }
set {words[index] = value; }
}Auch hier wird das Schlüsselwort value verwendet, um den Wert zu definieren, der zugewiesen wird.
Indexer müssen nicht durch einen Ganzzahlwert indiziert werden, es können auch andere Typen verwendet werden.
using System;
namespace Rextester
{
public class Months
{
string[] months = {"Jan", "Feb", "März", "April", "Mai", "Juni", "Juli",
"Aug", "Sep", "Okt", "Nov", "Dez"};
public string this[byte index]{
get {return months[index];}
}
}
public class Program
{
public static void Main(string[] args)
{
Months MonthList = new Months();
Console.WriteLine(MonthList[5]);
}
}
}@Rextester.eval(@CSharp)
Was ist der Vorteil der Klasse + Indexer Lösung? Wie würden Sie die Indizierung noch absichern?
Eine statische Klasse kann im Unterschied zu einer nicht statischen Klasse nicht instanziiert werden. Der Zugriff erfolgt immer über den Klassennamen.
Eine statische Klasse:
- enthält nur statische Member
- kann nicht instanziiert werden
- ist versiegelt
- darf keine Instanzkonstruktoren enthalten
using System;
namespace Rextester
{
public class Program
{
public static void Main(string[] args)
{
double number = -3.14;
Console.WriteLine(Math.Abs(number));
Console.WriteLine(Math.Floor(number));
Console.WriteLine(Math.Round(Math.Abs(number)));
//var a = new Math();
}
}
}@Rextester.eval(@CSharp)
Entwickeln Sie eine Klassenstruktur für die Speicherung der Daten eines Studenten.
using System;
using System.Collections.Generic;
namespace Rextester
{
public class Student
{
private static int globalerZähler;
private readonly int uid;
public string Name { get; set; }
private bool eingeschrieben;
private List<string> fächer;
static Student(){
globalerZähler = 0;
}
public Student(string name)
{
Name = name;
Eingeschrieben = true;
uid = globalerZähler;
fächer = new List<string>();
Console.WriteLine("Der Student {0} (Nr. {1}) ist angelegt!", Name, uid);
}
public bool Eingeschrieben
{
get {return eingeschrieben;}
set {
if (eingeschrieben != value)
eingeschrieben = value;
else
{
if (value) Console.WriteLine("!Student {0} ist schon eingeschrieben!", Name);
else Console.WriteLine("!Student {0} ist schon exmatrikuliert!", Name);
}
}
}
public void addTopic(string Fächername){
fächer.Add(Fächername);
}
public void printTopics(){
Console.WriteLine("Student {0} hat folgende Fächer absolviert:", Name);
foreach (string topic in fächer){
Console.Write(topic + " ");
}
Console.WriteLine();
}
}
public class Program
{
public static void Main(string[] args){
Student student0 = new Student("Humboldt");
student0.addTopic("Softwareentwicklung");
student0.addTopic("Höhere Mathematik I");
student0.addTopic("Prozedurale Programmierung");
student0.printTopics();
student0.Eingeschrieben = true;
}
}
}@Rextester.eval(@CSharp)
Referenzen
Autoren
Sebastian Zug, André Dietrich, Hernan Valdes, Christian Bräunlich