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

Inhaltsverzeichnis
Vorwort zur 5. Auflage
1 Allgemeine Einführung in .NET
2 Grundlagen der Sprache C#
3 Klassendesign
4 Vererbung, Polymorphie und Interfaces
5 Delegates und Ereignisse
6 Weitere .NET-Datentypen
7 Weitere Möglichkeiten von C#
8 Auflistungsklassen (Collections)
9 Fehlerbehandlung und Debugging
10 LINQ to Objects
11 Multithreading und die Task Parallel Library (TPL)
12 Arbeiten mit Dateien und Streams
13 Binäre Serialisierung
14 Einige wichtige .NET-Klassen
15 Projektmanagement und Visual Studio 2010
16 XML
17 WPF – Die Grundlagen
18 WPF-Containerelemente
19 WPF-Steuerelemente
20 Konzepte der WPF
21 Datenbindung
22 2D-Grafik
23 ADO.NET – verbindungsorientierte Objekte
24 ADO.NET – Das Command-Objekt
25 ADO.NET – Der SqlDataAdapter
26 ADO.NET – Daten im lokalen Speicher
27 ADO.NET – Aktualisieren der Datenbank
28 Stark typisierte DataSets
29 LINQ to SQL
30 Weitergabe von Anwendungen
Stichwort

Buch bestellen
Ihre Meinung?

Spacer
<< zurück
Visual C# 2010 von Andreas Kühnel
Das umfassende Handbuch
Buch: Visual C# 2010

Visual C# 2010
geb., mit DVD
1295 S., 49,90 Euro
Rheinwerk Computing
ISBN 978-3-8362-1552-7
Pfeil 3 Klassendesign
Pfeil 3.1 Einführung in die Objektorientierung
Pfeil 3.1.1 Vorteile der objektorientierten Programmierung
Pfeil 3.2 Klassendefinition
Pfeil 3.2.1 Klassen in Visual Studio anlegen
Pfeil 3.2.2 Das Projekt »GeometricObjectsSolution«
Pfeil 3.2.3 Deklaration von Objektvariablen
Pfeil 3.2.4 Zugriffsmodifizierer einer Klasse
Pfeil 3.2.5 Splitten einer Klassendefinition mit »partial«
Pfeil 3.3 Arbeiten mit Objektreferenzen
Pfeil 3.3.1 Prüfen auf Initialisierung
Pfeil 3.3.2 Ein Objekt freigeben
Pfeil 3.3.3 Mehrere Referenzen auf ein Objekt
Pfeil 3.4 Referenz- und Wertetypen
Pfeil 3.5 Eigenschaften eines Objekts
Pfeil 3.5.1 Öffentliche Felder
Pfeil 3.5.2 Datenkapselung mit Eigenschaftsmethoden sicherstellen
Pfeil 3.5.3 Ergänzung der Klasse »Circle«
Pfeil 3.5.4 Lese- und schreibgeschützte Eigenschaften
Pfeil 3.5.5 Sichtbarkeit der Accessoren »get« und »set«
Pfeil 3.5.6 Unterstützung von Visual Studio 2010
Pfeil 3.5.7 Automatisch implementierte Eigenschaften
Pfeil 3.5.8 Vereinfachte Objektinstanziierung mit Objektinitialisierern
Pfeil 3.6 Methoden eines Objekts
Pfeil 3.6.1 Methoden mit Rückgabewert
Pfeil 3.6.2 Methoden ohne Rückgabewert
Pfeil 3.6.3 Methoden mit Parameterliste
Pfeil 3.6.4 Methodenüberladung
Pfeil 3.6.5 Variablen innerhalb einer Methode (Lokale Variablen)
Pfeil 3.6.6 Referenz- und Wertparameter
Pfeil 3.6.7 Zugriff auf private Daten
Pfeil 3.6.8 Namenskonflikte mit »this« lösen
Pfeil 3.6.9 Trennung von Daten und Code im Speicher
Pfeil 3.6.10 Methode oder Eigenschaft?
Pfeil 3.6.11 Methoden und Eigenschaften umbenennen
Pfeil 3.7 Konstruktoren
Pfeil 3.7.1 Konstruktoren bereitstellen
Pfeil 3.7.2 Parametrisierte Konstruktoren und die Objektinitialisierung
Pfeil 3.7.3 Konstruktoraufrufe
Pfeil 3.7.4 Definition von Konstruktoren
Pfeil 3.7.5 »internal«-Konstruktoren
Pfeil 3.7.6 »private«-Konstruktoren
Pfeil 3.7.7 Konstruktorenaufrufe umleiten
Pfeil 3.8 Der Destruktor
Pfeil 3.9 Konstanten in einer Klasse
Pfeil 3.9.1 Konstanten mit dem Schlüsselwort »const«
Pfeil 3.9.2 Schreibgeschützte Felder mit »readonly«
Pfeil 3.10 Statische Klassenkomponenten
Pfeil 3.10.1 Begrifflichkeiten
Pfeil 3.10.2 Statische Klassenvariable in der Klasse »Circle«
Pfeil 3.10.3 Klassenspezifische Methoden
Pfeil 3.10.4 Statische Konstruktoren (Klasseninitialisierer)
Pfeil 3.10.5 Statische Klassen
Pfeil 3.10.6 Stand der Klasse »Circle«


