Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

Inhaltsverzeichnis
Vorwort zur 6. Auflage
1 Allgemeine Einführung in .NET
2 Grundlagen der Sprache C#
3 Das Klassendesign
4 Vererbung, Polymorphie und Interfaces
5 Delegates und Ereignisse
6 Strukturen und Enumerationen
7 Fehlerbehandlung und Debugging
8 Auflistungsklassen (Collections)
9 Generics – Generische Datentypen
10 Weitere C#-Sprachfeatures
11 LINQ
12 Arbeiten mit Dateien und Streams
13 Binäre Serialisierung
14 XML
15 Multithreading und die Task Parallel Library (TPL)
16 Einige wichtige .NET-Klassen
17 Projektmanagement und Visual Studio 2012
18 Einführung in die WPF und XAML
19 WPF-Layout-Container
20 Fenster in der WPF
21 WPF-Steuerelemente
22 Elementbindungen
23 Konzepte von WPF
24 Datenbindung
25 Weitere Möglichkeiten der Datenbindung
26 Dependency Properties
27 Ereignisse in der WPF
28 WPF-Commands
29 Benutzerdefinierte Controls
30 2D-Grafik
31 ADO.NET – Verbindungsorientierte Objekte
32 ADO.NET – Das Command-Objekt
33 ADO.NET – Der SqlDataAdapter
34 ADO.NET – Daten im lokalen Speicher
35 ADO.NET – Aktualisieren der Datenbank
36 Stark typisierte DataSets
37 Einführung in das ADO.NET Entity Framework
38 Datenabfragen des Entity Data Models (EDM)
39 Entitätsaktualisierung und Zustandsverwaltung
40 Konflikte behandeln
41 Plain Old CLR Objects (POCOs)
Stichwort

Download:
- Beispiele, ca. 62,4 MB

Buch bestellen
Ihre Meinung?

Spacer
Visual C# 2012 von Andreas Kühnel
Das umfassende Handbuch
Buch: Visual C# 2012

Visual C# 2012
Rheinwerk Computing
1402 S., 6., aktualisierte und erweiterte Auflage 2013, geb., mit DVD
49,90 Euro, ISBN 978-3-8362-1997-6
Pfeil 3 Das Klassendesign
Pfeil 3.1 Einführung in die Objektorientierung
Pfeil 3.2 Die Klassendefinition
Pfeil 3.2.1 Klassen im Visual Studio anlegen
Pfeil 3.2.2 Das Projekt »GeometricObjectsSolution«
Pfeil 3.2.3 Die Deklaration von Objektvariablen
Pfeil 3.2.4 Zugriffsmodifizierer einer Klasse
Pfeil 3.2.5 Splitten einer Klassendefinition mit »partial«
Pfeil 3.2.6 Arbeiten mit Objektreferenzen
Pfeil 3.3 Referenz- und Wertetypen
Pfeil 3.3.1 Werte- und Referenztypen nutzen
Pfeil 3.4 Die Eigenschaften eines Objekts
Pfeil 3.4.1 Öffentliche Felder
Pfeil 3.4.2 Datenkapselung mit Eigenschaftsmethoden sicherstellen
Pfeil 3.4.3 Die Ergänzung der Klasse »Circle«
Pfeil 3.4.4 Lese- und schreibgeschützte Eigenschaften
Pfeil 3.4.5 Sichtbarkeit der Accessoren »get« und »set«
Pfeil 3.4.6 Unterstützung von Visual Studio 2012
Pfeil 3.4.7 Automatisch implementierte Eigenschaften
Pfeil 3.5 Methoden eines Objekts
Pfeil 3.5.1 Methoden mit Rückgabewert
Pfeil 3.5.2 Methoden ohne Rückgabewert
Pfeil 3.5.3 Methoden mit Parameterliste
Pfeil 3.5.4 Methodenüberladung
Pfeil 3.5.5 Variablen innerhalb einer Methode (lokale Variablen)
Pfeil 3.5.6 Referenz- und Wertparameter
Pfeil 3.5.7 Besondere Aspekte einer Parameterliste
Pfeil 3.5.8 Zugriff auf private Daten
Pfeil 3.5.9 Die Trennung von Daten und Code
Pfeil 3.5.10 Namenskonflikte mit »this« lösen
Pfeil 3.5.11 Methode oder Eigenschaft?
Pfeil 3.5.12 Umbenennen von Methoden und Eigenschaften
Pfeil 3.6 Konstruktoren
Pfeil 3.6.1 Konstruktoren bereitstellen
Pfeil 3.6.2 Die Konstruktoraufrufe
Pfeil 3.6.3 Definition von Konstruktoren
Pfeil 3.6.4 »public«- und »internal«-Konstruktoren
Pfeil 3.6.5 »private«-Konstruktoren
Pfeil 3.6.6 Konstruktorenaufrufe umleiten
Pfeil 3.6.7 Vereinfachte Objektinitialisierung
Pfeil 3.7 Der Destruktor
Pfeil 3.8 Konstanten in einer Klasse
Pfeil 3.8.1 Konstanten mit dem Schlüsselwort »const«
Pfeil 3.8.2 Schreibgeschützte Felder mit »readonly«
Pfeil 3.9 Statische Klassenkomponenten
Pfeil 3.9.1 Statische Eigenschaften
Pfeil 3.9.2 Statische Methoden
Pfeil 3.9.3 Statische Klasseninitialisierer
Pfeil 3.9.4 Statische Klassen
Pfeil 3.9.5 Statische Klasse oder Singleton-Pattern?
Pfeil 3.10 Namensräume (Namespaces)
Pfeil 3.10.1 Zugriff auf Namespaces
Pfeil 3.10.2 Die »using«-Direktive
Pfeil 3.10.3 Globaler Namespace
Pfeil 3.10.4 Vermeiden von Mehrdeutigkeiten
Pfeil 3.10.5 Namespaces festlegen
Pfeil 3.10.6 Der »::«-Operator
Pfeil 3.10.7 Unterstützung von Visual Studio 2012 bei den Namespaces
Pfeil 3.11 Stand der Klasse »Circle«

Galileo Computing - Zum Seitenanfang

3.5 Methoden eines ObjektsZur nächsten Überschrift

In der Objektorientierung werden Klassendefinitionen dazu benutzt, einen logischen Zusammenhang zwischen Daten und Verhaltensweisen zu beschreiben. Wie Daten innerhalb einer Klasse zu behandeln sind, hat der letzte Abschnitt gezeigt. Nun wenden wir uns den Verhaltensweisen zu, die nichts anderes sind als Prozeduren bzw. Funktionen, die in der Objektorientierung als Methoden bezeichnet werden.

Dabei gilt es, Methoden in zwei Gruppen zu unterteilen:

  • Methoden mit Rückgabewert
  • Methoden ohne Rückgabewert

Galileo Computing - Zum Seitenanfang

3.5.1 Methoden mit RückgabewertZur nächsten ÜberschriftZur vorigen Überschrift

Sehen wir uns zunächst die allgemeine Syntax einer Methode mit Rückgabewert an:

[Modifizierer] Typ Bezeichner([Parameterliste])
{
[...]
return Wert
}

Einer Methode können Argumente übergeben werden, die von den Parametern in Empfang genommen werden. Parameter dienen dazu, die Anweisungen in der Methode mit Werten zu »füttern«, um auf diese Weise Einfluss auf das Verhalten auszuüben und den Ablauf zu steuern. Da die Parameterliste optional ist, gibt es auch Methoden, die parameterlos sind.

Die optionalen Modifizierer lassen sich in zwei Gruppen aufteilen:

  • Modifizierer, die die Sichtbarkeit und damit den Zugriff auf eine Methode beschreiben (Zugriffsmodifizierer).
  • Modifizierer, die eine weitergehende Beeinflussung der Verhaltensweise einer Methode bewirken, beispielsweise in der Vererbung (siehe Kapitel 4).

