Programmieren in C#: Einführung


Kapitel 3: Objektmerkmale


Inhaltsverzeichnis

Dieses Buch ist unter einer Creative Commons-Lizenz lizensiert.


3.1 Allgemeines

Felder, Eigenschaften und Methoden

Sie haben im Kapitel 2, Objektorientierung die Objektorientierung in groben Zügen kennengelernt. In diesem Kapitel sollen nun einige Details rund um Klassen und Objekte beleuchtet und vertieft werden. So lernen Sie Felder und Methoden im Detail kennen. Sie lernen spezielle Methoden wie Konstruktoren und Destruktoren kennen. Sie erfahren, wie Zugriffsattribute Ihnen helfen, größere Programme effizienter zu entwickeln und wie Eigenschaften Ihnen Arbeit ersparen. Abschließend lernen Sie die Bedeutung des Schlüsselworts static kennen.


3.2 Felder

Daten speichern

Sie wissen aus dem vorherigen Kapitel, dass Objekte über Felder und Methoden beschrieben werden. Felder sind dabei ganz gewöhnliche Variablen, die innerhalb der geschweiften Klammern einer Klasse definiert werden.

using System; 

class Favorite 
{ 
  string name; 
  Uri url; 
} 

Obige Klasse namens Favorite soll Favoriten nachahmen, wie sie im Internet Explorer verwendet werden. In anderen Browsern werden Favoriten Lesezeichen genannt. Allen Browsern aber ist gemeinsam, dass beim Anlegen eines Favoriten (oder Lesezeichens) ein Name und eine Adresse gespeichert wird. So können Sie die Website www.highscore.de, wenn sie Ihnen gefällt, als Favoriten ablegen: Der Browser speichert dann diese Adresse unter einem Namen wie beispielsweise "Highscore - Programmieren lernen".

www.highscore.de als Favorit im Internet Explorer

Weil ein Favorit aus einer Adresse und einem Namen besteht, enthält die Klasse Favorite zwei Felder. Es handelt sich hierbei um ganz gewöhliche Variablendefinitionen. Das heißt, es wird jeweils ein Datentyp gefolgt von einem Variablennamen angegeben. Die Variable name hat den Datentyp string, die Variable url den Datentyp Uri.

Variablen, die in den geschweiften Klammern einer Klasse definiert werden, heißen Felder. Ob es sich dabei um Referenz- oder Wertvariablen handelt, ist unerheblich. In der oben definierten Klasse Favorite sind beide Felder Referenzvariablen. Selbstverständlich ist es aber auch möglich, Wertvariablen als Felder zu verwenden.

Wenn Sie eigene Klassen entwickeln, fügen Sie demnach die Felder einer Klasse hinzu, die wichtig sind, damit die Klasse das gewünschte Objekt so beschreibt, wie es für eine bestimmte Aufgabenstellung angebracht ist. Da in diesem Buch Schritt für Schritt ein eigener Browser entwickelt werden soll, macht es Sinn, wenn Favoriten aus einem Namen und einer Adresse bestehen.


3.3 Methoden

Mit Feldern arbeiten

Die oben entwickelte Klasse Favorite kann bisher einfach nur einen Namen und eine Adresse speichern. Schön wäre es aber, wenn es auch möglich wäre, die entsprechende Adresse im Browser zu öffnen. Immerhin ist das der Sinn und Zweck von Favoriten: Sie speichern Adressen von Webseiten, damit Sie diese rasch wiederfinden und öffnen können.

Die Klasse Favorite soll nun erweitert werden. Sie soll eine Funktion anbieten, um die Adresse im Browser zu öffnen. Da hier etwas passieren soll - so soll sich ein Browserfenster öffnen und die entsprechende Webseite anzeigen - muss die Klasse Favorite um eine Methode erweitert werden. Felder verwenden Sie, um Daten zu speichern. Methoden verwenden Sie, wenn etwas mit Feldern passieren soll.

using System; 
using System.Diagnostics; 

class Favorite 
{ 
  string name; 
  Uri url; 

  void Open() 
  { 
    Process.Start(url.ToString()); 
  } 
} 

Die Klasse Favorite wurde um eine Methode Open() ergänzt. Sie erkennen Methoden daran, dass hinter ihrem Namen ein Paar runde Klammern angegeben wird. In den runden Klammern können Parameter definiert werden, die es einer Methode ermöglichen, Eingabewerte entgegen zu nehmen. Sind die runden Klammern so wie oben leer, erwartet die Methode keine Eingabewerte.

Während hinter dem Methodennamen Klammern und mögliche Parameter angegeben werden, muss vor dem Methodennamen ein Datentyp stehen. Dieser Datentyp gibt an, welche Art von Information eine Methode als Ergebnis zurückliefert. Gibt eine Methode kein Ergebnis zurück, wird void angegeben. Wohin aber wird ein mögliches Ergebnis zurückgegeben?