Galileo Computing - Zum Seitenanfang

3.6 Methoden eines Objekts Zur nächsten ÜberschriftZur vorigen Überschrift

In Umgebungen, die nicht objektorientiert sind, werden Prozeduren bzw. Funktionen dazu benutzt, bestimmte Operationen auszuführen. In der objektorientierten Programmierung 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 objektorientierten Programmierung 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.6.1 Methoden mit Rückgabewert Zur 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 hinsichtlich der Vererbung

Zugriffsmodifizierer beschreiben die Sichtbarkeit. Wie Sie wissen, kann eine Klasse nur public oder internal sein. In gleicher Weise wird aber auch die Sichtbarkeit und damit der Zugriff auf die Methoden einer Klasse festgeschrieben. Neben den beiden bekannten Zugriffsmodifizierern public und internal gibt es noch weitere, die Sie 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 das 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 Zugriff auf als private definierte Member. Die Sichtbarkeit ist in gleicher Weise eingeschränkt, jedoch sind als protected definierte Mitglieder in abgeleiteten Klassen sichtbar. Zu diesem Thema folgt später in diesem Kapitel noch mehr.

internal

Der Zugriff auf internal Member ist nur aus den Klassen heraus möglich, die sich in derselben Anwendung befinden.

protected internal

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


Sowohl protected als auch die Kombination protected internal spielen in diesem Kapitel noch keine Rolle, weil sie in direktem Zusammenhang mit dem Vererbungskonzept stehen, das in Kapitel 4, »Vererbung, Polymorphie und Interfaces«, behandelt wird.

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. Das Ergebnis ist von einem bestimmten Datentyp und muss hinter der Liste der Modifizierer angegeben werden. Sehen wir uns das Beispiel der Klasse Circle an, in der ein Feld definiert ist, das den Radius des Kreisobjekts beschreibt. Die Klasse enthält zwei Methoden: GetArea und GetCircumference.


public class Circle {
  private double _Radius; 
  // Methoden
  public double GetArea() {
    double area = 3.14 * Math.Pow(Radius, 2);
    return area;
  }
  public double GetCircumference() {
    double circumference = 2 * 3.14 * Radius;
    return circumference;
  }
  ...
}

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.

Hinter return wird angegeben, was die Methode als Resultat dem Aufrufer zurückliefert. 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 * 3.14 * Radius;
}

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.

Methoden mit Rückgabewert sind der einfachste Weg, um zwischen einer Methode und ihrem Aufrufer Daten auszutauschen. Dabei wird ein Methodenaufruf wie eine Variable bewertet, da die Methode einen bestimmten Wert repräsentiert. So wäre es möglich, einen Methodenaufruf in einem Ausdruck als Operand einzusetzen, wie das folgende Codebeispiel zeigt, das die Methode GetArea der Circle-Klasse dazu benutzt, um das Volumen eines Zylinders zu berechnen:


Circle kreis = new Circle() { Radius = 12 };
double hoehe = 30;
double volumen = kreis.GetArea() * hoehe

Aufruf einer Methode

Von der Richtigkeit der beiden Methoden in der Klasse Circle wollen wir uns auch noch ü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. Stattdessen reicht es 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 { 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();
  }
}

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


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

Das Ergebnis des Methodenaufrufs landet nun im Nirwana, weil er weder zwischengespeichert noch ausgegeben wird. Vielleicht werden Sie nun sagen, dass der Methodenaufruf dann keinen Sinn mehr macht. Aber so einfach lässt sich das nicht verallgemeinern. Tatsächlich ist in unserem Beispiel der Aufruf von GetArea zweifelsfrei sinnlos, aber es gibt sehr viele Methoden, deren Rückgabewert man in den meisten Fällen getrost ignorieren kann. Bei solchen Methoden kommt es nur auf die Operation der Methode an sich an, während der Rückgabewert nur in einigen bestimmten Situationen von Interesse ist.