Zugriffsmodifizierer beschreiben die Sichtbarkeit. Wie Sie wissen, kann eine Klasse nur public oder internal sein. In ähnlicher Weise wird auch die Sichtbarkeit und damit der Zugriff auf Methoden gesteuert (zu denen natürlich auch die Eigenschaften zu zählen sind). Neben den beiden bereits bekannten Zugriffsmodifizierern public und internal gibt es für Methoden noch weitere, die Sie der Tabelle 3.2 entnehmen können.

Tabelle 3.2 Zugriffsmodifizierer der Klassenmitglieder

Zugriffsmodifizierer Beschreibung

public

Der Zugriff unterliegt keinerlei Einschränkungen.

private

Der Zugriff auf ein als private definiertes Mitglied ist nur innerhalb der Klasse möglich, die den Member definiert. Alle anderen Klassen sehen private Member nicht. Deshalb ist darauf auch kein Zugriff möglich.

protected

Der Zugriff auf protected Member ähnelt dem private definierter Member. Die Sichtbarkeit ist in ähnlicher Weise eingeschränkt mit dem kleinen Unterschied, dass protected definierte Mitglieder in abgeleiteten Klassen sichtbar sind. In Kapitel 4 werden wir uns damit noch eingehend beschäftigen.

internal

Der Zugriff auf internal Member ist nur dem Programmcode gestattet, der sich in derselben Anwendung befindet.

protected internal

Stellt eine Kombination aus den beiden Modifizierern protected und internal dar.

Die Angabe eines Zugriffsmodifizierers ist optional. Wird darauf verzichtet, gilt die Methode als private deklariert.

Methoden können als Folge ihres Aufrufs ein Ergebnis an den Aufrufer zurückliefern. Dieses ist von einem bestimmten Datentyp und muss hinter der Liste der Modifizierer angegeben werden. Sehen wir uns das Beispiel der Klasse Circle an. Ergänzend zu der bisherigen Implementierung werden in der Klasse jetzt zusätzlich die beiden Methoden GetArea und GetCircumference bereitgestellt:

public class Circle {
[...]

// Methoden

public double GetArea() {
double area = Math.Pow(Radius, 2) * Math.PI;
return area;
}

public double GetCircumference() {
double circumference = 2 * Radius * Math.PI;
return circumference;
}
}

Listing 3.17 Methoden in der Klasse »Circle«

Die Bezeichner sind so gewählt, dass sie zweifelsfrei die Funktionalität der Methode verraten. Konventionsgemäß fangen öffentliche Methodenbezeichner mit einem Großbuchstaben an und setzen sich nach Möglichkeit aus mehreren Begriffen zusammen, die ihrerseits zur besseren Lesbarkeit immer mit einem Großbuchstaben beginnen. Der Rückgabewert, also das Ergebnis beider Methoden, ist vom Typ double. Beide Methoden sind public und somit uneingeschränkt sichtbar.

Da es in C# keinen Exponentialoperator gibt, wird der fehlende Operator durch die Methode Pow der Klasse Math ersetzt. Dem ersten Parameter wird dabei die zu potenzierende Zahl übergeben, dem zweiten Parameter der Exponent. Die Zahl PI wird durch die gleichnamige Konstante der Klasse Math beschrieben.

Hinter return wird das Resultat angegeben, das dem Aufrufer zurückgeliefert wird. In GetArea ist das der Inhalt der lokalen Variablen area, in GetCircumference der Inhalt von circumference. Sie können hinter return auch direkt eine mathematische Operation angeben, um den Code damit etwas kürzer zu formulieren:

public double GetCircumference() {
return 2 * Radius * Math.PI;
}

public double GetArea() {
return Math.Pow(Radius, 2) * Math.PI;
}

Listing 3.18 Kürzere Formulierung der Methoden »GetArea« und »GetCircumference«

Der Typ des hinter return angegebenen Werts muss mit der Typangabe in der Methodensignatur übereinstimmen oder implizit in diesen konvertiert werden können. Andernfalls ist im return-Statement eine explizite Konvertierung erforderlich. Sobald return erreicht wird, kehrt die Programmausführung zum aufrufenden Code zurück. Alle Anweisungen, die möglicherweise einem return folgen, werden nicht mehr ausgeführt.

Eine Methode mit Rückgabewert ist der einfachste Weg, um dem Methodenaufrufer Daten zu übermitteln. Der Methodenaufruf wird dabei wie eine Variable bewertet, da die Methode einen bestimmten Wert repräsentiert. Deshalb ist es möglich, einen Methodenaufruf in einem Ausdruck als Operand zu benutzen, wie das folgende Listing zeigt:

Circle kreis = new Circle();
kreis.Radius = 12;
int height = 30;
double volume = kreis.GetArea() * height

Listing 3.19 Aufruf von Methoden

Hier wird die Methode GetArea der Circle-Klasse dazu benutzt, das Volumen eines Zylinders zu berechnen.

Der Aufruf einer Methode

Von der Richtigkeit der beiden Methoden in der Klasse Circle wollen wir uns jetzt überzeugen. Um die Methode GetArea der Klasse Circle aufzurufen, muss Circle zuerst instanziiert werden. Anschließend legen wir den Radius des Objekts fest. GetArea wird auf der Referenz des Objekts mittels Punktnotation aufgerufen und liefert einen Rückgabewert, der in der Variablen area entgegengenommen und an der Konsole ausgegeben wird.

Obwohl GetArea und GetCircumference einen Wert liefern, muss dieser nicht unbedingt in einer Variablen zwischengespeichert werden. Es reicht vollkommen aus, das Ergebnis direkt dem Methodenaufruf zu entnehmen. Das wird anhand der Methode GetCircumference gezeigt.

class Program {
static void Main(string[] args) {
Circle kreis = new Circle();
kreis.Radius = 12;

// Kreisfläche abrufen

double area = kreis.GetArea();
Console.WriteLine("Fläche = {0}", area);

// Kreisumfang abrufen

Console.WriteLine("Umfang = {0}", kreis.GetCircumference());
Console.ReadLine();
}
}

Listing 3.20 Testen der Methoden in »Circle«

Andererseits muss der Rückgabewert einer Methode nicht zwangsläufig entgegengenommen werden, man kann ihn auch ignorieren:

Circle kreis = new Circle();
kreis.Radius = 12;
kreis.GetArea();

Das Ergebnis des Methodenaufrufs landet im Nirwana, weil es weder zwischengespeichert noch ausgegeben wird. Vielleicht werden Sie nun sagen, dass der Methodenaufruf dann keinen Sinn mehr ergibt. Aber so einfach lässt sich das nicht verallgemeinern. In unserem Beispiel ist der Aufruf von GetArea zweifelsfrei sinnlos, aber es gibt viele Methoden, deren Rückgabewert man durchaus ignorieren kann bzw. darf. Bei solchen Methoden kommt es nur auf die Operation der Methode an sich an, während der Rückgabewert nur unter bestimmten Umständen von Interesse ist.


Galileo Computing - Zum Seitenanfang

3.5.2 Methoden ohne RückgabewertZur nächsten ÜberschriftZur vorigen Überschrift

Wie bereits weiter oben erwähnt gibt es auch Methoden, die per Definition keinen Rückgabewert liefern.

Syntax einer Methode ohne Rückgabewert

[Modifizierer] void Bezeichner([Parameterliste])

{

[...]

}

Bei diesen Methoden wird anstelle des Rückgabedatentyps das Schlüsselwort void angegeben. Main, der Einstiegspunkt der Laufzeit in eine Anwendung, ist ein typisches Beispiel dafür. Dem Methodennamen folgt in runden Klammern eine optionale Parameterliste, um gegebenenfalls dem Methodenaufruf Daten zu übergeben, die die Methode zur Ausführung benötigt.

Die return-Anweisung ist nicht nur auf Methoden mit Rückgabewert beschränkt. Auch void-Methoden können, falls erforderlich, damit vorzeitig verlassen werden.


Galileo Computing - Zum Seitenanfang

3.5.3 Methoden mit ParameterlisteZur nächsten ÜberschriftZur vorigen Überschrift