Was Sie oben im Beispielcode sehen, ist eine Methodendefinition. Anhand des Methodenkopfes kann man erkennen, dass die Methode Open() heißt, keine Parameter erwartet und keinen Rückgabewert besitzt. Eine Methodendefinition an sich macht jedoch erstmal gar nichts. Es ist ein Methodenaufruf notwendig, damit der Code, der in den geschweiften Klammern einer Methodendefinition steht, auch ausgeführt wird.

In zahlreichen Beispielen haben Sie bereits gesehen, dass eine Methode aufgerufen werden kann, indem hinter einer Variable der Punkt als Zugriffsoperator gefolgt vom Methodennamen und einem Paar runder Klammern gesetzt wird. Denken Sie zum Beispiel an den Aufruf der Methode WriteLine() im Beispiel im Einführungskapitel. Sie werden in diesem Kapitel aber noch Beispiele sehen, in denen Open() aufgerufen wird.

Die Implementation der Methode Open() besteht nur aus einer einzigen Zeile. Diese Zeile ist einem Microsoft Knowledge Base-Artikel entnommen, in dem Microsoft erklärt, wie in C# der Browser geöffnet werden kann. Dazu muss eine Adresse wie http://www.highscore.de/ als einziger Parameter der Methode Start() der Klasse Process übergeben werden. Diese Klasse ist im Namensraum System.Diagnostics definiert.

Die Klasse Process beschreibt Prozesse. Prozesse sind momentan laufende Anwendungen. Da Anwendungen, um zu laufen, gestartet werden müssen, hat Microsoft der Klasse Process eine Methode Start() spendiert. Start() erwartet einen Parameter, um zu erfahren, welches Programm gestartet werden soll. Eigentlich müssten Sie nun den Dateinamen eines Browsers angeben, um den entsprechenden Browser zu starten. Da unter Windows aber Dokumenten Standardprogramme zugewiesen sein können und Sie es zum Beispiel gewohnt sind, dass bei einem Doppelklick auf eine Datei mit der Endung doc automatisch Microsoft Word gestartet wird, können Sie an Start() auch eine Adresse wie http://www.highscore.de/ übergeben. Daraufhin wird automatisch der Standardbrowser geöffnet.

Beachten Sie, dass die Variable url nicht direkt als Parameter an Start() übergeben wird. Die Methode Start() akzeptiert nämlich keinen Parameter mit dem Datentyp Uri. Es wird jedoch ein Parameter vom Datentyp string akzeptiert. Da die Klasse Uri eine Methode ToString() anbietet, die die Adresse als Zeichenkette vom Typ string zurückgibt, wird der Rückgabewert dieser Methode als Parameter an Start() übergeben. Dies geschieht, indem ToString() innerhalb der runden Klammern von Start() aufgerufen wird. Auf diese Weise können Methodenaufrufe verschachtelt werden, um den Rückgabewert der einen Methode als Parameter an die andere Methode zu übergeben.


3.4 Konstruktoren

Felder initialisieren

Während die Klasse Favorite bisher in der Lage ist, einen Namen und eine Adresse zu speichern und sogar eine Methode besitzt, um die entsprechende Adresse im Browser zu öffnen, fehlt bisher eine Möglichkeit, die beiden Variablen name und url zu initialisieren. Bisher handelt es sich bei beiden Variablen um Referenzvariablen, die auf kein Objekt zeigen. Der Wert, den diese Referenzvariablen daher speichern, ist null. null ist ein Schlüsselwort, das angibt, dass eine Referenzvariable momentan auf kein Objekt verweist.

Sie wissen bereits aus dem vorherigen Kapitel, dass Klassen instantiiert werden müssen, um Objekte zu erstellen, mit denen dann gearbeitet werden kann. Wenn nun ein Objekt vom Typ Favorite erstellt wird, wäre es praktisch, wenn dieses Objekt gleichzeitig initialisiert werden könnte, um eine Adresse und einen Namen zu speichern. C# ermöglicht dazu die Definition einer speziellen Methode, die Konstruktor heißt.

using System; 
using System.Diagnostics; 

class Favorite 
{ 
  string name; 
  Uri url; 

  Favorite(string name, Uri url) 
  { 
    this.name = name; 
    this.url = url; 
  } 

  void Open() 
  { 
    Process.Start(url.ToString()); 
  } 
} 

Ein Konstruktor ist eine Methode, die genauso heißt wie die Klasse, in der sie definiert ist. Da im obigen Beispielcode die Klasse Favorite heißt, ist die Methode Favorite() ein Konstruktor.