Galileo Computing - Zum Seitenanfang

3.6.2 Methoden ohne Rückgabewert Zur nächsten ÜberschriftZur vorigen Überschrift

Wie ich bereits weiter oben erwähnt habe, gibt es Methoden, die keinen Rückgabewert liefern:


[Modifizierer] void Bezeichner([Parameterliste])
{
  …
}

Bei diesen Methoden muss anstelle des Rückgabedatentyps das Schlüsselwort void angegeben werden. Main, der Einstiegspunkt der Laufzeit in eine Konsolenanwendung, ist ein typisches Beispiel dafür. Dem Methodennamen folgt in runden Klammern optional eine 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 Methoden ohne Rückgabewert (void) können damit vorzeitig verlassen werden. Bei void-Methoden ist die Angabe von return jedoch optional.


Galileo Computing - Zum Seitenanfang

3.6.3 Methoden mit Parameterliste Zur 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, wir wollten in der Klasse Circle eine Methode definieren, die den Bezugspunkt des Circle-Objekts in X- und Y-Richtung relativ verschiebt. Die Methode soll MoveXY heißen. In diesem Fall müssen beim Aufruf der Methode die Werte, die die Verschiebung beschreiben, als Argumente übergeben werden:


public void MoveXY(int dx, int dy) {
  XCoordinate += dx;
  YCoordinate += dy;
}

Die Deklaration 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.

Wir sollten noch einen Blick auf die Implementierung der Methode werfen. Dabei ist zu bemerken, dass die Verschiebung über den set-Accessor der entsprechenden Eigenschaftsmethoden führt. Natürlich hätten wir auch mit


public void MoveXY(int dx, int dy) {
  _XCoordinate += dx;
  _YCoordinate += dy;
}

den privaten Feldern die neuen Werte direkt mitteilen können. Das wäre allerdings wenig weitsichtig und könnte zu einem späteren Zeitpunkt zu einer fehlerhaften Klasse führen.

Warum das? Ganz einfach: 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üssen die Eigenschaftsmethoden überarbeitet werden, um der neuen Anforderung zu genügen. Trägt die Methode MoveXY die neuen Koordinatenwerte jedoch direkt in die privaten Felder ein, wären die neuen Werte ungeprüft und somit möglicherweise ungültig. Das Circle-Objekt könnte dann einen unzulässigen Bezugspunkt aufweisen. Rufen Sie in MoveXY jedoch den set-Zweig in der Eigenschaftsmethode auf, kann Ihnen das Malheur nicht passieren: 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 Umschweife an den Aufrufer. Aber sind Sie sich wirklich sicher, dass zukünftig nicht auch noch eine Überprüfung codiert wird, ob es dem aktuellen Benutzer der Anwendung überhaupt gestattet ist, den Eigenschaftswert auszulesen?

Sie sollten daher immer den folgenden Tipp beherzigen:


Tipp

Sie sollten prinzipiell nie direkt in private Felder schreiben oder Felder direkt auswerten. Benutzen Sie dazu immer die Eigenschaftsmethoden. Damit garantieren Sie eine robuste Klassendefinition, die auch nach einer Änderung fehlerfrei arbeitet.


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


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

Anstatt eines Literals können Sie auch eine Variable angeben, die zur Laufzeit durch die entsprechenden Werte ersetzt wird:


int x = 120;
int y = -200;
kreis.MoveXY(x, y);

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 { Radius = 12 };
  Circle kreis2 = new Circle { Radius = 23 };
  if (kreis1.Bigger(kreis2) == -1)
    Console.WriteLine("Objekt 'kreis1' ist kleiner als Objekt 'kreis2'");
  Console.ReadLine();
}

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 (hier: kreis1) verglichen werden soll, an einen Parameter vom Typ Circle und können in der Methode den Radius des übergebenen Objekts zur Auswertung heranziehen.


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


Hinweis

Das .NET Framework stellt uns Methoden bereit, die sehr ähnlich wie die hier vorgestellte Methode Bigger operieren.



Galileo Computing - Zum Seitenanfang

3.6.4 Methodenüberladung Zur nächsten ÜberschriftZur vorigen Überschrift