Viele Methoden, unabhängig davon, ob sie einen Rückgabewert haben oder nicht, benötigen Dateninformationen, die den Ablauf oder die Steuerung der Operation beeinflussen. Diese Daten werden der Methode beim Aufruf als Argumente übergeben. Die Methode nimmt die Argumente in ihrer Parameterliste in Empfang.

Nehmen wir an, dass wir in der Klasse Circle eine Methode definieren möchten, die den Bezugspunkt des Circle-Objekts in X- und Y-Richtung relativ verschiebt. Die Methode soll Move heißen. In diesem Fall müssen beim Aufruf der Methode die Werte, die die Verschiebung beschreiben, als Argumente übergeben werden:

public void Move(double dx, double dy) {
XCoordinate += dx;
YCoordinate += dy;
}

Listing 3.21 Definition der Methode »Move«

Die Definition eines Parameters erinnert an die Deklaration einer Variablen: Zuerst wird der Typ angegeben, danach folgt der Bezeichner. Beschreibt eine Methode mehrere Parameter, werden diese durch ein Komma getrennt.

Nun wollen wir die parametrisierte Methode testen. Dazu schreiben wir den folgenden Code:

static void Main(string[] args){
Circle kreis = new Circle();
kreis.XCoordinate = -100;
kreis.YCoordinate = 90;
kreis.Move(120, -200);
}

Listing 3.22 Aufruf der Methode »Move«

Bei Methoden, die mehr als einen Parameter erwarten, müssen Sie immer die Reihenfolge der übergebenen Argumente beachten: Das erste Argument wird dem ersten Parameter zugewiesen, das zweite Argument dem zweiten Parameter usw.

Eine weitere Methode in der Klasse »Circle«

Lassen Sie uns an dieser Stelle der Klasse Circle noch eine weitere Methode hinzufügen. Die Methode soll Bigger heißen und zwei Circle-Objekte miteinander vergleichen. Der Aufruf soll wie folgt aussehen:

static void Main(string[] args) {
Circle kreis1 = new Circle();
kreis1.Radius = 12;
Circle kreis2 = new Circle();
kreis2.Radius = 23;

if (kreis1.Bigger(kreis2) == -1)
Console.WriteLine("Objekt 'kreis1' ist kleiner als Objekt 'kreis2'");
Console.ReadLine();
}

Listing 3.23 Aufruf der Methode »Bigger«

Der Rückgabewert der Methode sei »1«, wenn das Objekt, auf dem die Methode aufgerufen wird, größer ist als das Objekt, das dem Parameter übergeben wird. Sind beide Objekte gleich groß, sei der Rückgabewert »0«, ansonsten »–1«.

Die Methode Bigger zu codieren, ist nicht weiter schwierig. Wir übergeben das Circle-Objekt, mit dem das aktuelle Objekt (im Listing 3.23 also kreis1) verglichen werden soll, an einen Parameter vom Typ Circle und können in der Methode den Radius des übergebenen Objekts zur Auswertung heranziehen. Der erste Entwurf würde dann wie folgt aussehen:

public int Bigger(Circle kreis) {
if (Radius > kreis.Radius) return 1;
if (Radius < kreis.Radius) return -1;
return 0;
}

Allerdings müssen wir berücksichtigen, dass an den Parameter der Methode auch ein Objekt übergeben werden könnte, das mit null initialisiert ist. Auf diese Übergabe hin würde der Aufruf der Eigenschaft Radius zu einer Ausnahme führen. Allerdings können wir auch feststellen, dass dann das Objekt, auf dem die Methode Bigger aufgerufen wird, größer ist als null. Diese Überlegung führt uns zu der endgültigen Fassung unserer Methode.

public int Bigger(Circle kreis) {
if (kreis == null || Radius > kreis.Radius) return 1;
if (Radius < kreis.Radius) return -1;
else return 0;
}

Listing 3.24 Die Methode »Bigger«

Beachten Sie, dass die Überprüfung auf null zuerst ausgeführt wird!

Das .NET Framework stellt uns Methoden bereit, die sehr ähnlich der hier vorgestellten Methode Bigger operieren. Tatsächlich werden wir in Kapitel 4 diese Methode auf eine ganz andere Komponente zurückführen und deshalb auch umbenennen müssen.


Galileo Computing - Zum Seitenanfang

3.5.4 MethodenüberladungZur nächsten ÜberschriftZur vorigen Überschrift

Im Verlauf der weiteren Entwicklung der Klasse Circle könnte sich herausstellen, dass noch eine weitere Methode erforderlich ist, die nicht nur den Bezugspunkt des Objekts relativ verschieben soll (gewissermaßen eine Verschiebung im Zweidimensionalen), sondern darüber hinaus auch noch den Radius ändern soll, also eine dreidimensionale Verschiebung. Sie könnten jetzt eine neue Methode bereitstellen und dieser einen in der Klasse Circle eindeutigen Namen geben. Sie dürfen die neue Methode auch Move nennen, obwohl bekanntlich bereits eine Methode mit diesem Bezeichner in der Klasse existiert. Die Technik, mehrere gleichnamige Methoden in einer Klasse zu definieren, wird Methodenüberladung genannt. Mit anderen Worten bedeutet dies, dass Sie die beiden Methoden

public void Move(double dx, double dy, int dRadius) {
XCoordinate += dx;
YCoordinate += dy;
Radius += dRadius;
}

und

public void Move(double dx, double dy) {
XCoordinate += dx;
YCoordinate += dy;
}

in der Klasse Circle bereitstellen dürfen, ohne dass dadurch ein Kompilierfehler verursacht wird.

Eine Verbesserung des Codes wollen wir auch noch vornehmen. Da die Werte für die Eigenschaften XCoordinate und YCoordinate in beiden Methoden gleich berechnet werden, bietet es sich an, in der 3-fach parametrisierten Variante die 2-fach parametrisierte Move-Methode aufzurufen, also:

public void Move(double dx, double dy, int dRadius) {
Move(dx, dy);
Radius += dRadius;
}

Listing 3.25 Überladung der Methode »Move«

Die Methodenüberladung wird üblicherweise eingesetzt, wenn die gleiche oder eine ähnliche Basisfunktionalität unter Übergabe unterschiedlicher Argumente bereitgestellt werden soll.

Von einer gültigen Methodenüberladung wird genau dann gesprochen, wenn

  • sich gleichnamige Methoden in der Anzahl der Parameter unterscheiden,
  • bei gleicher Parameteranzahl zumindest ein Parameter einen anderen Typ beschreibt.

Gemäß den Regeln der Methodenüberladung gelten die folgenden Methodendefinitionen einer fiktiv angenommenen Klasse als überladen:

public void DoSomething() {}

public void DoSomething(byte x) {}

public void DoSomething(long x) {}

public void DoSomething(long x, long y) {}

Eine Methode gilt als nicht gültig überladen, wenn

  • sich die Parameter nur im Bezeichner unterscheiden,
  • die Rückgabewerte der Methoden verschiedene Datentypen haben.

Der Compiler trifft anhand der Übergabeargumente beim Methodenaufruf die Entscheidung, welche Überladung aufzurufen ist. Das kann unter Umständen zu Irritationen führen, wenn in einer Klasse zwei Methoden wie folgt deklariert sind:

public void DoSomething(int x){ [...] }

public void DoSomething(long x){ [...] }

Wird im aufrufenden Code ein Literal (also eine Zahl) übergeben, also beispielsweise

obj.DoSomething(78);

wird dieses standardmäßig als int interpretiert. Das bedeutet, dass die Überladung mit dem long-Parameter nie ausgeführt wird (es sei denn, das Übergabeargument wird explizit konvertiert).

Wird an die Methode eine Variable vom Typ byte übergeben, wird der Compiler die Methode mit dem bestmöglichen Parametertyp suchen: In diesem Fall würde das die Methode mit dem int-Parameter sein.

Zugriff auf Eigenschaften des aktuellen Objekts

Wir sollten noch einen Blick auf die Methodenimplementierung von Move werfen:

public void Move(double dx, double dy) {
XCoordinate += dx;
YCoordinate += dy;
}

