Kapitel 6 101 -Klassen
In einer objektorientierten Sprache sind Klassen
das Herz jeder Anwendung. Dieses Kapitel ist in verschiedene Abschnitte
gegliedert. Der erste Abschnitt beschreibt die Elemente von C#, die
häufig eingesetzt werden, die folgenden Abschnitte behandeln seltener
verwendete Bestandteile, je nach dem, welche Art von Code geschrieben
wird.
6.1 Eine einfache Klasse
 
Eine C#-Klasse kann sehr einfach sein:
class VerySimple
{
int simpleValue = 0;
}
class Test
{
public static void Main()
{
VerySimple vs = new VerySimple();
}
}
Diese Klasse stellt einen Container für einen
einzigen integer-Wert dar. Da der integer-Wert deklariert
wird, ohne Informationen für den Zugriff bereitzustellen, handelt
es sich um einen privaten Wert der VerySimple-Klasse, der außerhalb der Klasse nicht referenziert
werden kann. Mit dem private-Modifikator könnte dies
explizit ausgedrückt werden.
Der integer-Wert simpleValue
ist ein Mitglied der Klasse; es können verschiedene Mitgliedstypen
vorliegen.
In der Main()-Funktion erstellt das System die Instanz im Heapspeicher,
ersetzt alle Datenmitglieder der Klasse durch Nullen und gibt einen
Verweis auf die Instanz zurück. Ein Verweis ist eine einfache Methode,
sich auf eine Instanz zu beziehen1 .
Es muss nicht angegeben werden, dass eine Instanz
nicht länger benötigt wird. Im vorangegangenen Beispiel ist
der Verweis auf die Instanz nach Beenden der Funktion Main()
nicht mehr vorhanden. Wenn der Verweis nicht an anderer Stelle gespeichert wurde, kann die Instanz von der Speicherbereinigung zurückgefordert werden. Durch die Speicherbereinigung
wird der zugewiesene Speicher ggf. ebenso zurückgefordert2 .
Dies alles ist sehr schön, aber die Klasse
vollbringt nichts Nützliches, da der integer-Wert
nicht zugänglich ist. Hier ein etwas sinnvolleres Beispiel3 :
using System;
class Point
{
// Erstellungsroutine
public Point(int x, int y)
{
this.x = x;
this.y = y;
}
// Mitgliedsfelder
public int x;
public int y;
}
class Test
{
public static void Main()
{
Point myPoint = new Point(10, 15);
Console.WriteLine("myPoint.x {0}", myPoint.x);
Console.WriteLine("myPoint.y {0}", myPoint.y);
}
}
In diesem Beispiel liegt die Klasse Point vor, die zwei integer-Werte
in der Klasse x und y aufweist. Diese Mitglieder
sind als öffentlich deklariert, d. h., die zugehörigen
Werte sind allen Codeabschnitten zugänglich, die die Klasse verwenden.
Zusätzlich zu den Datenmitgliedern ist eine Erstellungsroutine
für diejenige Klasse vorhanden, bei der es sich um eine besondere
Funktion handelt, welche aufgerufen wird, um eine Instanz der Klasse
zu erstellen. Die Erstellungsroutine erfordert zwei integer-Parameter.
In dieser Erstellungsroutine wird die besondere
Variable this verwendet; diese this-Variable
steht allen Mitgliedsfunktionen zur Verfügung und bezieht sich
immer auf die aktuelle Instanz. In Mitgliedsfunktionen sucht der Compiler zunächst nach lokalen Variablen
und Parametern für einen Namen, bevor nach Instanzmitgliedern gesucht wird. Bei der Referenzierung einer Instanzvariablen, für die ein gleichnamiger Parameter vorliegt,
muss die Syntax this.<Name> verwendet werden.
In dieser Erstellungsroutine verweist x
selbst auf den Parameter x, und this.x verweist
auf das integer-Mitglied namens x.
Neben der Point-Klasse ist eine Test-Klasse vorhanden, die
eine Main-Funktion zum Starten des Programms enthält.
Die Main-Funktion erstellt eine Instanz der Point-Klasse,
wodurch Speicher für das Objekt zugewiesen und die Erstellungsroutine
für die Klasse aufgerufen wird. Die Erstellungsroutine setzt die Werte für x und y.
In den verbleibenden Zeilen von Main()
werden die Werte für x und y ausgedruckt.
6.2 Mitgliedsfunktionen
 
Die Erstellungsroutine im vorherigen Codeabschnitt
ist ein Beispiel für eine Mitgliedsfunktion, also ein Codeabschnitt,
der für eine Instanz des Objekts aufgerufen wird. Erstellungsroutinen
können automatisch aufgerufen werden, wenn mit Hilfe von new
eine Objektinstanz erstellt wird.
Andere Mitgliedsfunktionen können folgendermaßen
deklariert werden:
using System;
class Point
{
public Point(int x, int y)
{
this.x = x;
this.y = y;
}
// Zugriffsfunktionen
public int GetX() {return(x);}
public int GetY() {return(y);}
// Variablen jetzt privat
int x;
int y;
}
class Test
{
public static void Main()
{
Point myPoint = new Point(10, 15);
Console.WriteLine("myPoint.X {0}", myPoint.GetX());
Console.WriteLine("myPoint.Y {0}", myPoint.GetY());
}
}
In diesem Beispiel wird direkt auf die Datenfelder
zugegriffen. Dies ist üblicherweise nicht empfehlenswert, da der
Benutzer der Klasse so von den Feldnamen abhängt und dadurch spätere
Änderungen erschwert werden.
In C# werden Mitgliedsfunktionen nicht so entworfen,
dass sie auf private Werte zugreifen. Stattdessen wird
eine Eigenschaft verwendet, die die Vorteile einer Mitgliedsfunktion
bietet und gleichzeitig das Benutzermodell für ein Feld wahrt.
Weitere Informationen hierzu finden Sie in Kapitel 18, Eigenschaften.
6.3 Ref- und Out-Parameter
 