Ein Konstruktor unterscheidet sich grundsätzlich nicht von anderen Methoden. Es gibt lediglich zwei Besonderheiten:

  • Bei der Definition eines Konstruktors darf kein Rückgabewert angegeben werden - noch nicht einmal void.

  • Ein Konstruktor kann nicht explizit aufgerufen werden. Pro Objekt wird ein Konstruktor nur ein einziges Mal ausgeführt - und zwar dann, wenn das Objekt mit new erstellt wird.

Um auf die beiden Felder name und url zuzugreifen und sie zu initialisieren, wird ihnen innerhalb des Konstruktors das Schlüsselwort this vorangestellt. Mit this kann sich explizit auf Merkmale des Objekts bezogen werden, in dem this verwendet wird. Im obigen Beispielcode ist this notwendig, weil die Parameter des Konstruktors ebenfalls name und url heißen. Damit der Compiler weiß, dass links vom Zuweisungsoperator nicht die Parameter, sondern die Felder gemeint sind, muss this vorangestellt werden.

Es ist natürlich auch möglich, die Parameter anders zu nennen oder zum Beispiel groß zu schreiben. Üblicherweise beginnen in C# jedoch Variablen mit einem Kleinbuchstaben. In diesem Buch wird diese Konvention übernommen.

Der oben definierte Konstruktor könnte wie folgt eingesetzt werden - könnte, weil der C#-Compiler momentan bei folgender Code-Zeile meckern würde. Dem Beispielcode muss noch ein Zugriffsattribut hinzugefügt werden, damit die folgende Code-Zeile so tatsächlich kompiliert werden kann.

var favHighscore = new Favorite("Highscore - Programmieren lernen", new Uri("http://www.highscore.de/")); 

Sie haben das Schlüsselwort new bereits kennengelernt und wissen, dass Sie hinter new einen Datentyp angeben müssen. Sie wissen außerdem, dass hinter dem Datentyp ein Paar runder Klammern angegeben werden muss. Nun wissen Sie, dass diese runden Klammern einen Konstruktoraufruf darstellen. Wenn die Klasse Favorite also einen Konstruktor definiert, der zwei Parameter erwartet, dann können Sie auf genau diesen Konstruktor zugreifen und in runden Klammern zwei Parameter angeben. Auf diese Weise ist es möglich, beim Erstellen eines Objekts dieses gleichzeitig zu initialisieren.

Beachten Sie, dass es von der Klassendefinition abhängt, ob und wie viele Parameter Sie in runden Klammern hinter new angeben müssen. Wenn eine Klasse wie Favorite einen einzigen Konstruktor definiert, der zwei Parameter erwartet, müssen Sie in runden Klammern genau zwei Parameter angeben. Klassen können mehrere Konstruktoren definieren, die sich in Anzahl und Typ der Parameter unterschieden. Sie müssen, wenn Sie mit new ein Objekt erstellen, aber immer einen von der Klasse angebotenen Konstruktor verwenden.


3.5 Destruktoren

Native Ressourcen freigeben

Das Gegenstück zum Konstruktor ist der Destruktor. Es handelt sich hierbei um eine Methode, die aufgerufen wird, wenn ein Objekt zerstört wird.

Der Destruktor wird in C# selten verwendet. Während es häufig notwendig ist, Konstruktoren zu definieren, um Objekte auf verschiedene Art und Weise zu initialisieren, gibt es selten einen Grund, den Destruktor zu definieren. Denn wenn ein Objekt zerstört wird, wird es komplett aus dem Speicher geladen und verschwindet.

Einen Destruktor zu definieren macht nur dann Sinn, wenn ein Objekt für eine native Ressource Verantwortung trägt und sichergestellt werden soll, dass diese Ressource freigegeben wird, wenn das Objekt verschwindet. Weil ein Destruktor immer garantiert aufgerufen wird, kann auf diese Weise die Freigabe der nativen Ressource garantiert werden.

Mit nativen Ressourcen sind alle Arten von Objekten gemeint, die nicht auf der .NET-Plattform laufen, sondern zum Beispiel durch das Betriebssystem zur Verfügung gestellt werden. Derartige Zugriffe auf native Ressourcen sind in C# zwar möglich, finden aber selten statt, da das .NET-Framework mit seinen unzähligen Klassen den Zugriff auf Betriebssystemfunktionen vereinfacht. Muss eines Ihrer Objekte aber auf Ressourcen außerhalb der .NET-Plattform zugreifen, ist es oft notwendig, diese Ressourcen freizugeben, wenn sie nicht mehr gebraucht werden. Während auf der .NET-Plattform ein Garbage Collector zur Verfügung steht, der Ressourcen automatisch freigibt, kann Ihnen der Garbage Collector bei der Verwaltung nativer Ressourcen nicht weiterhelfen. Native Ressourcen müssen von Ihnen selbst freigegeben werden.