Im Verlauf der weiteren Entwicklung der Klasse Circle könnte sich herausstellen, dass auch noch eine Methode erforderlich ist, die nicht nur den Bezugspunkt des Objekts relativ verschieben soll, sondern darüber hinaus auch noch den Radius ändern soll. Die Änderung des Radius soll ebenfalls einem Parameter übergeben werden.

Sie könnten jetzt eine Methode bereitstellen und dieser einen Namen geben, der in der Klasse Circle eindeutig ist. Sie dürfen die neue Methode aber auch MoveXY nennen, obwohl wir bekanntlich bereits eine Methode mit diesem Bezeichner in der Klasse haben.

Jetzt kommt die Technik der Methodenüberladung ins Spiel, die es erlaubt, mehrere gleichnamige Methoden in einer Klasse zu definieren. Mit anderen Worten bedeutet dies, dass Sie die beiden Methoden


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

und


public void MoveXY(int dx, int dy) {
  XCoordinate += dx;
  YCoordinate += dy;
}

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

Die Methodenüberladung wird üblicherweise dann eingesetzt, wenn die gleiche oder eine ähnliche Basisfunktionalität mit unterschiedlichem Coding bereitgestellt werden soll.

Von einer gültigen Methodenüberladung wird 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 MyMethod() {}
  • public void MyMethod(byte x) {}
  • public void MyMethod(long x) {}
  • public void MyMethod(long x, long y) {}

Eine Methode gilt nicht als überladen, wenn

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

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


public void MyMethod(int x){ ... }
public void MyMethod(long x){... }

Wird im aufrufenden Code ein Literal (also eine Zahl) übergeben, wird diese standardmäßig als int interpretiert. Das bedeutet, die Methode mit dem long-Parameter würde nie aufgerufen. Unter Umständen ist dann eine explizite Konvertierung notwendig.

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


Galileo Computing - Zum Seitenanfang

3.6.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.


public void MyMethod() {
  long lngVariable = 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 der Methode heraus eine zweite und aus dieser heraus wieder eine dritte Methode aufgerufen wird.

Die Lebensdauer einer lokalen Variablen ist auf die Dauer der Methodenausführung begrenzt. Ist diese beendet, wird auch jede in ihr deklarierte lokale Variable aufgegeben, und der Inhalt geht 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 MyMethod() {
  int intVar; 
  // die folgende Anweisung verursacht einen Compilerfehler,
  // weil intVar nicht initialisiert ist
  Console.WriteLine(intVar);
}

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 den folgenden Code an:


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

In der Methode SomeVariables 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 SomeVariables bekannt.


Galileo Computing - Zum Seitenanfang

3.6.6 Referenz- und Wertparameter Zur 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) {
    Demo myDemo = new Demo();
    myDemo.Init();
  }
} 

class Demo {
  public void Init() {
    int intVar = 3;
    TestProc(intVar);
    Console.WriteLine("intVar nachher = {0}", intVar);
    Console.ReadLine();
  }

  public void TestProc(int intPara) {
    intPara = 550;
  }
}

Aus Main heraus wird die Klasse Demo instanziiert, und auf dem Objekt wird die Methode Init aufgerufen. In Init ist die lokale Variable intVar deklariert. Nach der ersten Ausgabe des Variableninhalts an der Konsole wird aus Init heraus die Methode TestProc ausgeführt, der als Argument die lokale Variable intVar übergeben wird, die im Parameter intPara entgegengenommen wird.

In der ersten Anweisung der Methode TestProc wird der Inhalt von intPara in 550 geändert und anschließend an der Konsole ausgegeben. Danach wird die Kontrolle des Programmablaufs an die aufrufende Methode Init zurückgegeben, die ein weiteres Mal den Inhalt der lokalen Variablen intVar anzeigt. Wenn Sie das Programm starten, lautet die Ausgabe an der Konsole:


intVar nachher = 3

Festzuhalten bleibt, dass sich der Inhalt der lokalen Variablen intVar auch nach dem Aufruf der Methode TestProc nicht verändert hat.

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 intVar Speicher alloziert. Nehmen wir an, es sei die Speicheradresse 10042 (10042 = &intVar). 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 intVar beim Methodenaufruf wird von TestProc zunächst Speicher für den Parameter intPara alloziert – wir gehen von der Adresse 10050 (= &intPara) aus. Danach wird der Inhalt des Arguments intVar (also 3) in die Speicherzelle 10050 kopiert.

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


10042 = &intVar => 3
10050 = &intPara => 550