Das Aufrufen zweier Mitgliedsfunktionen zum Erhalt der Werte ist nicht immer praktikabel,
es wäre eleganter, beide Werte mit nur einem einzigen Funktionsaufruf abzurufen. Es gibt jedoch nur einen Rückgabewert.
Die Lösung besteht darin, Verweisparameter zu verwenden (ref-Parameter), damit
die an die Mitgliedsfunktion übergebenen Parameterwerte geändert
werden können:
// Fehler
using System;
class Point
{
public Point(int x, int y)
{
this.x = x;
this.y = y;
}
// Beide Werte mit einem Funktionsaufruf abrufen
public void GetPoint(ref int x, ref int y)
{
x = this.x;
y = this.y;
}
int x;
int y;
}
class Test
{
public static void Main()
{
Point myPoint = new Point(10, 15);
int x;
int y;
// unzulässig
myPoint.GetPoint(ref x, ref y);
Console.WriteLine("myPoint({0}, {1})", x, y);
}
}
In diesem Codeabschnitt wurden die Parameter ebenso
wie der Funktionsaufruf mit Hilfe des ref-Schlüsselwortes
deklariert.
Dieser Code sollte funktionieren, bei der Kompilierung
wird jedoch eine Fehlermeldung erzeugt, nach der für die ref-Parameter
x und y nicht initialisierte Werte verwendet
wurden. Dies bedeutet, dass Variablen an die Funktion übergeben
wurden, bevor deren Werte gesetzt wurden. Der Compiler lässt das
Offenlegen von Werten nicht initialisierter Variablen jedoch nicht zu.
Es gibt zwei Möglichkeiten, diesen Fehler zu
umgehen. Die erste Möglichkeit besteht darin, die Variablen nach
deren Deklaration zu initialisieren.
using System;
class Point
{
public Point(int x, int y)
{
this.x = x;
this.y = y;
}
public void GetPoint(ref int x, ref int y)
{
x = this.x;
y = this.y;
}
int x;
int y;
}
class Test
{
public static void Main()
{
Point myPoint = new Point(10, 15);
int x = 0;
int y = 0;
myPoint.GetPoint(ref x, ref y);
Console.WriteLine("myPoint({0}, {1})", x, y);
}
}
Der Code kann nun kompiliert werden, die Variablen
wurden jedoch lediglich mit dem Wert 0 initialisiert, um beim Aufruf
von GetPoint() überschrieben zu werden. In C# gibt
es eine weitere Option: Sie können die Definition der Funktion
GetPoint() so abändern, dass statt eines ref-Parameters
der Parameter out verwendet wird.
using System;
class Point
{
public Point(int x, int y)
{
this.x = x;
this.y = y;
}
public void GetPoint(out int x, out int y)
{
x = this.x;
y = this.y;
}
int x;
int y;
}
class Test
{
public static void Main()
{
Point myPoint = new Point(10, 15);
int x;
int y;
myPoint.GetPoint(out x, out y);
Console.WriteLine("myPoint({0}, {1})", x, y);
}
}
Out-Parameter verhalten sich genau wie ref-Parameter, nur dass diesen nicht initialisierte Variablen
übergeben werden können und der Aufruf nicht mit ref,
sondern mit out erfolgt4 .
6.4 Überladung
 
Gelegentlich kann es nützlich sein, über
zwei Funktionen zu verfügen, die zwar dem gleichen Zweck dienen,
jedoch unterschiedliche Parameter verwenden. Dies trifft besonders auf
Erstellungsroutinen zu, denn eine neue Instanz kann häufig auf
verschiedene Weise erstellt werden.
class Point
{
// Neuen Punkt aus den x- und y-Werten erstellen
public Point(int x, int y)
{
this.x = x;
this.y = y;
}
// Punkt aus einem vorhandenen Punkt erstellen
public Point(Point p)
{
this.x = p.x;
this.y = p.y;
}
int x;
int y;
}
class Test
{
public static void Main()
{
Point myPoint = new Point(10, 15);
Point mySecondPoint = new Point(myPoint);
}
}
Die Klasse verfügt über zwei Erstellungsroutinen. Die eine Routine kann mit x- und
y-Werten aufgerufen werden, die andere mit einem weiteren
Punkt. Die Main()-Funktion verwendet beide Erstellungsroutinen;
eine zum Erstellen einer Instanz aus einem x- und einem
y-Wert, die andere zum Erstellen einer Instanz aus einer
bereits vorhandenen Instanz.
Wird eine überladene Funktion aufgerufen, wählt
der Compiler die geeignete Funktion durch einen Abgleich der Parameter
im Aufruf mit den für die Funktion deklarierten Parametern.
1
Für diejenigen unter Ihnen, die üblicherweise
Zeiger verwenden: Ein Verweis ist ein Zeiger, der nur zugewiesen und
wieder aufgehoben werden kann.
2
Die in der .NET-Laufzeitumgebung verwendete Speicherbereinigung
wird in Kapitel 31, C# im Detail, behandelt.
3
Wenn Sie tatsächlich eine eigene point-Klasse
implementieren möchten, würden Sie wahrscheinlich eher einen
Datentyp (struct) als einen Verweistyp (class) verwenden.
4
Aus der Sicht anderer .NET-Sprachen besteht kein
Unterschied zwischen ref- und out-Parametern.
Ein C#-Programm, das diese Funktion aufruft, betrachtet die Parameter
als out-Parameter, in anderen Sprachen werden die Parameter
als ref-Parameter betrachtet.
|