Der Klasse Favorite wird im Folgenden exemplarisch ein Destruktor hinzugefügt, damit Sie sehen, wie Destruktoren definiert werden. Die Definition des Destruktors bleibt jedoch leer, da Favorite nicht auf Ressourcen außerhalb der .NET-Plattform zugreift und somit keinen Destruktor benötigt.

using System; 
using System.Diagnostics; 

class Favorite 
{ 
  string name; 
  Uri url; 

  Favorite(string name, Uri url) 
  { 
    this.name = name; 
    this.url = url; 
  } 

  ~Favorite() 
  { 
  } 

  void Open() 
  { 
    Process.Start(url.ToString()); 
  } 
} 

Sie werden in den seltensten Fällen Destruktoren definieren. Selbst dann, wenn Sie eine Klasse entwickeln, die auf Ressourcen außerhalb der .NET-Plattform zugreift, ist es meist besser, eine Methode wie Close() zu definieren, die explizit aufgerufen werden kann, um eine Ressource freizugeben. Der Gargabe Collector garantiert zwar, dass der Destruktor eines Objekts aufgerufen wird, wenn das Objekt zerstört wird. Es ist aber nicht vorherzusehen, wann der Garbage Collector sich entschließt, ein Objekt zu zerstören. Selbst dann, wenn Sie ein Objekt in Ihrem Programm nicht mehr benötigen und es keine Referenzvariable gibt, die auf das entsprechende Objekt zeigt, kann es einige Zeit dauern, bis der Garbage Collector das Objekt zerstört. Um sicherzustellen, dass die entsprechende Ressource vom Objekt dann freigegeben wird, wenn sie nicht mehr benötigt wird, kann eine Methode wie Close() nützlich sein, die explizit aufgerufen werden kann.

Beachten Sie, dass das .NET-Framework eine Methode Dispose() vorsieht, um Ressourcen explizit freizugeben. Diese Methode Dispose() sollte einer selbstgestrickten Methode wie Close() vorgezogen werden. Wenn Sie tatsächlich Ressourcen außerhalb der .NET-Plattform verwalten müssen, empfiehlt es sich, die Dokumentation von Dispose() zu lesen.


3.6 Zugriffsattribute

Datenkapselung

Die in diesem Kapitel entwickelte Klasse Favorite wurde bisher noch nicht in einem Programm verwendet. Sie wurde noch nicht instantiiert, um mit einem Objekt vom Typ Favorite zu arbeiten. Dies wird sich nun ändern.

Damit die Klasse Favorite instantiiert werden kann, muss dem Konstruktor das Zugriffsattribut public vorangestellt werden.

using System; 
using System.Diagnostics; 

class Favorite 
{ 
  string name; 
  Uri url; 

  public Favorite(string name, Uri url) 
  { 
    this.name = name; 
    this.url = url; 
  } 

  void Open() 
  { 
    Process.Start(url.ToString()); 
  } 
} 

class Program 
{ 
  static void Main(string[] args) 
  { 
    var favHighscore = new Favorite("Highscore - Programmieren lernen", new Uri("http://www.highscore.de/")); 
  } 
} 

Zugriffsattribute legen fest, wer auf welche Merkmale eines Objekts zugreifen darf. Wenn so wie bisher Merkmalen kein Zugriffsattribut vorangestellt wird, gilt automatisch private. private bedeutet, dass die entsprechenden Merkmale nur innerhalb der Klasse verfügbar sind. Von außerhalb der Klasse kann auf private Merkmale nicht zugegriffen werden. Um ein Merkmal für andere Klassen sichtbar zu machen, muss das Zugriffsattribut public verwendet werden.

Im obigen Beispielcode wurde dem Konstruktor public vorangestellt. Das ist zwingend notwendig, damit Favorite in der Klasse Program instantiiert werden kann. Da bei einer Instantiierung immer ein Konstruktor aufgerufen wird, muss der Konstruktor öffentlich sein.

Obiges Programm kann kompiliert und ausgeführt werden. Es wird ein Objekt vom Typ Favorite erstellt, das mit "Highscore - Programmieren lernen" und der Adresse http://www.highscore.de/ initialisiert wird.

Im Folgenden soll das Programm derart erweitert werden, dass die entsprechende Adresse im Browser geöffnet wird. Dazu ist es notwendig, auch die Methode Open() öffentlich zu machen, damit sie von außerhalb der Klasse Favorite aufgerufen werden kann.

using System; 
using System.Diagnostics; 

class Favorite 
{ 
  private string name; 
  private Uri url; 

  public Favorite(string name, Uri url) 
  { 
    this.name = name; 
    this.url = url; 
  } 