Nachdem der Programmablauf zu der aufrufenden Methode zurückgekehrt ist, wird der Inhalt der Variablen intVar, also der Inhalt der Speicheradresse 10042, auf der Konsole ausgegeben: Es ist die Zahl 3. Diese Technik der Argumentübergabe wird als Wertübergabe (engl. Call by Value) bezeichnet. In Abbildung 3.2 ist der Prozess der beschriebenen Wertübergabe schematisch dargestellt.

Abbildung 3.2 Wertparameter (Call by Value)

Parameter mit dem Modifizierer »ref«

Nehmen wir nun zwei kleine Änderungen vor. Zuerst wird der Methodenaufruf in Init wie folgt codiert:


TestProc(ref intVar);

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


public void TestProc(ref int intPara) {...}

Wenn Sie jetzt das Beispiel noch einmal starten, wird dies an der Eingabeaufforderung zu folgender Ausgabe führen:


intVar nachher = 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 intVar in der Methode Init – sie hat nach dem Methodenaufruf genau den Inhalt angenommen, der dem Parameter intPara zugewiesen worden ist. Wie ist das zu erklären?

Beim Aufruf von TestProc mit


TestProc(ref intVar);

wird nicht mehr der Inhalt der Variablen intVar übergeben, sondern deren Speicheradresse &intVar, also 10042. Der empfangende Parameter intPara muss selbstverständlich wissen, was ihn erwartet (nämlich die Speicheradresse eines int), und er wird daher ebenfalls mit ref deklariert. Für intPara muss die Methode natürlich auch weiterhin Speicher allozieren – gehen wir auch in diesem Fall noch einmal von der Adresse 10050 aus.

intPara wird ein Zeiger auf die Adresse 10042 der Variablen intVar übergeben. Alle Aufrufe an intPara werden nun an die Adresse &intVar, also 10042, umgeleitet. Die Methode TestProc weist dem Parameter intPara die Zahl 550 zu, die nun in die Adresse 10042 geschrieben wird. Damit gilt:


intPara = intVar = 550

Nachdem der Programmablauf an die aufrufende Methode zurückgegeben worden ist, wird an der Konsole der Inhalt der Variablen intVar – also der Inhalt, der unter der Adresse 10042 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 (siehe auch Abbildung 3.3).

Abbildung 3.3 Referenzparameter (Call by Reference)

Fassen wir an dieser Stelle kurz zusammen:

  • Wir sprechen von einer Wertübergabe (Call by Value), wenn der Parameter einer Methode eine Kopie des Arguments erhält und sich Änderungen nur auf die Kopie, nicht aber auf das Original auswirken. Ein solcher Parameter wird auch als Wertparameter bezeichnet.
  • Geht der Definition eines Methodenparameters das Schlüsselwort ref voraus, erhält der Parameter einen Verweis auf die Speicheradresse des übergebenen Arguments. Änderungen werden in das Original geschrieben. Diese Parameter werden als ref- oder Referenzparameter bezeichnet und die Übergabe als Referenzübergabe (Call by Reference).

Während die Übergabe an einen Wertparameter keinen besonderen Regeln unterliegt, ist die Übergabe an einen Referenzparameter an mehrere Bedingungen geknüpft:

  • 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, das heißt, es muss einen gültigen Wert aufweisen.
  • Das Übergabeargument darf keine Konstante sein. Lautet die Signatur einer Methode beispielsweise
public void ProcA(ref int x)
    • ist der folgende Methodenaufruf falsch:
obj.ProcA(ref 16);
  • Das Übergabeargument darf nicht direkt aus einem berechneten Ausdruck in Form eines Methodenaufrufs bezogen werden, z. B.:
obj.ProcA(ref a, ref obj.ProcB());

Parameter mit dem Modifizierer »out«

Zusätzlich zu diesen beiden Übergabetechniken kann ein Parameter auch mit out spezifiziert werden, der in derselben Weise wie ref verwendet wird: Er muss sowohl als Modifizierer des Übergabearguments als 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.
  • In der Methode muss einem out-Parameter ein Wert zugewiesen werden, während das bei einem ref-Parameter nicht zwingend notwendig ist.

In der folgenden Definition der Klasse Demo nimmt die Methode ProcA einen out-Parameter entgegen und weist ihm einen Wert zu.


class Demo {
  public void ProcA(out int x) {
    x = 17;
  }
}

Diese Methode kann wie folgt aufgerufen werden. Beachten Sie, dass intVar nicht initialisiert ist.


int intVar; 
Demo obj = new Demo();
obj.ProcA(out intVar);
Console.WriteLine(intVar);