Dabei ist zu bemerken, dass die Verschiebung über den Aufruf des set-Accessors der entsprechenden Eigenschaftsmethoden führt. Natürlich hätten wir auch mit

public void Move(double dx, double dy) {
_XCoordinate += dx;
_YCoordinate += dy;
}

den privaten Feldern die neuen Werte direkt mitteilen können. Das wäre allerdings sehr kurzsichtig und könnte zu einem späteren Zeitpunkt zu einer fehlerhaften Klasse führen. Momentan werden zwar alle X- und Y-Koordinatenwerte ohne Einschränkung akzeptiert, aber das muss nicht zwangsläufig immer so bleiben. Vielleicht wird zu einem späteren Zeitpunkt gefordert, dass der Bezugspunkt des Objekts nicht im dritten oder vierten Quadranten des kartesischen Koordinatensystems liegen darf. In diesem Fall müssten die Eigenschaftsmethoden überarbeitet werden, um der neuen Anforderung zu genügen. Trägt die Methode Move die neuen Koordinatenwerte jedoch direkt in die privaten Felder ein, wären die neuen Werte unter Umständen falsch und das Circle-Objekt hätte einen unzulässigen Bezugspunkt. Rufen Sie in Move jedoch den set-Zweig der Eigenschaftsmethode auf, kann Ihnen ein solches Malheur nicht passieren, denn bevor den Feldern die neuen Werte übergeben werden, durchlaufen sie den prüfenden Code des set-Zweigs in XCoordinate und YCoordinate.

Gleiches gilt natürlich auch für das Abrufen eines Eigenschaftswertes. Meistens enthalten die get-Accessoren nur eine return-Anweisung und liefern den Wert ohne weitere Überprüfung an den Aufrufer. Aber sind Sie sich wirklich sicher, ob in naher oder ferner Zukunft nicht auch noch eine Überprüfung des Benutzers notwendig wird, weil nicht jedem Anwender die Auswertung der entsprechenden Eigenschaft gestattet werden kann?

Sie sollten daher immer den folgenden Tipp beherzigen:

Sie sollten prinzipiell nie direkt in private Felder schreiben oder diese direkt auswerten. Benutzen Sie dazu immer, soweit vorhanden, die get- und set-Accessoren der Eigenschaftsmethoden. Damit garantieren Sie eine robuste Klassendefinition, die auch nach einer Änderung fehlerfrei arbeitet.


Galileo Computing - Zum Seitenanfang

3.5.5 Variablen innerhalb einer Methode (lokale Variablen)Zur nächsten ÜberschriftZur vorigen Überschrift

Variablen, die im Anweisungsblock einer Methode deklariert sind, gelten als lokale Variablen. Im nächsten Codefragment ist value eine lokale Variable.

public void DoSomething() {
long value = 34;
[...]
}

Lokale Variablen sind nur in der Methode sichtbar, in der sie deklariert sind. Programmcode, der sich außerhalb der Methode befindet, kann lokale Variablen weder sehen noch manipulieren oder gar auswerten. Das gilt auch für Aufrufverkettungen, wenn beispielsweise aus einer Methode heraus eine andere aufgerufen wird.

Die Lebensdauer einer lokalen Variablen ist auf die Dauer der Methodenausführung begrenzt. Wird die Methode beendet, geht die lokale Variable samt ihrem Inhalt verloren. Ein wiederholter Methodenaufruf hat zur Folge, dass die lokale Variable neu erzeugt wird.

In C# wird eine lokale Variable nicht automatisch mit einem typspezifischen Standardwert initialisiert. Sie sollten daher alle lokalen Variablen möglichst sofort initialisieren und ihnen unter Berücksichtigung des Datentyps einen gültigen Startwert zuweisen. Der Zugriff auf eine nicht initialisierte Variable verursacht eine Fehlermeldung.

public void DoSomething() {
int value;

// die folgende Anweisung verursacht einen Compilerfehler,

// weil value nicht initialisiert ist

Console.WriteLine(value);
}

Der Begriff lokale Variable lässt sich noch weiter ausdehnen, da nicht jede Variable, die innerhalb einer Methode deklariert ist, auch eine Sichtbarkeit aufweist, die sich über den gesamten Anweisungsblock der Methode erstreckt. Sehen Sie sich dazu das folgende Listing an.

class Demo {
public void DoSomething() {
int intVar = 0;
if(intVar > 0)
{
int intX = 1;
for(int i = 0; i <=100; i++)
{
double dblVar = 3.14;
}
}
}
}

Listing 3.26 Sichtbarkeit lokaler Variablen

In der Methode DoSomething sind einige Anweisungsblöcke ineinander verschachtelt. Anweisungsblöcke dienen nicht nur dazu, Anweisungssequenzen zusammenzufassen, sondern beschreiben darüber hinaus auch die Sichtbarkeit lokaler Variablen. Dabei wird die Sichtbarkeit von dem am nächsten stehenden, äußeren geschweiften Klammerpaar begrenzt. Deshalb beschränkt sich die Sichtbarkeit von dblVar auf den Anweisungsblock der for-Schleife und die Sichtbarkeit von intX auf den Anweisungsblock des if-Statements, kann aber auch innerhalb der for-Schleife verwendet werden. Die lokale Variable intVar ist in der gesamten Methode DoSomething bekannt.


Galileo Computing - Zum Seitenanfang

3.5.6 Referenz- und WertparameterZur nächsten ÜberschriftZur vorigen Überschrift

Parameter ohne zusätzlichen Modifizierer

Sehen Sie sich das folgende Beispiel an:

// Beispiel: ..\Kapitel 3\Wertuebergabe

class Program {

static void Main(string[] args) {
int value = 3;
DoSomething(value);
Console.WriteLine("value = {0}", value);
Console.ReadLine();
}

static void DoSomething(int param) {
param = 550;
}
}

Listing 3.27 Parameterübergabe (Call by Value)

In Main wird die lokale Variable value deklariert und danach der Methode DoSomething als Argument übergeben. Die Methode DoSomething nimmt das Argument im Parameter param entgegen und ändert danach den Inhalt von param in 550. Nachdem der Methodenaufruf beendet ist, wird der Inhalt der lokalen Variablen value in die Konsole geschrieben. Wenn Sie das Programm starten, lautet die Ausgabe an der Konsole:

value = 3

Der Inhalt der lokalen Variablen value hat sich nach dem Aufruf der Methode DoSomething nicht verändert.

Um zu verstehen, was sich bei diesem Methodenaufruf abspielt, müssen wir einen Blick in den Teilbereich des Speichers werfen, in dem die Daten vorgehalten werden. Zunächst wird für die Variable value Speicher allokiert. Nehmen wir an, es sei die Speicheradresse 1000. In diese Speicherzelle (genau genommen sind es natürlich vier Byte, die ein Integer für sich beansprucht) wird die Zahl 3 geschrieben.

Ein Parameter unterscheidet sich nicht von einer lokalen Variablen. Genau das ist der entscheidende Punkt, denn folgerichtig ist ein Parameter ebenfalls ein Synonym für eine bestimmte Adresse im Speicher. Mit der Übergabe des Arguments value beim Methodenaufruf wird von DoSomething zunächst Speicher für den Parameter param allokiert – wir gehen von der Adresse 2000 aus. Danach wird der Inhalt des Arguments value – also der Wert 3 – in die Speicherzelle 2000 kopiert.

Ändert DoSomething den Inhalt von param, wird die Änderung in die Adresse 2000 geschrieben. Damit weisen die beiden in unserem Beispiel angenommenen Speicheradressen die folgenden Inhalte auf:

Adresse 1000 = 3

Adresse 2000 = 550

Nachdem der Programmablauf zu der aufrufenden Methode zurückgekehrt ist, wird der Inhalt der Variablen value, also der Inhalt der Speicheradresse 1000 an der Konsole ausgegeben: Es ist die Zahl 3. Diese Technik der Argumentübergabe wird als Wertübergabe (engl.: Call by Value) bezeichnet.