  public void Open() 
  { 
    Process.Start(url.ToString()); 
  } 
} 

class Program 
{ 
  static void Main(string[] args) 
  { 
    var favHighscore = new Favorite("Highscore - Programmieren lernen", new Uri("http://www.highscore.de/")); 
    favHighscore.Open(); 
  } 
} 

Wenn Sie obiges Programm ausführen, wird der Standardbrowser gestartet und http://www.highscore.de/ geöffnet.

Wie Sie anhand des obigen Beispiels sehen, ist es möglich, das Zugriffsattribut private explizit zu verwenden. So wurde hier private vor die beiden Variablen name und url gesetzt.

Neben private und public gibt es drei weitere Zugriffsattribute: protected wird Ihnen im Kapitel 4, Klassenhierarchien vorgestellt, für internal und internal protected wird auf die Dokumentation der Zugriffsmodifizierer in Microsofts C#-Programmierhandbuch verwiesen. Für den Anfang und in diesem Buch sind die Zugriffsattribute internal und internal protected unwichtig.


3.7 Eigenschaften

Zugriffsmethoden für Felder

Die bisher entwickelte Klasse Favorite besitzt zwei öffentliche Methoden - einen Konstruktor und eine Methode namens Open() - und zwei private Felder. Sie stellt somit ein Beispiel für ein Prinzip dar, das als Datenkapselung bekannt ist.

Unter Datenkapselung versteht man, dass Zugriffsattribute so gesetzt werden, dass Felder nie von außerhalb der Klasse verwendet werden können. Felder sollten also immer privat sein, so dass sie nur innerhalb einer Klasse sichtbar sind. Diese Empfehlung hat zwei Gründe:

  • Wenn Felder privat sind und von außerhalb einer Klasse nicht direkt auf Felder zugegriffen werden kann, können Methoden definiert werden, die es anderen Klassen erlauben, kontrolliert Felder zu schreiben und zu lesen. So kann eine Methode zum Beispiel überprüfen, ob ein Wert, der in ein Feld abgelegt werden soll, für die entsprechende Klasse Sinn macht. Auf diese Weise kann Fehlern vorgebeugt werden.

  • Wenn dennoch ein Fehler auftritt und Sie feststellen, dass ein Feld einen ungültigen Wert speichert, und Sie sich wundern, wo dieser unerwartete Wert herkommt, müssen Sie nur den Code nach dem Fehler durchsuchen, der auch tatsächlich auf das Feld zugreifen kann. Wenn das Feld privat ist und daher nur innerhalb einer Klasse verwendet werden kann, wissen Sie, dass der Fehler in genau dieser Klasse sein muss. Egal, aus wie vielen Klassen Ihr Programm besteht - Sie können alle anderen Klassen bei der Fehlersuche ignorieren.

Im Folgenden soll die Klasse Favorite so geändert werden, dass der gespeicherte Name und die gespeicherte Adresse von außerhalb gelesen werden können. So könnten beide Felder zum Beispiel öffentlich gemacht werden, um anderen Klassen einen direkten Zugriff zu gestatten.

using System; 
using System.Diagnostics; 

class Favorite 
{ 
  public string name; 
  public Uri url; 

  public Favorite(string name, Uri url) 
  { 
    this.name = name; 
    this.url = url; 
  } 

  public void Open() 
  { 
    Process.Start(url.ToString()); 
  } 
} 

class Program 
{ 
  static void Main(string[] args) 
  { 
    var favHighscore = new Favorite("Highscore - Programmieren lernen", new Uri("http://www.highscore.de/")); 
    Console.Out.WriteLine(favHighscore.name); 
    Console.Out.WriteLine(favHighscore.url); 
  } 
} 

Die Klasse Program kann nun auf die Felder name und url der Klasse Favorite zugreifen und sie auf die Standardausgabe ausgeben. Das funktioniert, verletzt aber das Prinzip der Datenkapselung. So können die Felder nun nicht nur gelesen, sondern auch geschrieben werden. Sehen Sie sich folgendes Beispiel an, in dem das Feld url auf null gesetzt wird, bevor Open() aufgerufen wird.

using System; 
using System.Diagnostics; 

class Favorite 
{ 
  public string name; 
  public Uri url; 

  public Favorite(string name, Uri url) 
  { 
    this.name = name; 
    this.url = url; 
  } 

  public void Open() 
  { 
    Process.Start(url.ToString()); 
  } 
} 

class Program 
{ 
  static void Main(string[] args) 
  { 
    var favHighscore = new Favorite("Highscore - Programmieren lernen", new Uri("http://www.highscore.de/")); 
    favHighscore.url = null; 
    favHighscore.Open(); 
  } 
} 