Die abschließende Konsolenausgabe lautet 17.

Einem out-Parameter können Sie eine initialisierte Variable übergeben:


int a = 2;
Demo obj = new Demo();
obj.ProcA(out a);

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 Folgen: Die Variable, die als Argument übergeben wird, hat nach dem Methodenaufruf garantiert einen anderen Inhalt.

Die Definition eines Referenzparameters 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. Richtig eingesetzt, erhöhen Referenzparameter die Flexibilität der Programmierung.

Ü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 von Objekten an die Parameter wird deutlich, wie wichtig die Unterscheidung zwischen Referenz- und Wertetypen ist. Ein Beispiel soll das zeigen.


class Program {
  static void Main(string[] args) {
    Demo objA = new Demo();
    Demo2 objB = new Demo2();
    objB.ChangeObject(objA);
    Console.WriteLine(objA.TestValue);
    Console.ReadLine();
  }
}
class Demo {
  public int TestValue = 500;
}
class Demo2 {
  public void ChangeObject(Demo obj) {
    obj.TestValue = 4711;
  }
}

Hier sind die beiden Klassen Demo und Demo2 definiert. Demo2 hat eine Methode, der im Parameter obj ein Objekt vom Typ Demo übergeben wird. In der Methode wird das Feld TestValue des Demo-Objekts manipuliert. In Main wird je ein Objekt der beiden Klassen erzeugt. Dem Aufruf der Methode ChangeObject des Demo2-Objekts wird das Objekt vom Typ Demo übergeben. Nach dem Methodenaufruf wird an der Konsole der Inhalt des Feldes TestValue des Demo-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 Startadresse des Objekts, wodurch Änderungen an den Werten des Objekts im ursprünglichen Objekt gespeichert werden. Die Übergabe eines Objekts entspricht demnach immer der Übergabe by reference. Wollen Sie diesen Effekt vermeiden, müssen Sie eine Kopie des Objekts an den Parameter übergeben.

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


class Demo2 {
  public void ChangeObject(Demo obj) {
    obj = new Demo();
    obj.TestValue = 4711;
  }
}