Parameter mit dem Modifizierer »ref«

Nehmen wir nun zwei kleine Änderungen am Listing 3.27 vor. Zuerst wird der Methodenaufruf in Main wie folgt codiert:

DoSomething(ref value);

Im zweiten Schritt ergänzen wir in ähnlicher Weise auch die Parameterliste von DoSomething:

public void DoSomething(ref int param) {...}

Wenn Sie jetzt das Beispiel erneut starten, wird das zu folgender Ausgabe führen:

value = 550

Die Ergänzung sowohl des Methodenaufrufs als auch der Parameterliste um das Schlüsselwort ref hat also bedeutende Konsequenzen für die lokale Variable value – sie hat nach dem Methodenaufruf genau den Inhalt angenommen, der dem Parameter param zugewiesen worden ist. Wie ist das zu erklären?

Beim Aufruf von DoSomething wird nicht mehr der Inhalt der Variablen value übergeben, sondern deren Speicheradresse, also 1000. Der empfangende Parameter param muss selbstverständlich wissen, was ihn erwartet (nämlich eine Speicheradresse), und wird daher ebenfalls mit ref definiert. Für param muss die Methode natürlich auch weiterhin Speicher allokieren – gehen wir auch in diesem Fall noch einmal von der Adresse 2000 aus. Alle Aufrufe an param werden nun jedoch an die Adresse 1000 umgeleitet. Die Methode DoSomething weist dem Parameter param die Zahl 550 zu, die in die Adresse 1000 geschrieben wird. Damit gilt:

Adresse param = Adresse value = 550

Nachdem der Programmablauf an die aufrufende Methode zurückgegeben worden ist, wird an der Konsole der Inhalt der Variablen value – also der Inhalt, der unter der Adresse 1000 zu finden ist – angezeigt: Es handelt sich um die Zahl 550. Diese Technik der Parameterübergabe wird als Referenzübergabe (engl.: Call by Reference) bezeichnet.

Folgende Regeln sind im Zusammenhang mit der Referenzübergabe zu berücksichtigen:

  • In der Parameterliste der Methode muss der Parameter mit dem Schlüsselwort ref gekennzeichnet werden.
  • Im Methodenaufruf muss dem zu übergebenden Argument das Schlüsselwort ref vorangestellt werden.
  • Das zu übergebende Argument muss initialisiert sein, d. h., es muss einen gültigen Wert aufweisen.
  • Das Übergabeargument darf keine Konstante sein. Lautet die Signatur einer Methode beispielsweise
    public void DoSomething(ref int x)
    ist der folgende Methodenaufruf falsch:
    @object.DoSomething(ref 16);
  • Das Übergabeargument darf nicht direkt aus einem berechneten Ausdruck in Form eines Methodenaufrufs bezogen werden, z. B.:
    @object.DoSomething(ref a, ref obj.ProcB());

Parameter mit dem Modifizierer »out«

Zusätzlich zu diesen beiden Übergabetechniken kann ein Methodenparameter auch mit out spezifiziert werden, der in derselben Weise wie ref verwendet wird: Er muss sowohl als Modifizierer des Übergabearguments wie auch als Modifizierer des empfangenen Parameters in der Methodendefinition angegeben werden. Obwohl der Effekt, der mit out erzielt werden kann, derselbe wie bei ref ist, gibt es zwischen den beiden zwei Unterschiede:

  • Während die Übergabe einer nicht initialisierten Variablen mit ref zu einem Kompilierfehler führt, ist dies bei out zulässig.
  • Innerhalb der Methode muss einem out-Parameter ein Wert zugewiesen werden, während das bei einem ref-Parameter nicht zwingend notwendig ist.

In der folgenden Methodendefinition von DoSomething ist param als out-Parameter definiert:

public void DoSomething(out int param) {
param = 550;
}

Die Methode kann wie folgt aufgerufen werden:

int value; 
DoSomething(out value);
Console.WriteLine(value);

Beachten Sie, dass value nicht initialisiert ist. Die abschließende Konsolenausgabe lautet 550. Einem out-Parameter können Sie natürlich auch eine initialisierte Variable übergeben:

int value = 3;
DoSomething(out value);

Allerdings müssen Sie einen wichtigen Punkt bedenken: In der aufgerufenen Methode wird dem out-Parameter in jedem Fall ein neuer Wert zugewiesen. In der aufrufenden Methode hat das ziemlich brutale Konsequenzen: Die Variable, die als Argument übergeben wird, hat nach dem Methodenaufruf garantiert einen anderen Inhalt.

Die Definition eines out- oder ref-Parameters birgt gewisse Risiken, derer man sich bewusst sein sollte: Ein ref-Parameter kann den Originalwert manipulieren – was möglicherweise im laufenden Programm zu falschen Ergebnissen führt, wenn dies unkontrolliert geschieht; ein out-Parameter wird das in jedem Fall tun.

Übergabe von Objekten

Wie Sie wissen, ordnet .NET alle Datentypen zwei Gruppen zu: entweder den Werte- oder den Referenztypen. Zu den Wertetypen gehören beispielsweise bool, byte, int, double usw., zu den Referenztypen alle Typen, die auf einer Klassendefinition basieren.

Bei der Übergabe eines Objekts an einen Parameter wird deutlich, wie wichtig die Unterscheidung zwischen Referenz- und Wertetypen ist. Ein Beispiel soll das zeigen.

// Beispiel: ..\Kapitel 3\ UebergabeEinerReferenz

class Program {
static void Main(string[] args)
{
Demo1 object1 = new Demo1();
Demo2 object2 = new Demo2();
object2.ChangeValue(object1);
Console.WriteLine(object1.Value);
Console.ReadLine();
}
}

class Demo1 {
public int Value = 500;
}

class Demo2 {
public void ChangeValue(Demo1 @object) {
@object.Value = 4711;
}
}

Listing 3.28 Übergabe einer Referenz an eine Methode

Hier sind die beiden Klassen Demo1 und Demo2 definiert. Demo2 hat eine Methode, der im Parameter @object ein Objekt vom Typ Demo1 übergeben wird. In der Methode wird das Feld Value des Demo1-Objekts manipuliert. In Main wird je ein Objekt der beiden Klassen erzeugt. Dem Aufruf der Methode ChangeValue des Demo2-Objekts wird das Objekt vom Typ Demo1 übergeben. Nach dem Methodenaufruf wird an der Konsole der Inhalt des Feldes Value des Demo1-Objekts angezeigt – es ist der Wert 4711.

Die Zuweisung eines Objekts an einen Parameter bedeutet, dass die Referenz auf das Objekt als Argument übergeben wird, nicht irgendein Wert. Eine Referenz beschreibt aber die Adresse des Objekts, wodurch Änderungen an den Werten des Objekts im ursprünglichen Objekt gespeichert werden. Die Übergabe eines Referenztyps entspricht demnach immer der Übergabe »by reference«. Wollen Sie diesen Effekt vermeiden, müssen Sie zuerst eine Kopie des Objekts erzeugen und dieses an den Parameter übergeben.

Nun nehmen wir eine Ergänzung in der Methode der Klasse Demo2 vor:

class Demo2 {
public void ChangeValue(Demo1 @object)
{
@object = new Demo1();
@object.Value = 4711;
}
}

Listing 3.29 Änderung der Klasse »Demo2« aus dem Listing 3.28

@object wird beim Aufruf von ChangeValue der Verweis auf das Originalobjekt übergeben. In der Methode wird der Verweis jedoch »umgebogen«, indem ihm ein neues Demo1-Objekt zugewiesen wird. In diesem Moment liegen zwei Objekte vom Typ Demo1 vor. Der Aufrufer merkt von diesem Vorgang nichts. Er behält weiterhin die Referenz auf das Original, das sich nach Beendigung der Methode auch eindeutig durch das unveränderte Feld (500) zu erkennen gibt.