Wenn Sie obiges Programm ausführen, stürzt es ab. Denn Open() versucht, über die Referenzvariable url auf ein Objekt zuzugreifen, obwohl url auf null gesetzt ist und somit auf gar kein Objekt zeigt. In einem kleinen Programm wie obigen ist der Fehler leicht zu vermeiden. Je größer das Programm und je mehr Klassen aufeinander zugreifen, umso schwieriger wird dies jedoch.

Damit der Name und die Adresse nur gelesen, aber nicht geschrieben werden können, bietet es sich an, entsprechende Methoden zu definieren.

using System; 
using System.Diagnostics; 

class Favorite 
{ 
  string name; 
  Uri url; 

  public Favorite(string name, Uri url) 
  { 
    this.name = name; 
    this.url = url; 
  } 

  public string GetName() 
  { 
    return name; 
  } 

  public Uri GetURL() 
  { 
    return url; 
  } 

  public void Open() 
  { 
    Process.Start(url.ToString()); 
  } 
} 

class Program 
{ 
  static void Main(string[] args) 
  { 
    var favHighscore = new Favorite("Highscore - Programmieren lernen", new Uri("http://www.highscore.de/")); 
    Console.Out.WriteLine(favHighscore.GetName()); 
    Console.Out.WriteLine(favHighscore.GetURL()); 
  } 
} 

Die beiden Felder name und url sind wieder privat. Um sie auszulesen, können die Methoden GetName() und GetURL() aufgerufen werden, die den Namen und die Adresse zurückgeben. Auf diese Weise kann die Klasse Favorite es anderen Klassen gefahrlos ermöglichen, die Werte zu lesen.

Weil es sehr häufig notwendig sein kann, dass andere Klassen einen kontrollierten Zugriff auf Felder erhalten, bietet C# Eigenschaften an, mit denen der kontrollierte Zugriff etwas einfacher eingeräumt werden kann als über die Definition von Methoden.

using System; 
using System.Diagnostics; 

class Favorite 
{ 
  string name; 
  Uri url; 

  public Favorite(string name, Uri url) 
  { 
    this.name = name; 
    this.url = url; 
  } 

  public string Name 
  { 
    get { return name; } 
  } 

  public Uri URL 
  { 
    get { return url; } 
  } 

  public void Open() 
  { 
    Process.Start(url.ToString()); 
  } 
} 

class Program 
{ 
  static void Main(string[] args) 
  { 
    var favHighscore = new Favorite("Highscore - Programmieren lernen", new Uri("http://www.highscore.de/")); 
    Console.Out.WriteLine(favHighscore.Name); 
    Console.Out.WriteLine(favHighscore.URL); 
  } 
} 

Eigenschaften werden definiert, indem hinter etwas, was wie eine Felddefinition aussieht, ein Paar geschweifte Klammern gesetzt wird. Während string Name; also ein Feld ist, ist string Name{} eine Eigenschaft.

In den geschweiften Klammern muss mindestens eines der beiden Schlüsselwörter set oder get verwendet werden - wieder gefolgt von einem Paar geschweifter Klammern. In den geschweiften Klammern hinter einem get muss mit return ein Wert zurückgegeben werden.

set und get werden Accessoren genannt. Genaugenommen handelt es sich um Methoden, die bei einem schreibenden oder lesenden Zugriff auf eine Eigenschaft ausgeführt werden.

Bei einem Zugriff auf die Eigenschaften Name und URL werden der Name und die Adresse zurückgegeben, die in den Feldern name und url gespeichert sind. Da die Eigenschaften lediglich ein get und kein set besitzen, können sie gelesen, aber nicht geschrieben werden.

Im folgenden Beispiel wird die Eigenschaft Name erweitert, um sie auch schreiben zu können.

using System; 
using System.Diagnostics; 

class Favorite 
{ 
  string name; 
  Uri url; 

  public Favorite(string name, Uri url) 
  { 
    this.name = name; 
    this.url = url; 
  } 

  public string Name 
  { 
    get { return name; } 
    set { name = value; } 
  } 

  public Uri URL 
  { 
    get { return url; } 
  } 

  public void Open() 
  { 
    Process.Start(url.ToString()); 
  } 
} 

class Program 
{ 
  static void Main(string[] args) 
  { 
    var favHighscore = new Favorite("Highscore - Programmieren lernen", new Uri("http://www.highscore.de/")); 
    favHighscore.Name = "Meine Lieblingswebsite Highscore"; 
    Console.Out.WriteLine(favHighscore.Name); 
    Console.Out.WriteLine(favHighscore.URL); 
  } 
} 

In den geschweiften Klammern hinter set muss das Schlüsselwort value verwendet werden, um den Wert weiterzuverarbeiten, der in die Eigenschaft geschrieben wurde. So wird im obigen Programm auf diese Weise der neue Name "Meine Lieblingswebsite Highscore" im Feld name gespeichert. Während es also nun möglich ist, den Namen zu lesen und zu schreiben, kann die Adresse weiterhin nur gelesen werden.