obj wird beim Aufruf von ChangeObject der Verweis auf das Originalobjekt übergeben. In der Methode wird der Verweis jedoch umgebogen, indem ihm der Verweis auf ein neues Demo-Objekt zugewiesen wird. In diesem Moment liegen zwei Objekte vom Typ Demo 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 obj 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 TestValue 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 dazu 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 MyMethod(int x) { }
public void MyMethod(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 MyMethod(out int x) { }
public void MyMethod(ref int x) { }

Besondere Aspekte einer Parameterliste

Typ des Arguments beachten

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


class Demo {
  public void MyMethod(int x, float y) {
    ...
  }
}

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


Demo myObj = new Demo();
myObj.MyMethod(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:


float y = 3.12

Ein Literal vom Typ einer Fließkommazahl wird von der Laufzeitumgebung grundsätzlich als double interpretiert. Jetzt kommen die Richtlinien 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:


obj.MyMethod(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.MyMethod(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\ArrayUebergabe
// -----------------------------------------------------------------
class Program {
  static void Main(string[] args) {
    Demo myObj = new Demo();
    int[] myArr = { 3, 6, 9, 4, 13, 22, 2, 29, 17 };
    Console.Write("Der Maximalwert beträgt ");
    Console.Write(myObj.GetMaxValue(myArr));
    Console.ReadLine();
  }
}
// Klassendefinition
class Demo {
  public int GetMaxValue(int[] arr) {
    int maxValue = arr[0];
    foreach (int element in arr)
      if (element > maxValue)
        maxValue = element;
    return maxValue;
  }
}

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 geprüft. Ist der Inhalt 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 myArr beim Aufruf der Methode aus:


myObj.GetMaxValue(myArr);

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 Addition(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 Addition wie folgt überladen:


public long Addition(int value1, int value2, int value3) 
{/*...*/}
 
public long Addition(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 Addition 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 Addition(int value1, int value2, params int[] liste) {
  long summe = value1 + value2;
  foreach(int z in liste)
    summe += z;
  return summe;
}

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 summe zwischengespeichert. Nachdem für das letzte Element die Schleife durchlaufen hat, 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 ist immer das letzte Element einer Parameterliste.
  • Eine Kombination mit den Modifikatoren out oder ref ist unzulässig.
  • Ein params-Parameter ist immer 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[] myList = {1,2,3};
Console.WriteLine(obj.Addition(15, 19, myList));
  • Sie übergeben diesem Methodenparameter eine Liste von Elementen:
obj.Addition(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 Addition(params int[] liste) {/*...*/}

und


public long Addition(int[] liste) {/*...*/}

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 jedoch Pflicht.

Optionale Parameter

Sehr viel Spektakuläres hat sich mit dem Erscheinen von C# 4.0 im Vergleich zu C# 3.0 nicht getan. Vielmehr wurden ein paar wenige Spracherweiterungen ergänzt, die das Leben des Entwicklers in einigen wenigen speziellen Sonderfällen erleichtern. Dazu gehören die optionalen und die benannten Parameter.

Im Vergleich zu Visual Basic .NET war C# immer um eine kleine Nasenlänge voraus. In einem Punkt aber war das nicht der Fall: VB.NET kannte schon immer optionale Parameter. Diese sind nun mit der Version 4 auch in C# eingeführt worden.

Als optionale Parameter werden Methodenparameter bezeichnet, die beim Aufruf in der Parameterliste nicht angeführt werden müssen. Optionale Parameter, die im Coding nicht angegeben sind, können dennoch einen Standardwert erhalten.

Folgendes Codefragment zeigt die Implementierung der Methode TestMethod, die einen optionalen Parameter value des Datentyps Integer mit dem Standardwert 25 enthält.


public void TestMethod(string name, int value = 25)
{
  ...
}

Sie können die Methode aufrufen und dabei den optionalen Parameter ignorieren, z. B.:


obj.TestMethod("Hallo");

Wollen Sie den optionalen Parameter nutzen, weisen Sie ihm ein Argument zu:


obj.TestMethod("Hallo", 100);

Soll eine Methode sowohl fixe als auch optionale Parameter haben, müssen Sie zuerst die fixen und danach die optionalen angeben.

Mit optionalen Parametern lässt sich die Überladung einer Methode durch ein Hintertürchen umgehen. Im Einzelfall ist zu prüfen, welcher Variante der Vorzug gegeben werden soll. Ein Problemfall ist bei den optionalen Parametern sicherlich der Standardwert. Eine Änderung des Standardwerts in der 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. Optionale Parameter spielen aber ihre Vorzüge beim Zugriff auf die Klassen der Microsoft-Office-Objektbibliotheken aus. Unter C# mussten Entwickler bisher optionale Parameter durch eine Instanz der Klasse Missing einsetzen, was zu mehrzeiligen Befehlen führte. Hier hatte bisher VB.NET Vorteile: Die Methodenaufrufe fielen deutlich kürzer aus.

Aufruf mittels benannter Argumente

Ein weiteres neues Sprachfeature von C# 4.0 ist, dass Sie bei Methodenaufrufen auch benannte Argumente angeben können. Die folgende Anweisung zeigt den Aufruf der Methode MoveXY der Klasse Circle mit benannten Argumenten:


kreis1.MoveXY(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, da Sie den Argumenten namentlich eindeutig den Zielparameter nennen:


kreis1.MoveXY(dy: -200, dx: 100);

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 TestMethod(int a = 10, int b = 3, int c = -5, int d = 5) {...}

Soll nur dem letzten Parameter ein spezifischer Wert übergeben werden, dürfen Sie die Methode nicht mit


obj.TestMethod( , , , 4711); // falsch!

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


obj.TestMethod(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.6.7 Zugriff auf private Daten Zur nächsten ÜberschriftZur vorigen Überschrift

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


class TestClass {
  private int intVar;
  public void InternProc(TestClass obj) {
    Console.Write("intVar des Objekts = {0}",obj.intVar);
  }
  public int MyValue {
    get {return intVar;}
    set {intVar = value;}
  }
}

In der Klassendefinition ist das Feld intVar als private deklariert, um den unbefugten, direkten Zugriff von außen zu unterbinden. Dieses Feld kann infolgedessen nur durch eine Eigenschaft manipuliert werden, in unserem Beispiel MyValue.

Mit etwas Besonderem wartet die Methode InternProc auf. Sie empfängt beim Aufruf im Parameter obj die Referenz auf ein anderes Objekt vom Typ TestClass. Es mag überraschend klingen, aber diese Referenz soll dazu benutzt werden, um auf die private Variable intVar des übergebenen Objekts zuzugreifen. Nach allen bisherigen Aussagen dürfte dieser Zugriff eigentlich nicht erlaubt sein.

Im folgenden Codefragment wollen wir die Klasse TestClass testen. Dazu werden zwei konkrete Objekte vom Typ TestClass erzeugt und wird der Eigenschaft MyValue des ersteren ein Wert zugewiesen.


TestClass obj1 = new TestClass();
TestClass obj2 = new TestClass();
obj1.MyValue = 4711;

Im nächsten Schritt folgt der Aufruf der InternProc-Methode des Objekts obj2 unter Übergabe der Referenz auf das Objekt obj1:


obj2.InternProc(obj1);

Tatsächlich wird an der Konsole der Inhalt der gekapselten, privaten Variablen angezeigt:


intVar des Objekts = 4711

Die Kapselung der Variablen wird also aufgebrochen, die Variable ist auswertbar. Dieser Effekt scheint im Widerspruch zu dem zu stehen, was Sie bisher über die Kapselung gehört haben, und bildet die einzige Ausnahme von der Regel.


Galileo Computing - Zum Seitenanfang

3.6.8 Namenskonflikte mit »this« lösen Zur nächsten ÜberschriftZur vorigen Überschrift

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


class Demo {
  public int myValue;
  public void Method1() {
    int myValue = 0;
    ...
    myValue = 4711;
  }

  public void Method2() {
    myValue = 25;
  }
}

Demo enthält das Feld myValue. Derselbe Bezeichner wurde in der Methode Method1 für eine lokale Variable gewählt. Eine Anweisung in Method1 wie beispielsweise


myValue = 4711;

verändert den Inhalt derjenigen Variablen, deren Gültigkeitsbereich der der Anweisung am nächsten stehende ist – in diesem Fall wird also der Inhalt der lokalen Variablen geändert und nicht das gleichnamige Feld. Soll in Method1 aber das gleichnamige, auf Klassenebene deklarierte Feld angesprochen werden, muss dem Feldnamen das Schlüsselwort this vorausgehen, z. B.:


this.myValue = 245;

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


Hinweis

Bei dem Schlüsselwort this handelt es sich um den Zeiger eines Objekts auf sich selbst. Damit kann das aktuelle Objekt seine eigene Referenz abfragen oder weiterleiten.



Galileo Computing - Zum Seitenanfang

3.6.9 Trennung von Daten und Code im Speicher Zur nächsten ÜberschriftZur vorigen Überschrift

Ein Objekt besteht im Wesentlichen aus Eigenschaften und Methoden. Eigenschaften sind im Grunde genommen nichts anderes als Variablen, also Elemente, die Daten enthalten. Objekte werden meist durch mehrere Felder (Eigenschaften) beschrieben. Für jedes Feld wird entsprechender Speicher reserviert, für einen Integer beispielsweise vier Byte. Alle Eigenschaften eines Objekts sind 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 Zustandsdaten aufzunehmen. Mit


Circle kreis = new Circle();

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

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.6.10 Methode oder Eigenschaft? Zur nächsten ÜberschriftZur vorigen Überschrift

Vielleicht haben Sie sich bei den vorangegangenen 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 XCoordinate 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 _XCoordinate;
  public int XCoordinate {
    get {return _XCoordinate;}
    set {_XCoordinate = value;}
  }
  ...
}

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


public class Circle {
  private int _XCoordinate = 0;
  public void SetXCoordinate(int newValue) {
    _XCoordinate = newValue;
  }
  public int GetXCoordinate() {
    return _XCoordinate;
  }
}

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

  • Der Zugriff auf das Feld erfolgt über zwei Methoden mit unterschiedlichen Namen. Würde in allen Klassen so verfahren, wäre der Einarbeitungsaufwand relativ groß, weil die meisten Klassen eine größere Anzahl Felder beschreiben. Zudem verringert sich die Übersichtlichkeit der Klassenfunktionalitäten mit der Anzahl der Klassenmember.
  • Die Syntax, um einer Eigenschaft einen Wert zuzuweisen, würde anders lauten. Normalerweise erwartet der Aufrufer einer Klasse, unter Angabe des Zuweisungsoperators einer Eigenschaft einen Wert zuzuweisen:
obj.XCoordinate = 100;
    • Würde stattdessen eine Methode ohne Rückgabewert implementiert, müsste der Wert als Argument in Klammern übergeben werden:
obj.SetXCoordinate(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.6.11 Methoden und Eigenschaften umbenennen topZur 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 2010 auf sehr einfache Weise Methoden oder auch Eigenschaften umbenennen können.

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

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

Abbildung 3.4 Umbenennen von Eigenschaften und Methoden



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# 2010

Visual C# 2010
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 2010
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