Eine Änderung des Parameters @object in der Weise, ihm das Schlüsselwort ref voranzustellen, hat allerdings Konsequenzen für den Aufrufer. Denn nun wird das Originalobjekt zerstört und durch das neue ersetzt. Das lässt sich sehr einfach nachweisen, weil an der Konsole der Inhalt von Value als 4711 ausgegeben wird.

Zusammenfassend lässt sich feststellen, dass sich eine Wert- oder Referenzübergabe bei Referenztypen nur dann auswirkt, wenn in der aufgerufenen Methode der Parameter durch Zuweisung einer neuen Referenz überschrieben wird. Es gelten dabei dieselben Gesetze wie bei den Wertetypen.

Methodenüberladung und Parametermodifizierer

Weiter oben haben Sie gelernt, was unter der Methodenüberladung verstanden wird. An dieser Stelle ist noch eine kleine Ergänzung notwendig. Eine gültige Methodenüberladung ist nämlich auch dann gegeben, wenn der Parameter in der ersten Methode als Wertparameter definiert ist und in der überladenen Methode als Referenzparameter mit out bzw. ref. Damit ist die folgende Überladung richtig:

public void DoSomething(int x) { }
public void DoSomething(ref int x) { }

Eine unzulässige Methodenüberladung liegt dann vor, wenn sich die beiden typgleichen Parameter nur dadurch unterscheiden, dass der erste mit ref und der andere mit out definiert ist, beispielsweise:

// Unzulässige Methodenüberladung

public void DoSomething(out int x) { }
public void DoSomething(ref int x) { }

Galileo Computing - Zum Seitenanfang

3.5.7 Besondere Aspekte einer ParameterlisteZur nächsten ÜberschriftZur vorigen Überschrift

Den Typ des Arguments beachten

Nehmen Sie an, Sie hätten die Methode DoSomething in der Klasse Demo wie folgt definiert:

class Demo {
public void DoSomething(int x, float y)
{
[...]
}
}

Die Idee, diese Methode unter Übergabe von Literalen aufzurufen, liegt nahe:

Demo @object = new Demo();

@object.DoSomething(7, 3.12);

Der C#-Compiler wird diesen Code jedoch nicht kompilieren, denn die Übergabe des zweiten Arguments ist falsch. Im ersten Moment mag das unverständlich sein, bei einer genaueren Analyse wird es aber klar, da die Übergabe eines Arguments an einen Parameter nichts anderes ist als eine Zuweisungsoperation, also:

float y = 3.12

Ein Literal vom Typ einer Fließkommazahl wird von der Laufzeitumgebung grundsätzlich als double interpretiert. Jetzt kommen die Regeln der impliziten Konvertierung ins Spiel, nach denen ein double implizit nicht in einen float konvertiert werden kann. Das Literal muss daher zuerst in einen float umgewandelt werden:

@object.DoSomething(7, (float)3.12);

Eine Alternative wäre es, in der aufrufenden Methode eine Variable vom Typ float zu deklarieren, ihr den Wert 3.12 zu übergeben und dann die Variable selbst als Argument anzugeben:

float fltVar = 3.12F;
obj.DoSomething(7, fltVar);

Denken Sie daran, hier das Typsuffix F bzw. f bei der Zuweisung des Dezimalzahl-Literals an die float-Variable anzugeben.

Übergabe eines Arrays an die Parameterliste

Das nächste Beispiel ist ein wenig komplexer. Bisher haben wir jeweils nur einfache Daten als Argument übergeben, nun sollen es mehrere typgleiche sein. Dazu benutzen wir einen Parameter vom Typ eines Arrays.

// Beispiel: ..\Kapitel 3\Array_Uebergabe

class Program {
static void Main(string[] args) {
Demo @object = new Demo();
int[] array = { 3, 6, 9, 4, 13, 22, 2, 29, 17 };
Console.WriteLine("Maximalwert = {0}", @object.GetMaxValue(array));
Console.ReadLine();
}
}

class Demo {
public int GetMaxValue(int[] arr) {
int maxValue = arr[0];
foreach (int element in arr)
if (element > maxValue)
maxValue = element;
return maxValue;
}
}

Listing 3.30 Parameter vom Typ eines Arrays

Die Methode GetMaxValue hat die Aufgabe, aus dem im Parameter übergebenen Array den größten Wert zu ermitteln. Dazu wird in der Methode zuerst die int-Variable maxValue deklariert und ihr der Inhalt des 0-indizierten Array-Elements zugewiesen. In einer foreach-Schleife werden danach alle Array-Elemente durchlaufen, und deren Inhalt wird geprüft. Ist dieser größer als der von maxValue, ersetzt der Array-Wert den alten Inhalt von maxValue. Am Ende wird maxValue an den Aufrufer zurückgegeben. Die foreach-Schleife bewirkt, dass das erste Array-Element insgesamt sogar zweimal ausgewertet wird: bei der Zuweisung an maxValue und in der Schleife. Wenn Sie das vermeiden wollen, können Sie auch eine einfache for-Schleife codieren:

for(int index = 1; index < arr.Length; index++) {[...]}

Der Parameter arr der Methode erwartet die Referenz auf ein Array. Da die Angabe des Array-Namens dieser Forderung entspricht, reicht die Übergabe von array beim Aufruf der Methode aus.

Der Modifizierer »params«

Stellen Sie sich vor, Sie beabsichtigen, eine Methode zu entwickeln, um Zahlen zu addieren. Eine Addition ist nur dann sinnvoll, wenn aus wenigstens zwei Zahlen eine Summe gebildet wird. Daher definieren Sie die Methode wie folgt:

public long Add(int value1, int value2) {
return value1 + value2;
}

Vielleicht haben Sie danach noch die geniale Idee, nicht nur zwei Zahlen, sondern drei bzw. vier zu addieren. Um dieser Forderung zu genügen, könnten Sie die Methode Add wie folgt überladen:

public long Add(int value1, int value2, int value3) {[...]}

public long Add(int value1, int value2, int value3, int value4) {[...]}

Wenn Ihnen dieser Ansatz kritiklos gefällt, sollten Sie sich mit der Frage auseinandersetzen, wie viele überladene Methoden Sie maximal zu schreiben bereit sind, wenn möglicherweise nicht nur vier, sondern 10 oder 25 oder beliebig viele Zahlen addiert werden sollen.

Es muss für diese Problemstellung eine bessere Lösung geben – und es gibt sie auch: Sie definieren einen Parameter mit dem Modifizierer params. Dieser gestattet es, einer Methode eine beliebige Anzahl von Argumenten zu übergeben. Die Übergabewerte werden der Reihe nach in ein Array geschrieben.

Nun kann die Methode Add diesen Feinschliff erhalten. Da eine Addition voraussetzt, dass zumindest zwei Summanden an der Operation beteiligt sind, werden zuerst zwei konkrete Parameter definiert und anschließend ein params-Parameter für alle weiteren Werte.

public long Add(int value1, int value2, params int[] list) {
long sum = value1 + value2;
foreach(int z in list)
sum += z;
return sum;
}

Listing 3.31 Der »params«-Parameter

Werden einem params-Parameter Werte zugewiesen, wird das Array anhand der Anzahl der übergebenen Argumente implizit dimensioniert. In unserem Beispiel werden alle Elemente des Arrays in einer Schleife addiert und in der lokalen Variablen sum zwischengespeichert. Nachdem für das letzte Element die Schleife durchlaufen ist, wird mit return das Ergebnis an den Aufrufer übermittelt.

Mit einem params-Parameter sind ein paar Regeln verbunden, die eingehalten werden müssen:

  • In der Parameterliste darf nur ein Parameter mit params festgelegt werden.
  • Ein params-Parameter steht immer an letzter Position in einer Parameterliste.
  • Eine Kombination mit den Modifikatoren out oder ref ist unzulässig.
  • Ein params-Parameter ist grundsätzlich eindimensional.

Wenn Sie eine Methode aufrufen, die einen params-Parameter enthält, haben Sie zwei Möglichkeiten, diesem Werte zuzuweisen:

  • Sie übergeben die Referenz auf ein Array, z. B.:
    int[] list = {1,2,3};Console.WriteLine(obj.Add(15, 19, list));
  • Sie übergeben diesem Methodenparameter eine Liste von Elementen:
    @object.Add(1, 2, 3, 4, 5, 6);