3.8 Statische Methoden, Felder und Eigenschaften

Zugriff auf Felder und Methoden ohne Objekte

Sie haben in diesem Kapitel bereits viel über den Umgang mit Feldern und Methoden gelernt. Ein Schlüsselwort, das in allen Beispielprogrammen bisher auftauchte, ist Ihnen aber noch unbekannt: static. Es muss vor die Methode Main() gesetzt werden, die den Einstiegspunkt einer Anwendung darstellt - ansonsten kann die Anwendung nicht gestartet werden. Genaugenommen weigert sich Visual C# sogar, den Code zu kompilieren und die Anwendung zu erstellen.

Der Grund, warum Main() statisch sein muss, ist, dass Main() direkt über die Klasse aufgerufen wird, in der die Methode definiert ist. Es wird demnach kein Objekt von der entsprechenden Klasse erstellt. Der entsprechende Methodenaufruf sieht wie folgt aus:

Program.Main(args); 

Vergleichen Sie dies mit einer Instantiierung und einem anschließenden Aufruf von Main() über das entsprechende Objekt:

var p = new Program(); 
p.Main(args); 

Ausschließlich statische Methoden können direkt über eine Klasse aufgerufen werden. Für alle anderen Methoden, die nicht statisch sind, gilt, dass erst ein Objekt erstellt werden muss, damit diese Methoden aufgerufen werden können.

Der Grund, warum Main() statisch sein muss, liegt ganz einfach darin, dass beim Start des Programms Main() direkt über die Klasse aufgerufen wird. Weil Microsoft sich für diese Vorgehensweise entschieden hat, müssen Sie Main() statisch machen.

Welchen Zweck haben statische Methoden? Sie werden verwendet, wenn es keinen Sinn macht, mehrere Objekte vom Typ einer Klasse zu erstellen. Wenn Sie eine Klasse Program definieren, die Ihr Programm beschreibt und eine Methode Main() besitzt, dann beschreibt diese Klasse natürlich genau das Programm, in dem sie verwendet wird. Es macht keinen Sinn, die Klasse Program zu instantiieren und mehrere Objekte vom Typ Program zu verwenden. Die Klasse Program bezieht sich ja lediglich auf genau das eine Programm, in dem sie verwendet wird.

Sie hatten bereits in diesem Kapitel mit statischen Methoden zu tun, ohne dass Sie wussten, dass sie statisch sind.

using System; 
using System.Diagnostics; 

class Favorite 
{ 
  string name; 
  Uri url; 

  void Open() 
  { 
    Process.Start(url.ToString()); 
  } 
} 

Der Aufruf der Methode Start() über die Klasse Process ist nur deswegen möglich, weil Start() statisch ist. Wäre Start() nicht statisch, wären Sie gezwungen, zuerst ein Objekt vom Typ Process zu erstellen, bevor Sie diese Methode aufrufen könnten. Da Objekte vom Typ Process aber momentan laufende Anwendungen darstellen, soll erst durch einen erfolgreichen Aufruf von Start() ein Objekt vom Typ Process erstellt werden. Deswegen hat Microsoft diese Methode statisch gemacht.

Neben statischen Methoden gibt es auch statische Felder und Eigenschaften. Diese werden definiert, indem ebenfalls das Schlüsselwort static vor diese gesetzt wird.

Sie haben auch bereits auf statische Eigenschaften zugegriffen. Denken Sie zurück an das Programmbeispiel aus dem Einführungskapitel.

using System; 

class Program 
{ 
  static void Main(string[] args) 
  { 
    Console.Out.WriteLine("Hallo, Welt!"); 
  } 
} 

Im obigen Beispiel wird über die Klasse Console auf eine Eigenschaft namens Out zugegriffen. Das funktioniert, weil Out statisch ist. Wäre Out nicht statisch, müssten Sie mit new zuerst ein Objekt vom Typ Console erstellen. Weil es nur eine einzige Konsole mit nur einer einzigen Standardausgabe gibt, hat sich Microsoft entschieden, Out statisch zu machen.

Weil es auch keinen Sinn macht, ein oder mehrere Objekte vom Typ Console zu erstellen, ist Console als sogenannte statische Klasse definiert worden. Um eine Klasse statisch zu machen, muss das Schlüsselwort static vor class gesetzt werden. Dadurch wird verhindert, dass die Klasse instantiiert werden kann.


3.9 Zusammenfassung

In den Tiefen der Objektorientierung