Vielleicht stellen Sie sich an dieser Stelle die Frage, ob nicht die einfache Deklaration als Array dieselbe Leistung erbringen würde. Mit anderen Worten: Wo liegt der Unterschied zwischen den beiden Methoden

public long Add(params int[] list) {[...]}

und

public long Add(int[] list) {[...]}

wenn beide die Übergabe eines Arrays ermöglichen? Die Antwort ist sehr einfach: Einem params-Parameter muss nicht zwangsläufig ein Wert oder Array übergeben werden, bei einem herkömmlichen Array ist das Pflicht.

Optionale Parameter

Als optionale Parameter werden Methodenparameter bezeichnet, die beim Aufruf in der Parameterliste nicht übergeben werden müssen. Optionale Parameter sind daran zu erkennen, dass ihnen in der Methodendefinition ein Standardwert zugewiesen wird. Wird dem optionalen Parameter beim Methodenaufruf nicht ausdrücklich ein Wert übergeben, behält der optionale Parameter den Standardwert.

Folgendes Codefragment zeigt die Implementierung der Methode DoSomething, die mit value einen optionalen Parameter beschreibt, dessen Standardwert –1 ist.

public void DoSomething(string name, int value = -1) 
{
[...]
}

Listing 3.32 Methode mit optionalem Parameter

Hat eine Methode sowohl feste als auch optionale Parameter, sind zuerst die festen und danach die optionalen anzugeben.

Wollen Sie wissen, ob dem optionalen Parameter ein Wert übergeben worden ist oder nicht, brauchen Sie nur zu prüfen, ob der Parameterwert vom Standardwert in der Parameterdefinition abweicht, also beispielsweise:

public void DoSomething(string name, int value = -1) {
if(value != -1)

// dem optionalen Parameter wurde ein Wert übergeben

}

Die Methode DoSomething kann auf zweierlei Weise aufgerufen werden. Zunächst einmal können Sie den optionalen Parameter ignorieren, z. B.:

@object.DoSomething("Hallo");

Wollen Sie den optionalen Parameter nutzen, weisen Sie ihm einen Wert zu:

@object.DoSomething("Hallo", 100);

Mit optionalen Parametern ließe sich im Grunde genommen die Methodenüberladung durch ein Hintertürchen umgehen. Da optionale Parameter jedoch nicht zum fundamentalen Konzept der Objektorientierung gehören und auch nicht von allen .NET-basierten Programmiersprachen unterstützt werden, sollten Sie der Methodenüberladung den Vorzug geben. Optionale Parameter spielen ihre Vorzüge besonders beim Zugriff auf die Klassen der Microsoft-Office-Objektbibliotheken aus. Unter C# mussten Entwickler lange Zeit optionale Parameter durch eine Instanz der Klasse Missing einsetzen, was zu mehrzeiligen Befehlen führte. Erst mit C# 4.0 hat sich das mit der Einführung optionaler Parameter geändert.

Ein Problemfall ist sicherlich der Standardwert optionaler Parameter. Eine Änderung des Standardwerts in einer neuen Version einer Bibliothek ist nicht zulässig, da das ursprünglich spezifizierte Verhalten der Methode damit zu einem inkonsistenten Verhalten der Anwendung führen kann.

Methodenaufruf mittels benannter Argumente

Ein Sprachfeature, das zusammen mit den optionalen Parametern eingeführt wurde, ist der Methodenaufruf mittels benannter Argumente. Die folgende Anweisung zeigt den Aufruf der Methode Move der Klasse Circle mit benannten Argumenten:

kreis.Move(dx: 100, dy: -200);

Dazu geben Sie bei der Argumentübergabe den Bezeichner des Parameters an und dahinter, getrennt durch einen Doppelpunkt, das Argument. Die Reihenfolge der Argumente spielt keine Rolle, weil sie eindeutig den entsprechenden Parametern zugeordnet werden können.

Sie können unbenannte und benannte Argumente bei einem Methodenaufruf verwenden. Allerdings sind die benannten immer nach den unbenannten anzugeben.

Eine besondere Rolle kommt den benannten Argumenten im Zusammenhang mit Methoden zu, die mehrere optionale Parameter haben. Angenommen, eine Methode definiert vier optionale Parameter, beispielsweise

public void DoSomething(int a = 10, int b = 3, int c = -5, int d = 5) {[...]}

Ohne die syntaktische Fähigkeit benannter Argumente bliebe Ihnen nur übrig, allen Parametern ausdrücklich einen Wert zuzuweisen. Mit

@object.DoSomething(d: 4711);

wird aber die Zuweisung an den vierten optionalen Parameter zu einer sehr überschaubaren und auch gut lesbaren Angelegenheit.


Galileo Computing - Zum Seitenanfang

3.5.8 Zugriff auf private DatenZur nächsten ÜberschriftZur vorigen Überschrift

Eine Objektmethode kann nicht auf die privaten Daten eines anderen Objekts zugreifen. Dies war bisher die Aussage, die allerdings nicht uneingeschränkt gültig ist, wie das folgende Beispiel der Klasse Demo zeigen soll:

class Demo {
private int _Value;

public void DoSomething(Demo @object) {
@object._Value = 122;
}

public int Value {
get { return _Value; }
set { _Value = value; }
}
}

Listing 3.33 Zugriff auf die privaten Daten eines Objekts

In der Klassendefinition ist das Feld _Value privat definiert, um den direkten Zugriff von außen zu unterbinden. Das Feld kann also nur durch die Eigenschaftsmethode Value manipuliert werden.

Mit etwas Besonderem wartet die Methode DoSomething auf. Sie empfängt beim Aufruf im Parameter @object die Referenz auf ein anderes Objekt vom Typ Demo. Es mag überraschend klingen, aber diese Referenz soll dazu benutzt werden, um auf die private Variable _Value des übergebenen Objekts zuzugreifen und einen Wert zuzuweisen. Nach allen bisherigen Aussagen dürfte dieser Zugriff eigentlich nicht erlaubt sein. Mit dem folgenden Listing wollen wir das testen.

static void Main(string[] args) {
Demo object1 = new Demo();
Demo object2 = new Demo();
object1.Value = 4711;
object2.DoSomething(object1);
Console.WriteLine("Private Variable = {0}", object1.Value);
Console.ReadLine();
}

Listing 3.34 Auf private Daten desselben Typs zugreifen

Zuerst werden zwei konkrete Objekte vom Typ Demo erzeugt, und der Eigenschaft Value des Ersteren wird ein Wert zugewiesen. Im nächsten Schritt folgt der Aufruf der DoSomething-Methode des Objekts object2 unter Übergabe der Referenz auf das Objekt object1.

Tatsächlich wird an der Konsole der veränderte Inhalt des privaten Feldes angezeigt, also die Zahl 122. Die Kapselung des Feldes wird aber nur aufgebrochen, wenn man sich innerhalb eines anderen Objekts derselben Klasse befindet. Diese Regel ist die einzige Ausnahme hinsichtlich der ansonsten strengen Datenkapselung.


Galileo Computing - Zum Seitenanfang

3.5.9 Die Trennung von Daten und CodeZur nächsten ÜberschriftZur vorigen Überschrift

Ein Objekt besteht im Wesentlichen aus Eigenschaften und Methoden. Eigenschaften sind im Grunde genommen nichts anderes als Elemente, die objektspezifische Daten enthalten. Objekte werden meist durch mehrere Eigenschaften beschrieben. Für jedes Feld wird entsprechender Speicher reserviert, für einen Integer beispielsweise vier Byte. Alle Eigenschaften eines Objekts sind natürlich nicht wild verstreut im Speicher zu finden, sondern in einem zusammenhängenden Block.

Typgleiche Objekte reservieren grundsätzlich gleich große Datenblöcke, deren interne Struktur vollkommen identisch aufgebaut ist. Wenn Sie in Ihrem Code die Objektvariable der Klasse Circle deklarieren, wird Speicherbereich reserviert, der groß genug ist, um alle Daten aufzunehmen. Mit

Circle kreis1 = new Circle();

zeigt die Objektvariable kreis1 auf die Startadresse dieses Datenblocks im Speicher: Sie referenziert das Objekt. Daher stammt auch die gebräuchliche Bezeichnung Objektreferenz.

Das Speicherprinzip ist in der folgenden Abbildung 3.4 anhand der beiden Objekte kreis1 und kreis2 dargestellt. Tatsächlich sind die Vorgänge zur Laufzeit deutlich komplexer, aber zum Verständnis des Begriffs »Datenblock« und zur Erkenntnis, dass sich hinter jeder Objektvariablen eigentlich eine Speicheradresse verbirgt, trägt die Abbildung anschaulich bei.

Abbildung

Abbildung 3.4 Prinzipielle Verwaltung von Objekten im Arbeitsspeicher

Jedes Objekt beansprucht einen eigenen Datenblock. Diese Notwendigkeit besteht nicht für die Methoden, also den Code einer Klasse. Dieser befindet sich nur einmal »en bloc« im Speicher. Der Code arbeitet zwar mit den Daten eines Objekts, ist aber trotzdem völlig unabhängig von diesen. Im objektorientierten Sprachgebrauch wird dies auch als die Trennung von Code und Daten bezeichnet. Der Code der Methoden wird nur einmal im Speicher abgelegt, und zwar auch dann, wenn noch kein Objekt dieses Typs existiert.


Galileo Computing - Zum Seitenanfang

3.5.10 Namenskonflikte mit »this« lösenZur nächsten ÜberschriftZur vorigen Überschrift

Felder und lokale Variablen können und dürfen gleichnamig sein, wie das folgende Codefragment demonstriert:

class Demo {
public int Value {get; set;}

public void DoSomething1() {
int Value = 0;
[...]
Value = 4711;
}
public void DoSomething2()
{
Value = 25;
}
}

Listing 3.35 Der Einsatz der »this«-Referenz

Die Klasse Demo definiert das Feld Value, derselbe Bezeichner wurde in der Methode DoSomething1 für eine lokale Variable gewählt. Eine Anweisung in DoSomething1 wie beispielsweise

Value = 4711;

verändert den Inhalt der lokalen Variablen, denn deren Gültigkeitsbereich liegt der Anweisung näher als die Felddefinition. Soll in DoSomething1 aber das gleichnamige Feld angesprochen werden, muss dem Feldnamen das Schlüsselwort this vorausgehen, z. B.:

this.Value = 245;

Bei dem this-Schlüsselwort handelt es sich um den Zeiger eines Objekts auf sich selbst. Damit kann das aktuelle Objekt seine eigene Referenz, also gemäß Abbildung 3.4 die Speicheradresse, abfragen oder weiterleiten. Mit this können aller Member einer Klasse adressiert werden, die sich im Kontext eines Objekts befinden.

DoSomething2 manipuliert ebenfalls Value. Da in DoSomething2 die lokale Variable Value der Methode DoSomething1 unbekannt ist, wird der Wert direkt dem Feld zugewiesen. Es wäre aber trotzdem nicht falsch, this zu verwenden.


Galileo Computing - Zum Seitenanfang

3.5.11 Methode oder Eigenschaft?Zur nächsten ÜberschriftZur vorigen Überschrift

Vielleicht haben Sie sich bei den vorherigen Ausführungen gefragt, warum eine relativ komplexe Eigenschaftsmethode angeboten wird. Schließlich könnte man auch über einen herkömmlichen Methodenaufruf einem Feld einen Wert zuweisen bzw. diesen abrufen.

Nehmen wir das Beispiel der Eigenschaft Radius in der Klasse Circle. Um den Paradigmen der Objektorientierung zu entsprechen, wird der Wert, den die Eigenschaft beschreibt, in einem privaten Feld gekapselt und über eine Eigenschaftsmethode der Außenwelt zugänglich gemacht.

public class Circle {
private int _Radius;

public int Radius {
get {return _Radius;}
set {[...]}
}
[...]
}

Nun wollen wir einen alternativen Weg beschreiten. Identisch mit dem gezeigten Codefragment ist nur die private Variable _Radius. Um dieser einen Wert zuzuweisen, wird eine Methode SetRadius definiert. Der Parameter value empfängt den neuen Wert und weist ihn _Radius zu. Die Rückgabe des Eigenschaftswertes erfolgt über die Methode GetRadius.

public class Circle {
private int _Radius;

public void SetRadius(int value) {
_Radius = value;
}

public int GetRadius() {
return _Radius;
}
}

Syntaktisch ist am Code nichts zu beanstanden. Der Aufruf von SetRadius bewirkt, dass dem Feld _Radius ein Wert zugewiesen wird, während der Aufruf von GetRadius den Inhalt zurückliefert. Aber diese Variante ist nicht empfehlenswert, und das hat zwei Gründe:

  • Der Zugriff auf das Feld erfolgt über zwei unterschiedlich benannte Methoden. Würde in allen Klassen so verfahren, wäre der Einarbeitungsaufwand relativ groß, weil, ganz im Gegensatz zu Circle, die meisten Klassen eine größere Anzahl Felder beschreiben. Zudem verringert sich die Übersichtlichkeit des Codings mit der Anzahl der Klassen-Member.
  • Die Syntax, um einer Eigenschaft einen Wert zuzuweisen, würde anders lauten. Normalerweise erwartet der Aufrufer, unter Angabe des Zuweisungsoperators einer Eigenschaft einen Wert zuzuweisen:
    kreis.Radius = 100;
    Würde stattdessen eine Methode ohne Rückgabewert implementiert, müsste der Wert als Argument in Klammern übergeben werden:
    kreis.Radius(100);

Damit ist klar: Um der allgemeinen .NET-Konvention zu folgen und einer Eigenschaft mit dem Zuweisungsoperator einen Wert zuzuweisen bzw. die Eigenschaft auszuwerten, sollten Sie der Definition einer Eigenschaftsmethode den Vorzug geben.


Galileo Computing - Zum Seitenanfang

3.5.12 Umbenennen von Methoden und EigenschaftenZur vorigen Überschrift

Häufig werden Sie Programmcode schreiben und Variablen- oder Methodenbezeichner wählen, die Sie später ändern wollen. An dieser Stelle sei daher auch noch ein Hinweis gegeben, wie Sie mit der Unterstützung von Visual Studio 2012 auf sehr einfache Weise Methoden oder auch Eigenschaften umbenennen können.

Setzen Sie dazu den Eingabecursor auf den umzubenennenden Bezeichner. Öffnen Sie das Kontextmenü, und wählen Sie hier Umgestalten und dann Umbenennen (siehe Abbildung 3.5). Ändern Sie nun den Bezeichner ab. Visual Studio 2012 wird Ihnen auch in einem weiteren Fenster anzeigen, welche Stellen im Code von der Änderung betroffen sind (z. B. Methodenaufrufe), und diese nach Bestätigung ebenfalls an den neuen Bezeichner anpassen.

Abbildung

Abbildung 3.5 Umbenennen von Eigenschaften und Methoden

Alternativ können Sie das Umbenennen eines Bezeichners auch aus dem Menü Umgestalten heraus erreichen.



Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.

>> Zum Feedback-Formular
<< zurück
  Zum Katalog
Zum Katalog: Visual C# 2012

Visual C# 2012
Jetzt bestellen


 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchempfehlungen
Zum Katalog: Professionell entwickeln mit Visual C# 2012






 Professionell
 entwickeln mit
 Visual C# 2012


Zum Katalog: Windows Presentation Foundation






 Windows Presentation
 Foundation


Zum Katalog: Schrödinger programmiert C++






 Schrödinger
 programmiert C++


Zum Katalog: C++ Handbuch






 C++ Handbuch


Zum Katalog: C/C++






 C/C++


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo





Copyright © Rheinwerk Verlag GmbH 2013
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Rheinwerk Computing]

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de