Sie haben in diesem Kapitel wichtige Details rund um die objektorientierte Softwareentwicklung mit C# kennengelernt. Felder, Eigenschaften und Methoden werden von Ihnen in jedem C#-Programm verwendet. Auch Zugriffsattribute sind entscheidend, um die Übersicht in größeren Softwareprogrammen mit vielen Klassen nicht zu verlieren. Das Schlüsselwort static und seine Bedeutung sollte ebenfalls jeder C#-Entwickler kennen.


3.10 Aufgaben

Übung macht den Meister

Sie können die Lösungen zu allen Aufgaben in diesem Buch als ZIP-Datei erwerben.

  1. Erstellen Sie eine Konsolenanwendung, die den Anwender auffordert, eine bestimmte Zahl an Favoriten zu speichern. Die Zahl der zu speichernden Favoriten soll dabei als Kommandozeilenparameter übergeben werden. Speichern Sie dann die Eingaben, die aus einem Namen und einer WWW-Adresse bestehen, in einem Array vom Typ der in diesem Kapitel entwickelten Klasse Favorite. Anschließend soll der Anwender den Index des entsprechenden Favoriten angegeben, der im Browser geöffnet werden soll.

    Greifen Sie auf den Parameter von Main() zu, der typischerweise args heißt, um Kommandozeilenparameter zu verarbeiten. Da der Parameter ein string-Array ist, Sie jedoch eine Ganzzahl vom Typ int benötigen, um ein Favorite-Array zu erstellen, müssen Sie die Zahl im entsprechenden String interpretieren. Sie können dazu die statische Methode Parse() der Struktur Int32 verwenden.

    Greifen Sie dann in einer Schleife mehrfach auf die Konsole zu, um den Anwender zur Eingabe des Namens und der WWW-Adresse aufzufordern. Beachten Sie, dass Sie keine foreach-Schleife verwenden können, um Favoriten im Array zu speichern, da in einer foreach-Schleife nur ein lesender Zugriff auf ein Array erfolgen kann.

  2. Ändern Sie die Klasse Favorite in Ihrer Lösung zur Aufgabe 1 dahingehend, dass Sie den Konstruktor privat machen. Fügen Sie der Klasse dann eine statische und öffentliche Methode hinzu, die verwendet werden kann, um Objekte vom Typ Favorite zu erstellen. Passen Sie das restliche Programm an, damit es so funktioniert wie Ihre Lösung zur Aufgabe 1.

    Wenn die Konstruktoren einer Klasse privat sind, können Objekte nur in einer Methode der Klasse erstellt werden. Denn nur diese Methoden haben Zugriff auf private Konstruktoren. Indem Sie eine statische Methode erstellen, in der mit new Objekte vom Typ Favorite erstellt werden, können andere Klassen über diese Methode eine Instantiierung vornehmen.

  3. Fügen Sie der Klasse Favorite in Ihrer Lösung zur Aufgabe 1 eine Eigenschaft Rating hinzu, über die ein Anwender angeben kann, wie wichtig ihm der entsprechende Favorit ist. Die Eigenschaft soll dabei auf eine Ganzzahl gesetzt werden können, wobei eine höhere Zahl wichtige Favoriten kennzeichnet. Erweitern Sie die Klasse Favorite um einen neuen Konstruktor, der einen Rating-Wert als zusätzlichen Parameter erwartet. Wenn das Programm den Anwender auffordert, Favoriten einzugeben, soll dieser neue Konstruktor zum Einsatz kommen, um nicht nur eine WWW-Adresse und einen Namen zu speichern, sondern auch ein Rating.

    Sie müssen der Klasse Favorite nicht nur eine Eigenschaft hinzufügen, sondern auch ein Feld. Denn irgendwo muss das Rating gespeichert werden. Fügen Sie dazu Favorite zum Beispiel eine Variable rating vom Typ int hinzu. Der neue Konstruktor muss als zusätzlichen Parameter dann ebenfalls einen int-Wert erwarten, mit dem das Feld initialisiert wird. Ändern Sie die Methode Main(), mit der Ihr Programm startet, erst dann, wenn Sie die Klasse Favorite aktualisiert haben.

  4. Ändern Sie Ihre Lösung zur Aufgabe 3 dahingehend, dass der Anwender nach Eingabe der Favoriten nicht mehr gefragt wird, welcher Favorit im Browser geöffnet werden soll, sondern alle Favoriten auf die Konsole ausgegeben werden, deren Rating größer oder gleich 5 ist. Dabei soll sowohl die WWW-Adresse, der Name und das Rating ausgegeben werden.

    Greifen Sie zum Beispiel in einer foreach-Schleife auf alle Favoriten im Array zu und überprüfen Sie mit Hilfe von if, ob der Rating-Wert eines Favoriten größer oder gleich 5 ist. Nur für diesen Fall geben Sie den Favoriten auf die Konsole aus.