Klassen und Strukturen
Was sind Klassen?
Da C# eine objektorientierte Sprache ist, wird am häufigsten das Konzept der Klasse zur
Erstellung eigener Typen verwendet. Klassen sind, wie bereits erwähnt, Baupläne für
Objekte, mit denen Daten modelliert werden können.
Eine Klasse wird in C# mit dem Schlüsselwort
class erzeugt, dem der Name der Klasse
folgt. Klassen besitzen ebenso wie Namensräume einen durch geschweifte Klammern
eingeschlossenen Rumpf, weshalb ihre Definition nicht durch ein Semikolon abgeschlossen
wird.
Wie bei Namensräumen, so gibt es auch bei Klassen Richtlinien, wie deren Namen gebildet
werden. Ein Klassenname besteht aus einem oder mehreren Substantiven, die den Zweck der
Klasse beschreiben, wobei in der Regel der Singular verwendet wird. Für die Schreibweise
gilt Pascal Case.
| C# |
1
2
3
4
5
6
7
8
|
using System;
namespace GoloRoden.GuideToCSharp
{
class ComplexNumber
{
}
}
|
Dieser Code erzeugt eine Klasse zur Darstellung komplexer Zahlen. Komplexe Zahlen
zeichnen sich in der Mathematik dadurch aus, dass mit ihnen die Wurzel von -1
berechnet werden kann, was unter Verwendung lediglich reeller Zahlen nicht möglich
ist. Die Wurzel aus -1 wird dabei mit der imaginären Einheit i bezeichnet, wobei
i² = -1
gilt. Komplexe Zahlen werden in der Regel in der Form
a + b × i
dargestellt, wobei a als der Real- und b als der Imaginärteil bezeichnet werden.
Bevor die Klasse ausgebaut wird, um komplexe Zahlen darstellen und verarbeiten zu
können, sollte sie zunächst kommentiert werden.
Prinzipiell gibt es in C# drei Arten von Kommentaren. Die einfachste Variante
stellen einzeilige Kommentare dar, die durch einen doppelten Schrägstrich
eingeleitet werden, und an einer beliebigen Stelle einer Zeile beginnen können,
wobei für einen Kommentar in der Regel eine neue Zeile verwendet wird, um die
Übersichtlichkeit zu bewahren.
Einzeilige Kommentare werden im wesentlichen für interne Kommentare des Entwicklers
verwendet und kennzeichnen häufig Zeilen im Code, an denen die Arbeit noch nicht
abgeschlossen ist.
Außerdem werden einzeilige Kommentare oft verwendet, um die Arbeitsweise von Code zu
erläutern, so dass dies auch nach Wochen oder Monaten noch nachvollzogen werden kann,
ohne dass eine mühsame Analyse und Einarbeitung erforderlich wäre.
Generell ist es beim Einfügen von Kommentaren ratsam, diese mit einem Datum zu versehen.
Vor allem in Teams wird dies zudem häufig durch ein Namenskürzel ergänzt, was Nachfragen
erleichtert. Daher wird es im allgemeinen als guter Stil angesehen, wenn Code derart
kommentiert wird.
In welcher Sprache kommentiert wird, ist prinzipiell beliebig, allerdings wird oft
auf Englisch zurückgegriffen, unter anderem, um in mehrsprachigen Teams über eine
einheitliche Kommunikationssprache zu verfügen.
| C# |
1
2
3
4
5
6
7
8
9
10
|
using System;
namespace GoloRoden.GuideToCSharp
{
class ComplexNumber
{
// TODO gr: Add code here.
// 2007-04-08
}
}
|
Bei umfangreicheren Kommentaren kann es lästig sein, jede Zeile einzeln durch einen
doppelten Schrägstrich einleiten zu müssen. Daher gibt es die zweite Variante von
Kommentaren, sogenannte Blockkommentare, die durch einen Schrägstrich gefolgt von einem
Stern eingeleitet werden und erst dann enden, wenn sie durch einen Stern gefolgt von
einem Schrägstrich wieder geschlossen werden.
Wie viele Zeilen sich innerhalb eines solchen Blockkommentars befinden, ist dabei
beliebig. Das Anwendungsgebiet von Blockkommentaren ist dabei aber das gleiche wie das
von einzeiligen Kommentaren. Generell gilt für Kommentare, dass sie keine Anweisungen
darstellen und daher nicht mit einem Semikolon abgeschlossen werden müssen.
| C# |
1
2
3
4
5
6
7
8
9
10
|
using System;
namespace GoloRoden.GuideToCSharp
{
class ComplexNumber
{
/* TODO gr: Add code here.
2007-04-08 */
}
}
|
Beiden Typen von Kommentaren ist gemein, dass sie nur für den internen Gebrauch gedacht
sind. Gerade bei Komponenten, die auch von anderen Entwicklern genutzt werden, ist jedoch
eine Trennung in private und öffentliche Kommentare sinnvoll. Die privaten Kommentare
werden dabei weiterhin dafür genutzt, den Code mit internen Anmerkungen zu versehen, die
öffentlichen Kommentare dienen hingegen als Dokumentation.
Zur Erstellung dieser Dokumentation dient die dritte Variante, die durch drei Schrägstriche
eingeleitet wird und durch XML formatiert werden kann, weshalb diese Kommentare gelegentlich
auch als XML-Kommentare bezeichnet werden. In diesen Kommentaren werden im Gegensatz zu den
anderen Typen weder Datum noch Namenskürzel angegeben.
Außerdem können XML-Kommentare nicht an beliebiger Stelle im Code auftreten, sondern müssen
direkt vor dem zu kommentierenden Element stehen. Die Beschreibung einer Klasse wird durch
die XML-Elemente <summary> und </summary> eingeschlossen.
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
13
|
using System;
namespace GoloRoden.GuideToCSharp
{
/// <summary>
/// Represents a complex number.
/// </summary>
class ComplexNumber
{
// TODO gr: Add code here.
// 2007-04-08
}
}
|
Für jede Klasse muss entschieden werden, ob sie nur innerhalb der Assembly genutzt
werden können soll, welche die Klasse enthält, oder ob jede Komponente der Anwendung
Zugriff erhalten soll. Die Antwort auf die Frage, ob es sinnvoll ist, die Sichtbarkeit
einzuschränken, hängt vom Zweck der Klasse ab.
Handelt es sich um eine unterstützende Klasse, die nur innerhalb der Assembly benötigt
wird, empfiehlt es sich, die Sichtbarkeit einzuschränken. Ist die Klasse jedoch eine
tragende Datenstruktur, die der gesamten Anwendung zur Verfügung stehen soll, wird sie
uneingeschränkt zur Verfügung gestellt.
Um die Sichtbarkeit einer Klasse auf die sie enthaltende Assembly zu beschränken, wird
ihre Definition mit dem Zugriffsmodifizierer
internal versehen. Der öffentliche Zugriff
wird erreicht, indem statt dessen der Zugriffsmodifizierer
public angegeben wird. Wird
auf die Angabe eines solchen Zugriffsmodifizierers verzichtet, ist eine Klasse implizit
internal.
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
13
|
using System;
namespace GoloRoden.GuideToCSharp
{
/// <summary>
/// Represents a complex number.
/// </summary>
public class ComplexNumber
{
// TODO gr: Add code here.
// 2007-04-08
}
}
|
Aus Gründen der Übersichtlichkeit wird generell für jede Klasse eine eigene Datei
verwendet, deren Name dem der enthaltenen Klasse entspricht. Prinzipiell ist es zwar
möglich, innerhalb einer Datei mehrere Klassen zu definieren, dies ist jedoch unüblich
und gilt als schlechter Stil.
Zwei Ausnahmen von dieser Regel kommen in C# vor: Partielle Klassen und verschachtelte
Klassen. Partielle Klassen, die seit Version 2.0 von C# verfügbar sind, ermöglichen es
durch Angabe des zusätzlichen Schlüsselwortes
partial bei der Definition der Klasse, eine
Klasse auf mehrere Dateien zu verteilen.
Dies wird beispielsweise von Visual Studio genutzt, um vom Benutzer geschriebenen Code
und von Visual Studio generierten Code, der sich auf die gleiche Klasse bezieht,
voneinander zu trennen, so dass der Benutzer nicht versehentlich generierten Code
überschreibt oder verändert, und umgekehrt. Außer in Fällen, in denen generierter
und benutzergeschriebener Code gemischt werden, sollte vom Einsatz partieller Klassen
abgesehen werden.
Verschachtelte Klassen hingegen ermöglichen, innerhalb einer Klasse eine weitere Klasse
zu definieren, genau so, wie auch innerhalb eines Namensraumes ein weiterer Namensraum
angelegt werden kann. Im Gegensatz zu Namensräumen ist dies bei Klassen in der Praxis
jedoch unüblich, zudem gibt es so gut wie keine Anwendungsfälle, in denen ein solches
Verfahren notwendig wäre, weshalb darauf nicht näher eingegangen wird.
Felder
Damit ein Objekt Daten speichern kann, müssen in der zugehörigen Klasse Felder für die
einzelnen Daten definiert werden. Felder sollten nur für solche Daten definiert werden,
die nicht funktional abhängig von anderen Daten sind - das heißt, lassen sich Daten aus
anderen vorhandenen Daten ermitteln, werden sie nicht abgespeichert.
Um eine komplexe Zahl mit der Klasse ComplexNumber abbilden zu können, werden zwei Felder
benötigt, nämlich eines für den Real- und eines für den Imaginärteil. Die Frage, von welchem
Typ diese Felder sind, ist einfach zu beantworten: Da so wohl Real- wie auch Imaginärteil
nach Definition reelle Zahlen sind, werden beide mit Hilfe eines Typs für Dezimalzahlen
dargestellt.
Die Bennenung von Feldern erfolgt ähnlich wie die von Klassen, da auch hier der Name aus
einem oder mehreren Substantiven gebildet wird und als Gesamtbegriff im Singular steht.
Allerdings wird für Felder Camel Case eingesetzt, zudem wird den Namen häufig ein Unterstrich
vorangestellt.
Da die Definition eines Feldes in C# eine Anweisung darstellt, wird sie mit einem Semikolon
abgeschlossen. Zudem werden auch Felder mit Hilfe von XML-Kommentaren dokumentiert, wobei auch
hier wieder das <summary>-Tag zum Einsatz kommt.
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
using System;
namespace GoloRoden.GuideToCSharp
{
/// <summary>
/// Represents a complex number.
/// </summary>
public class ComplexNumber
{
/// <summary>
/// Contains the real part.
/// </summary>
float _realPart;
/// <summary>
/// Contains the imaginary part.
/// </summary>
float _imaginaryPart;
// TODO gr: Add code here.
// 2007-04-08
}
}
|
Ebenso wie für Klassen, so muss auch für Felder die Sichtbarkeit entschieden werden.
Außer
internal und
public, welche die gleiche Bedeutung wie bei Klassen haben, steht
für Felder zusätzlich noch das Schlüsselwort
private zur Verfügung. Wird ein Feld als
private gekennzeichnet, kann nur aus der Klasse auf das Feld zugegriffen werden, die
das Feld enthält.
Im Sinne eines durchgängig objektorientierten Aufbaus einer Anwendung ist es allerdings
erforderlich, quasi jedes Feld als
private zu markieren, um den direkten Zugriff von
außen zu verhindern.
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
using System;
namespace GoloRoden.GuideToCSharp
{
/// <summary>
/// Represents a complex number.
/// </summary>
public class ComplexNumber
{
/// <summary>
/// Contains the real part.
/// </summary>
private float _realPart;
/// <summary>
/// Contains the imaginary part.
/// </summary>
private float _imaginaryPart;
// TODO gr: Add code here.
// 2007-04-08
}
}
|
Felder können mit Standardwerten versehen werden, indem ihnen bei der Definition der
gewünschte Wert zugewiesen wird, wobei dies in der Praxis eher selten angewandt wird,
weswegen im folgenden in der Regel darauf verzichtet wird.
Genau genommen wird bei Feldern zwischen Deklaration und Definition unterschieden -
während das Feld bei der Deklaration nur der Klasse hinzugefügt wird, wird ihm bei der
Definition zusätzlich noch ein Wert zugewiesen. In der Regel wird diese Unterscheidung
allerdings nur selten genutzt und generell von Definition gesprochen.
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
using System;
namespace GoloRoden.GuideToCSharp
{
/// <summary>
/// Represents a complex number.
/// </summary>
public class ComplexNumber
{
/// <summary>
/// Contains the real part.
/// </summary>
private float _realPart = 0;
/// <summary>
/// Contains the imaginary part.
/// </summary>
private float _imaginaryPart = 0;
// TODO gr: Add code here.
// 2007-04-08
}
}
|
Die einzige Ausnahme zu dieser Regel besteht in der Definition eines Feldes mit konstantem
Wert, wobei dessen Typ das Schlüsselwort
const vorangestellt wird. Der Wert eines solchen
konstanten Feldes kann im weiteren Verlauf der Anwendung dann nicht mehr geändert werden.
Konstanten werden beispielsweise genutzt, um mathematisch feststehende Werte wie die Zahl
Pi oder die Eulersche Zahl zu definieren.
| C# |
1
|
private const double _pi = 3.1415926;
|
Eigenschaften
Da der Zugriff auf Felder, die als
private gekennzeichnet wurden, nur noch
innerhalb der Klasse möglich ist, stellt sich die Frage, wie Daten eines Objektes
überhaupt gelesen oder geschrieben werden können, ohne die Sichtbarkeit des
entsprechenden Feldes wieder auf
internal oder
public ändern zu müssen.
Die Lösung stellen Eigenschaften dar, die zwar nach außen sichtbar sind, aber auf
die Felder einer Klasse zugreifen können. Der Unterschied zwischen dem Zugriff auf
ein Feld mit Hilfe einer Eigenschaft und dem direkten Zugriff liegt darin, dass die
Eigenschaft zusätzliche Prüfungen ausführen kann.
Eine Eigenschaft trägt üblicherweise den gleichen Namen wie das Feld, für das die
Eigenschaft zuständig ist. Der einzige Unterschied liegt darin, dass Pascal Case an
Stelle von Camel Case verwendet wird und der einleitende Unterstrich entfällt. Zudem
ist eine Eigenschaft in der Regel
internal oder
public, da sie ansonsten von außen nicht
sichtbar wäre - dennoch können Eigenschaften theoretisch auch als
private gekennzeichnet
werden.
Da eine Eigenschaft den Zugriff auf ein Feld gestattet, muss sie über den gleichen Typ
verfügen. Der Zugriff an sich erfolgt über zwei Schlüsselwörter,
get und
set, die für das
Auslesen und Schreiben der entsprechenden Daten zuständig sind.
Die einfachste Variante einer Eigenschaft besteht darin, mit
get lediglich ein Feld
zurückzugeben, ohne weitere Prüfungen auszuführen. Dies geschieht mit Hilfe der Anweisung
return, der das zurückzugebende Feld folgt. Ebenso wird mit
set nur der zu setzende
Wert in das entsprechende Feld geschrieben. Der zu setzende Wert befindet sich dabei in
einem Parameter namens
value und kann mit Hilfe des Zuweisungsoperators geschrieben
werden.
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
using System;
namespace GoloRoden.GuideToCSharp
{
/// <summary>
/// Represents a complex number.
/// </summary>
public class ComplexNumber
{
/// <summary>
/// Contains the real part.
/// </summary>
private float _realPart;
/// <summary>
/// Contains the imaginary part.
/// </summary>
private float _imaginaryPart;
public float RealPart
{
get
{
return _realPart;
}
set
{
_realPart = value;
}
}
public float ImginaryPart
{
get
{
return _imaginaryPart;
}
set
{
_imaginaryPart = value;
}
}
// TODO gr: Add code here.
// 2007-04-08
}
}
|
Zudem ist es mit Eigenschaften möglich, ein Feld nur für den Lese- oder nur für den
Schreibzugriff freizugeben, indem nur entweder
get oder
set definiert wird. Außerdem kann
seit C# 2.0 entweder
get oder
set ein stärker einschränkender Zugriffsmodifizierer zugewiesen
werden, um beispielsweise den schreibenden Zugriff auf die Klassenebene zu beschränken,
den lesenden Zugriff aber auf Anwendungsebene zu gestatten.
Dazu wird entweder
get oder
set ein entsprechender Zugriffsmodifizierer wie
internal
oder
private vorangestellt. Hierbei muss allerdings beachtet werden, dass dies nur
erlaubt ist, wenn eine Eigenschaft so wohl über
get wie auch
set verfügt, und selbst dann
darf ein weiterer Zugriffsmodifizierer nur bei einem der beiden Schlüsselwörter angegeben
werden. Das andere behält den Zugriffsmodifizierer, der für die Eigenschaft an sich
definiert ist.
Zudem muss der Zugriffsmodifizierer, der
get oder
set vorangestellt wird, restriktiver
sein als der Zugriffsmodifizierer der gesamten Eigenschaft. Wenn also beispielsweise der
schreibende Zugriff auf den Realteil einer komplexen Zahl auf die Klasse beschränkt
werden soll, muss dem
set ein
private vorangestellt werden.
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
|
public float RealPart
{
get
{
return _realPart;
}
private set
{
_realPart = value;
}
}
|
Auch Eigenschaften werden mit Hilfe von XML-Kommentaren dokumentiert, wobei ein
Kommentar mit einem <summary>-Tag zum Einsatz kommt. Zusätzlich enthält der
Kommentar für eine Eigenschaft aber noch eine Beschreibung der Daten, die von der
Eigenschaft ausgelesen beziehungsweise gesetzt werden. Dies geschieht mit Hilfe des
<value>-Tags.
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
using System;
namespace GoloRoden.GuideToCSharp
{
/// <summary>
/// Represents a complex number.
/// </summary>
public class ComplexNumber
{
/// <summary>
/// Contains the real part.
/// </summary>
private float _realPart;
/// <summary>
/// Contains the imaginary part.
/// </summary>
private float _imaginaryPart;
/// <summary>
/// Gets or sets the real part.
/// </summary>
/// <value>The real part.</value>
public float RealPart
{
get
{
return _realPart;
}
set
{
_realPart = value;
}
}
/// <summary>
/// Gets or sets the imaginary part.
/// </summary>
/// <value>The imaginary part.</value>
public float ImginaryPart
{
get
{
return _imaginaryPart;
}
set
{
_imaginaryPart = value;
}
}
// TODO gr: Add code here.
// 2007-04-08
}
}
|
Als funktional abhängige Eigenschaft bietet sich der Betrag einer komplexen Zahl an,
der aus dem Real- und dem Imaginärteil ermittelt werden kann, indem beide quadriert und
addiert werden und aus dem Ergebnis die Wurzel gezogen wird.
|z| = sqrt(a2 + b2)
Da mathematische Operatoren noch nicht behandelt wurden, wird die entsprechende
Eigenschaft an dieser Stelle nur als Platzhalter eingefügt, wobei als Ergebnis vorerst
immer 0 zurückgegeben wird. Zusätzlich wird die Eigenschaft mit einem Kommentar
versehen, der darauf hinweist, dass die Arbeit an diesem Codeabschnitt noch nicht
abgeschlossen ist. Da lediglich das Auslesen des Betrages Sinn ergibt, wird für
diese Eigenschaft nur
get definiert, so dass ein schreibender
Zugriff nicht möglich ist.
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
|
using System;
namespace GoloRoden.GuideToCSharp
{
/// <summary>
/// Represents a complex number.
/// </summary>
public class ComplexNumber
{
/// <summary>
/// Contains the real part.
/// </summary>
private float _realPart;
/// <summary>
/// Contains the imaginary part.
/// </summary>
private float _imaginaryPart;
/// <summary>
/// Gets or sets the real part.
/// </summary>
/// <value>The real part.</value>
public float RealPart
{
get
{
return _realPart;
}
set
{
_realPart = value;
}
}
/// <summary>
/// Gets or sets the imaginary part.
/// </summary>
/// <value>The imaginary part.</value>
public float ImginaryPart
{
get
{
return _imaginaryPart;
}
set
{
_imaginaryPart = value;
}
}
/// <summary>
/// Gets the absolute value.
/// </summary>
/// <value>The absolute value.</value>
public float AbsoluteValue
{
get
{
// TODO gr: Calculate absolute value.
// 2007-04-08
return 0;
}
}
// TODO gr: Add code here.
// 2007-04-08
}
}
|
Obwohl es möglich ist, innerhalb von
get und
set
weitere Anweisungen unterzubringen, enthalten die meisten Eigenschaften lediglich die
Minimalvariante zum Lesen und Schreiben eines Feldes. Seit der Version 3.0 von C# gibt es
für solche Standardeigenschaften eine verkürzte Schreibweise, so dass an Stelle von
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
using System;
namespace GoloRoden.GuideToCSharp
{
/// <summary>
/// Represents a foo class.
/// </summary>
public class Foo
{
/// <summary>
/// Contains a bar value.
/// </summary>
private object _bar;
/// <summary>
/// Gets or sets the bar value.
/// </summary>
/// <value>The bar value.</value>
public object Bar
{
get
{
return this._bar;
}
set
{
this._bar = value;
}
}
}
}
|
auch die kürzere Variante
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
using System;
namespace GoloRoden.GuideToCSharp
{
/// <summary>
/// Represents a foo class.
/// </summary>
public class Foo
{
/// <summary>
/// Gets or sets the bar value.
/// </summary>
/// <value>The bar value.</value>
public object Bar
{
get;
set;
}
}
}
|
geschrieben werden kann. Semantisch sind beide Varianten identisch, allerdings stellt sich
bei der verkürzten Schreibweise die Frage, auf welches Feld mit Hilfe der Eigenschaft
zugegriffen wird. Die Antwort auf diese Frage lautet, dass C# intern ein Feld anlegt,
dessen Name dem Entwickler nicht bekannt ist, weshalb auf dieses Feld ausschließlich über
die Eigenschaft zugegriffen werden kann.
Methoden
Während Eigenschaften zwar geeignet sind, auf Felder lesend und schreibend zuzugreifen,
sind ihre Möglichkeiten, andere Aufgaben auszuführen, eher gering. Außerdem beziehen sich
Eigenschaften immer nur auf jeweils ein Feld, allerdings kann es vorkommen, dass mehrere
Werte verarbeitet werden müssen. Für diese Fälle, die über einen reinen Datenzugriff
hinausgehen, gibt es Methoden.
Eine Methode ist ein benannter Codeabschnitt, der über seinen Namen aufgerufen und
ausgeführt werden kann. Dabei können einer Methode mit Hilfe von Parametern Daten
übergeben werden, außerdem kann eine Methode über einen Rückgabewert verfügen. Parameter
dienen also der Eingabe von Daten, der Rückgabewert hingegen der Ausgabe von Daten, wobei
beide allerdings optional sind.
Eine einfache Methode verfügt weder über Parameter noch über einen Rückgabewert und
bezieht alle Daten, die zu ihrer Ausführung benötigt werden, aus der Klasse, welche die
Methode enthält.
Prinzipiell werden Parameter in einer kommagetrennten Liste an die Methode übergeben, die
in runden Klammern hinter dem Methodennamen angegeben wird. Werden keine Parameter
verwendet, so wird nur ein leeres Paar runder Klammern an den Methodennamen angehängt.
Der Rückgabewert wird hingegen vor dem Methodennamen notiert, indem der Typ des
Rückgabewertes angegeben wird. Wird kein Rückgabewert verwendet, wird dies mit dem
Schlüsselwort
void gekennzeichnet.
Als Methode ohne Parameter und Rückgabewert wird daher Conjugate eingeführt, welche die
Konjugation einer komplexen Zahl berechnet. Die Konjugation ergibt sich, indem das
Vorzeichen des Imaginärteils umgekehrt wird, so dass die Konjugation der komplexen Zahl
a + b × i
als
a - b × i
dargestellt wird. Da mathematische Operatoren an dieser Stelle noch nicht behandelt
wurden, wird die Methode nur als Platzhalter eingefügt, wobei sie vorerst über keine
Funktionalität verfügt.
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
using System;
namespace GoloRoden.GuideToCSharp
{
/// <summary>
/// Represents a complex number.
/// </summary>
public class ComplexNumber
{
/// <summary>
/// Gets or sets the real part.
/// </summary>
/// <value>The real part.</value>
public float RealPart
{
get;
set;
}
/// <summary>
/// Gets or sets the imaginary part.
/// </summary>
/// <value>The imaginary part.</value>
public float ImginaryPart
{
get;
set;
}
/// <summary>
/// Gets the absolute value.
/// </summary>
/// <value>The absolute value.</value>
public float AbsoluteValue
{
get
{
// TODO gr: Calculate the absolute value
// and return it to the caller.
// 2007-04-08
}
}
void Conjugate()
{
// TODO gr: Calculate the conjugation.
// 2007-04-09
}
// TODO gr: Add code here.
// 2007-04-08
}
}
|
Für die Namensgebung einer Methode gilt, dass der Name mit einem Verb beginnt, dem
Substantive folgen können, wobei für die Schreibweise Pascal Case verwendet wird. Auch
für eine Methode kann die Sichtbarkeit definiert werden, wobei die gleichen
Zugriffsmodifizierer wie bei Feldern zur Verfügung stehen.
In der Regel werden Methoden, die Hilfsaufgaben übernehmen, als private gekennzeichnet.
Methoden, welche die Schnittstelle einer Klasse nach außen darstellen, werden mit dem
gleichen Zugriffsmodifizierer wie die Klasse gekennzeichnet, also mit
internal oder
public, je nachdem, ob die Methode nur in der Assembly oder der gesamten Anwendung
benötigt wird. Wird kein Zugriffsmodifizierer angegeben, ist eine Methode implizit
private.
Methoden werden, da sie die Semantik einer Klasse definieren, ebenfalls mit
XML-Kommentaren versehen, wobei wiederum das <summary>-Tag zum Einsatz kommt.
Da eine Methode keine Anweisung ist, wird ihre Definition nicht mit einem Semikolon
abgeschlossen.
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
using System;
namespace GoloRoden.GuideToCSharp
{
/// <summary>
/// Represents a complex number.
/// </summary>
public class ComplexNumber
{
/// <summary>
/// Gets or sets the real part.
/// </summary>
/// <value>The real part.</value>
public float RealPart
{
get;
set;
}
/// <summary>
/// Gets or sets the imaginary part.
/// </summary>
/// <value>The imaginary part.</value>
public float ImginaryPart
{
get;
set;
}
/// <summary>
/// Gets the absolute value.
/// </summary>
/// <value>The absolute value.</value>
public float AbsoluteValue
{
get
{
// TODO gr: Calculate the absolute value
// and return it to the caller.
// 2007-04-08
}
}
/// <summary>
/// Calculates the conjugation.
/// </summary>
public void Conjugate()
{
// TODO gr: Calculate the conjugation.
// 2007-04-09
}
// TODO gr: Add code here.
// 2007-04-08
}
}
|
Wenn ein Rückgabewert für eine Methode benötigt wird, so kann er dadurch definiert
werden, dass sein Typ in der Definition der Methode an Stelle von
void angegeben wird.
Eine Methode, die überprüft, ob so wohl Real- wie auch Imaginärteil dem Zahlenwert Null
entsprechen - und damit die gesamte komplexe Zahl der komplexen Null entspricht - gibt
entweder
true oder
false zurück, womit sich als Typ des Rückgabewertes
bool ergibt.
Methoden, deren Rückgabewert
bool ist, folgen bei der Benennung einer weiteren
Richtlinie: Als Verb wird in der Regel is eingesetzt, so dass sich für den Test auf Null
der Name IsZero ergibt. Der Grund für diese Richtlinie ist, dass der Name einer solchen
Methode als logische Aussage gelesen werden kann.
Außerdem enthalten Methoden, die über einen Rückgabewert verfügen, als letzte Anweisung
ein
return, so dass in dieser Hinsicht eine gewisse Ähnlichkeit zu
get von Eigenschaften
besteht.
Sofern eine Methode über einen Rückgabewert verfügt, wird dieser gesondert von
<summary> in dem XML-Kommentar der Methode aufgeführt und durch das XML-Tag
<returns> gekennzeichnet. Um innerhalb der Dokumentation Schlüsselwörter als
solche hervorzuheben, können sie durch das XML-Tag <c> markiert werden.
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
|
using System;
namespace GoloRoden.GuideToCSharp
{
/// <summary>
/// Represents a complex number.
/// </summary>
public class ComplexNumber
{
/// <summary>
/// Gets or sets the real part.
/// </summary>
/// <value>The real part.</value>
public float RealPart
{
get;
set;
}
/// <summary>
/// Gets or sets the imaginary part.
/// </summary>
/// <value>The imaginary part.</value>
public float ImginaryPart
{
get;
set;
}
/// <summary>
/// Gets the absolute value.
/// </summary>
/// <value>The absolute value.</value>
public float AbsoluteValue
{
get
{
// TODO gr: Calculate the absolute value
// and return it to the caller.
// 2007-04-08
}
}
/// <summary>
/// Calculates the conjugation.
/// </summary>
public void Conjugate()
{
// TODO gr: Calculate the conjugation.
// 2007-04-09
}
/// <summary>
/// Checks whether the complex number is zero.
/// </summary>
/// <returns><c>true</c> if the real and the
/// imaginary part are zero; <c>false</c>
/// otherwise.<returns>
public bool IsZero()
{
// TODO gr: Check whether the real and the
// imaginary part are zero and return
// the result to the caller.
// 2007-04-09
}
// TODO gr: Add code here.
// 2007-04-08
}
}
|
Schließlich können Methoden auch Parameter enthalten, mit deren Hilfe Daten an eine
Methode bei ihrem Aufruf übergeben werden können. Wie bereits erwähnt, werden Parameter
in einer kommaseparierten Liste innerhalb der runden Klammern definiert. Im Gegensatz
zum Rückgabewert reicht es allerdings nicht aus, hierbei nur die Typen der Parameter
anzugeben, da sie sonst innerhalb der Methode nicht unterscheidbar wären.
Daher erhält jeder Parameter einen Namen, wobei dafür die Richtlinien der Namensgebung
von Feldern gelten, mit der Ausnahme, dass für die Schreibweise von Parametern Camel
Case verwendet wird. Als Beispiel bieten sich die Addition und die Multiplikation mit
einer weiteren komplexen Zahl und die Potenz mit einer reellen Zahl an. Alle drei
Methoden verfügen über keinen Rückgabewert, da das Ergebnis direkt in der komplexen
Zahl gespeichert wird.
Während den ersten beiden Methoden ein Objekt der Klasse ComplexNumber übergeben wird,
erwartet die Potenz eine Dezimalzahl als Parameter. Jeder Parameter wird, wie bereits
der Rückgabewert, durch einen entsprechenden XML-Kommentar beschrieben, der durch das
<param>-Tag gekennzeichnet wird. Innerhalb des öffnenden Tags befindet sich das
Attribut name, dem der Name des beschriebenen Parameters zugewiesen wird.
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
|
using System;
namespace GoloRoden.GuideToCSharp
{
/// <summary>
/// Represents a complex number.
/// </summary>
public class ComplexNumber
{
/// <summary>
/// Gets or sets the real part.
/// </summary>
/// <value>The real part.</value>
public float RealPart
{
get;
set;
}
/// <summary>
/// Gets or sets the imaginary part.
/// </summary>
/// <value>The imaginary part.</value>
public float ImginaryPart
{
get;
set;
}
/// <summary>
/// Gets the absolute value.
/// </summary>
/// <value>The absolute value.</value>
public float AbsoluteValue
{
get
{
// TODO gr: Calculate the absolute value
// and return it to the caller.
// 2007-04-08
}
}
/// <summary>
/// Calculates the conjugation.
/// </summary>
public void Conjugate()
{
// TODO gr: Calculate the conjugation.
// 2007-04-09
}
/// <summary>
/// Checks whether the complex number is zero.
/// </summary>
/// <returns><c>true</c> if the real and the
/// imaginary part are zero; <c>false</c>
/// otherwise.<returns>
public bool IsZero()
{
// TODO gr: Check whether the real and the
// imaginary part are zero and return
// the result to the caller.
// 2007-04-09
}
/// <summary>
/// Adds the specified summand to the current complex
/// number.
/// </summary>
/// <param name="summand">The complex number that is
/// used as summand.</param>
public void Add(ComplexNumber summand)
{
// TODO gr: Add the summand to the current complex
// number.
// 2007-04-09
}
/// <summary>
/// Multiplies the current complex number with the
/// specified factor.
/// </summary>
/// <param name="factor">The complex number that is
/// used as factor.</param>
public void Multiply(ComplexNumber factor)
{
// TODO gr: Multiply the factor with the current
// complex number.
// 2007-04-09
}
/// <summary>
/// Raises the current complex number to the power of
/// the specified real number.
/// </summary>
/// <param name="exponent">The real number that is
/// used as exponent.</param>
public void Pow(float exponent)
{
// TODO gr: Raise the current complex number to a
// power.
// 2007-04-09
}
// TODO gr: Add code here.
// 2007-04-08
}
}
|
Obwohl die Definitionen der Methoden Add, Multiply und Pow prinzipiell gleich aussehen,
unterscheiden sie sich in einem wesentlichen Aspekt: Die Parameter von Add und Multiply
sind vom Typ ComplexNumber - einem Verweistyp -, während der Parameter der Methode Pow
vom Typ
float ist - einem Wertetyp. Das heißt, dass die Methoden Add und Multiply nur
einen Verweis auf ihren jeweiligen Parameter erhalten, die Methode Pow dagegen eine Kopie
des Wertes des Parameters.
Verändert eine der Methoden also ihren Parameter, so hat das verschiedene Auswirkungen.
Die Methoden Add und Multiply würden nicht nur den Wert ändern, auf den sie zugreifen,
sondern auch den Wert der Methode, aus der sie aufgerufen werden, was eventuell nicht
gewünscht ist. Die Methode Pow hingegen kann ihren Wert nach Belieben ändern, da sie
eine eigene Kopie erhalten und daher keinen Zugriff auf die Daten der aufrufenden
Methode hat.
Diese beiden Möglichkeiten, einen Parameter als Verweis oder als echte Kopie der Daten
zu übergeben, werden by reference und by value genannt. Wertetypen werden standardmäßig
by value übergeben, Verweistypen by reference. Allerdings kann auch ein Wertetyp by
reference übergeben werden, so dass die aufrufende und die aufgerufene Methode auf die
gleichen Daten zugreifen.
Dies geschieht, indem das Schlüsselwort
ref dem Parameter vorangestellt wird, was
allerdings in der Praxis nur sehr selten benötigt wird. Andersherum kann ein Verweistyp
auch by value übergeben werden, allerdings muss dazu händisch eine Kopie des Objektes
angelegt werden, was unter Umständen sehr aufwändig ist.
Gelegentlich kommt es vor, dass mehr als ein Rückgabewert benötigt wird. In der Regel
sollte man für diesen Fall eine eigene Datenstruktur entwickeln, welche alle notwendigen
Daten aufnehmen kann. Alternativ können Parameter aber auch als zusätzliche Rückgabewerte
definiert werden, indem ihnen das Schlüsselwort
out vorangestellt wird. Parameter, die als
Ausgabeparameter gekennzeichnet werden, werden implizit by reference übergeben.
Um eine Methode aufzurufen, wird zunächst das Objekt, an dem sie aufgerufen werden soll,
genannt. Darauf folgt der Operator . und der Name der Methode, gefolgt von runden Klammern.
Schließlich wird dieser Aufruf mit einem Semikolon abgeschlossen. Sofern Parameter an die
Methode übergeben werden sollen, werden deren Werte innerhalb der runden Klammern
kommasepariert angegeben. Sofern eine Methode innerhalb des eigenen Objektes aufgerufen
werden soll, entfällt die Angabe des Objektnamens.
| C# |
1
2
3
4
5
6
7
8
|
// Conjugate a complex number.
complexNumber.Conjugate();
// Raise it to the power of 2.
complexNumber.Pow(2);
// Raise to the power of 2 from within the current instance.
Pow(2);
|
Allen Feldern, Eigenschaften und Methoden, die bislang vorgestellt wurden, ist gemein,
dass sie objektgebunden sind. Das heißt, sie beziehen sich immer auf ein Objekt, auch
wenn sie innerhalb einer Klasse definiert wurden. In der Regel entspricht dies dem
gewünschten Verhalten, gelegentlich sollen Felder, Eigenschaften oder Methoden aber
klassengebunden sein.
Auf klassengebundene Felder, Eigenschaften und Methoden kann direkt über die Klasse
zugegriffen werden, ohne ein bestimmtes Objekt ansprechen zu müssen. Zudem können diese
Elemente verwendet werden, ohne dass überhaupt ein Objekt der entsprechenden Klasse
erzeugt wurde. Außerdem existiert ein klassengebundenes Element nur ein einziges Mal,
unabhängig davon, wie viele Objekte erzeugt wurden - alle Objekte der Klasse teilen sich
die einzige Instanz der klassengebundenen Elemente.
Klassengebundene Felder können beispielsweise dazu genutzt werden, um klassenweit
gültige Status- oder Konfigurationsdaten allen Objekten der Klasse zur Verfügung zu
stellen, ohne dass für jedes Objekt eine eigene Verwaltung dieser Daten bestehen muss.
Elemente, die klassengebunden sind, werden in C# als statisch bezeichnet.
Um ein Element als statisch zu kennzeichnen, wird hinter dessen Zugriffsmodifizierer
das Schlüsselwort
static angegeben. Wenn eine Klasse nur
statische Elemente enthält, kann neben den einzelnen Elementen auch die gesamte Klasse
als statisch markiert werden, indem hinter ihrem Zugriffsmodifizierer das Schlüsselwort
static angegeben wird. Da ein Objekt einer statischen Klasse auf
Grund der fehlenden eigenen Felder, Eigenschaften und Methoden sinnlos wäre, kann von
einer statischen Klasse kein Objekt erzeugt werden.
Der Aufruf einer statischen Methode erfolgt genauso wie der einer objektgebundenen
Methode, mit der Ausnahme, dass nicht das Objekt vorangestellt wird, an dem die
Methode aufgerufen werden soll. Statt dessen wird die Klasse angegeben, welche die
entsprechende Methode enthält. Sofern eine Methode innerhalb der eigenen Klasse
aufgerufen werden soll, entfällt die Angabe des Klassennamens.
| C# |
1
2
3
4
5
6
7
8
|
// Call a static method on class Foo.
Foo.Bar();
// Call a static method with parameters.
Foo.Bar("Hello world.");
// Call a static method from within the current class.
Bar();
|
Eine besondere Rolle in diesem Zusammenhang spielt die statische Methode Main, bei der
die Ausführung einer Anwendung startet, weshalb in der gesamten Anwendung nur eine einzige
Methode diesen Namens existieren darf. Die Klasse, in der die Methode Main enthalten ist,
spielt dabei zunächst keine Rolle, da von ihr kein Objekt erzeugt wird - was wiederum
begründet, warum Main eine statische Methode sein muss.
Als Rückgabewert für Main können die Typen
void und
int angegeben werden, je nachdem,
ob ein Rückgabewert benötigt wird. Falls nicht, wird
void verwendet, bei Angabe von
int
kann mit Hilfe der Anweisung
return ein Wert zurückgegeben werden, der vom Betriebssystem
ausgewertet werden kann. Insbesondere bei Konsolenanwendungen ist dieser Rückgabewert ein
häufig genutztes Verfahren, um Fehler in der Anwendung an das Betriebssystem zu melden.
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
using System;
namespace GoloRoden.GuideToCSharp
{
/// <summary>
/// Represents the application class.
/// </summary>
public static class Program
{
/// <summary>
/// Executes the application.
/// </summary>
public static void Main()
{
}
}
}
|
Die Klasse Program verfügt mit der Existenz der statischen Methode Main über alle
Voraussetzungen, um in eine ausführbare Assembly mit der Dateiendung .exe übersetzt
zu werden. Das Kompilieren erfolgt genauso wie bei einer Assembly, die als Komponente
übersetzt wird, außer dass der Parameter an Stelle von /target:library nun /target:exe
lautet.
Der Aufruf von
csc /target:exe Program.cs
unter .NET und von
mcs /target:exe Program.cs
unter Mono erzeugen also eine entsprechende Assembly, die ausgeführt werden kann. Unter
anderen Betriebssystemen als Windows kann es notwendig sein, die Assembly explizit über
die Runtime von Mono zu starten.
mono Program.exe
Da die Klasse Program die Klasse ComplexNumber nutzen können soll, muss sie entsprechenden
Zugriff erhalten. Dies geschieht entweder, indem die Klasse ComplexNumber als eigene
Komponente übersetzt und anschließend eingebunden wird, oder indem beide Klassen in der
gleichen Assembly bereitgestellt werden. Das zweite ist an dieser Stelle deutlich
einfacher, weshalb die Anwendung mit
csc /target:exe Program.cs ComplexNumber.cs
unter .NET und mit
mcs /target:exe Program.cs ComplexNumber.cs
unter Mono erneut übersetzt wird.
Da die Klasse ComplexNumber inzwischen ein wenig länger geworden ist, bietet es sich an,
den Code zu gliedern. Dazu gibt es in C# die Direktive #region, die den Beginn einer
Region markiert, die durch eine weitere Direktive - #endregion - abgeschlossen wird.
Regionen werden beispielsweise von Visual Studio dazu genutzt, Abschnitte zusammenfassen
und zuklappen zu können.
Des weiteren kann eine Region benannt werden, indem hinter der Direktive #region eine
Beschreibung angegeben wird. Außerdem können Regionen ineinander verschachtelt werden,
um untergeordnete Regionen zu erstellen.
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
|
using System;
namespace GoloRoden.GuideToCSharp
{
/// <summary>
/// Represents a complex number.
/// </summary>
public class ComplexNumber
{
#region Properties
/// <summary>
/// Gets or sets the real part.
/// </summary>
/// <value>The real part.</value>
public float RealPart
{
get;
set;
}
/// <summary>
/// Gets or sets the imaginary part.
/// </summary>
/// <value>The imaginary part.</value>
public float ImginaryPart
{
get;
set;
}
/// <summary>
/// Gets the absolute value.
/// </summary>
/// <value>The absolute value.</value>
public float AbsoluteValue
{
get
{
// TODO gr: Calculate the absolute value
// and return it to the caller.
// 2007-04-08
}
}
#endregion
#region Methods
/// <summary>
/// Calculates the conjugation.
/// </summary>
public void Conjugate()
{
// TODO gr: Calculate the conjugation.
// 2007-04-09
}
/// <summary>
/// Checks whether the complex number is zero.
/// </summary>
/// <returns><c>true</c> if the real and the
/// imaginary part are zero; <c>false</c>
/// otherwise.<returns>
public bool IsZero()
{
// TODO gr: Check whether the real and the
// imaginary part are zero and return
// the result to the caller.
// 2007-04-09
}
/// <summary>
/// Adds the specified summand to the current complex
/// number.
/// </summary>
/// <param name="summand">The complex number that is
/// used as summand.</param>
public void Add(ComplexNumber summand)
{
// TODO gr: Add the summand to the current
// complex number.
// 2007-04-09
}
/// <summary>
/// Multiplies the current complex number with the
/// specified factor.
/// </summary>
/// <param name="factor">The complex number that is
/// used as factor.</param>
public void Multiply(ComplexNumber factor)
{
// TODO gr: Multiply the factor with the current
// complex number.
// 2007-04-09
}
/// <summary>
/// Raises the current complex number to the power of
/// the specified real number.
/// </summary>
/// <param name="exponent">The real number that is
/// used as exponent.</param>
public void Pow(float exponent)
{
// TODO gr: Raise the current complex number to
// a power.
// 2007-04-09
}
#endregion
// TODO gr: Add code here.
// 2007-04-08
}
}
|
Die Methoden zur Addition und Multiplikation von komplexen Zahlen haben einen Nachteil,
denn sie ermöglichen nur die Addition und Multiplikation von zwei komplexen Zahl. Um
allerdings die Summe oder das Produkt aus einer komplexen und einer reellen Zahl zu
berechnen, muss die reelle Zahl erst in eine komplexe Zahl abgebildet werden, bei welcher
der Realteil der reellen Zahl entspricht, der Imaginärteil hingegen Null ist.
Zur Lösung dieses Problems können die Methoden Add und Multiply mehrfach definiert werden,
sofern sich die einzelnen Definitionen in ihrer Signatur unterscheiden. Als Signatur wird
dabei der Name einer Methode einschließlich der Typen ihrer Parameter bezeichnet. Der
Rückgabewert spielt für die Signatur allerdings keine Rolle, weshalb zwar zwei Methoden
mit dem gleichen Rückgabewert und unterschiedlichen Parametern definiert werden können,
allerdings nicht umgekehrt.
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
|
using System;
namespace GoloRoden.GuideToCSharp
{
/// <summary>
/// Represents a complex number.
/// </summary>
public class ComplexNumber
{
#region Properties
/// <summary>
/// Gets or sets the real part.
/// </summary>
/// <value>The real part.</value>
public float RealPart
{
get;
set;
}
/// <summary>
/// Gets or sets the imaginary part.
/// </summary>
/// <value>The imaginary part.</value>
public float ImginaryPart
{
get;
set;
}
/// <summary>
/// Gets the absolute value.
/// </summary>
/// <value>The absolute value.</value>
public float AbsoluteValue
{
get
{
// TODO gr: Calculate the absolute value
// and return it to the caller.
// 2007-04-08
}
}
#endregion
#region Methods
/// <summary>
/// Calculates the conjugation.
/// </summary>
public void Conjugate()
{
// TODO gr: Calculate the conjugation.
// 2007-04-09
}
/// <summary>
/// Checks whether the complex number is zero.
/// </summary>
/// <returns><c>true</c> if the real and the
/// imaginary part are zero; <c>false</c>
/// otherwise.<returns>
public bool IsZero()
{
// TODO gr: Check whether the real and the
// imaginary part are zero.
// 2007-04-09
// Return the result to the caller.
return false;
}
/// <summary>
/// Adds the specified summand to the current complex
/// number.
/// </summary>
/// <param name="summand">The complex number that is
/// used as summand.</param>
public void Add(ComplexNumber summand)
{
// TODO gr: Add the summand to the current
// complex number.
// 2007-04-09
}
/// <summary>
/// Adds the specified summand to the current complex
/// number.
/// </summary>
/// <param name="summand">The real number that is
/// used as summand.</param>
public void Add(float summand)
{
// TODO gr: Add the summand to the current
// complex number.
// 2007-04-10
}
/// <summary>
/// Multiplies the current complex number with the
/// specified factor.
/// </summary>
/// <param name="factor">The complex number that is
/// used as factor.</param>
public void Multiply(ComplexNumber factor)
{
// TODO gr: Multiply the factor with the current
// complex number.
// 2007-04-09
}
/// <summary>
/// Multiplies the current complex number with the
/// specified factor.
/// </summary>
/// <param name="factor">The real number that is
/// used as factor.</param>
public void Multiply(float factor)
{
// TODO gr: Multiply the factor with the current
// complex number.
// 2007-04-10
}
/// <summary>
/// Raises the current complex number to the power of
/// the specified real number.
/// </summary>
/// <param name="exponent">The real number that is
/// used as exponent.</param>
public void Pow(float exponent)
{
// TODO gr: Raise the current complex number to a
// power.
// 2007-04-09
}
#endregion
// TODO gr: Add code here.
// 2007-04-08
}
}
|
Seit der Version 3.0 von C# gibt es neben partiellen Klassen auch sogenannte partielle
Methoden, die ebenfalls mit Hilfe des Schlüsselwortes
partial
definiert werden. Eine partielle Methode ermöglicht es, das Vorhandensein einer Methode
in einer partiellen Klasse zu definieren, ohne die Methode an sich bereitstellen zu
müssen. Die partielle Methode kann dann in einem anderen Bestandteil der Klasse
implementiert werden, geschieht dies nicht, wird der Aufruf der entsprechenden Methode
entfernt.
Das Einsatzgebiet von partiellen Methoden ähnelt dem von partiellen Klassen: Während es
mit partiellen Klassen möglich ist, generierten Code von benutzerdefiniertem Code zu
trennen, was beispielsweise von den Designern in Visual Studio genutzt wird, ermöglichen
partielle Methoden dem Designer, eine Methode zu definieren und bereits zu verwenden,
deren Inhalt allerdings vom Entwickler noch implementiert werden muss.
Partielle Methoden müssen zwingend mit dem Zugriffsmodifizierer
private
gekennzeichnet werden und können nur
void als Rückgabetyp haben.
Zudem können partielle Methoden nur innerhalb einer partiellen Klasse definiert werden, da
sie sonst nicht vom Entwickler ergänzt werden könnten. Zu guter letzt können partielle Methoden
so wohl klassen- wie auch instanzbezogen sein und über Parameter verfügen.
Konstruktoren
Nachdem die Klasse ComplexNumber nun sämtliche benötigten Felder, Eigenschaften und
Methoden enthält, fehlt zu der Vollendung ihres Rahmens noch eine Methode, die zur
Laufzeit der Anwendung ein Objekt dieser Klasse erzeugt und dieses Objekt mit geeigneten
Standardwerten initialisiert. Eine solche Methode wird in der objektorientierten
Programmierung als Konstruktor bezeichnet.
Prinzipiell trägt ein Konstruktor immer den Namen der Klasse und gleicht abgesehen von
einer Ausnahme einer normalen Methode: Ein Konstruktor verfügt im Gegensatz zu allen
anderen Methoden nicht über einen Rückgabewert, so dass dessen Angabe schlichtweg
entfällt. Parameter hingegen können auch bei Konstruktoren angegeben werden, um
beispielsweise Standardwerte für das zu erstellende Objekt vorzugeben.
Wird für eine Klasse kein Konstruktor definiert, verfügt sie implizit über einen
parameterlosen Konstruktor, der lediglich dazu dient, ein Objekt dieser Klasse zu
erzeugen. Ebenso wie normale Methoden können Konstruktoren überladen und mit einem
Zugriffsmodifizierer versehen werden, wobei dieser angibt, von wo aus die Klasse
instanziiert werden kann.
In der Regel wird als Zugriffsmodifizierer der der Klasse verwendet, allerdings
gibt es Fälle, in denen die Instanziierung verhindert werden soll. Um ein solches
Verhalten zu erreichen, kann
private als Zugriffsmodifizierer für den Konstruktor
angegeben werden, wodurch eine Instanziierung nur noch aus der Klasse selbst
erfolgen kann.
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
|
using System;
namespace GoloRoden.GuideToCSharp
{
/// <summary>
/// Represents a complex number.
/// </summary>
public class ComplexNumber
{
#region Properties
/// <summary>
/// Gets or sets the real part.
/// </summary>
/// <value>The real part.</value>
public float RealPart
{
get;
set;
}
/// <summary>
/// Gets or sets the imaginary part.
/// </summary>
/// <value>The imaginary part.</value>
public float ImginaryPart
{
get;
set;
}
/// <summary>
/// Gets the absolute value.
/// </summary>
/// <value>The absolute value.</value>
public float AbsoluteValue
{
get
{
// TODO gr: Calculate the absolute value
// and return it to the caller.
// 2007-04-08
}
}
#endregion
#region Methods
/// <summary>
/// Calculates the conjugation.
/// </summary>
public void Conjugate()
{
// TODO gr: Calculate the conjugation.
// 2007-04-09
}
/// <summary>
/// Checks whether the complex number is zero.
/// </summary>
/// <returns><c>true</c> if the real and the
/// imaginary part are zero; <c>false</c>
/// otherwise.<returns>
public bool IsZero()
{
// TODO gr: Check whether the real and the
// imaginary part are zero and return
// the result to the caller.
// 2007-04-09
}
/// <summary>
/// Adds the specified summand to the current complex
/// number.
/// </summary>
/// <param name="summand">The complex number that is
/// used as summand.</param>
public void Add(ComplexNumber summand)
{
// TODO gr: Add the summand to the current complex
// number.
// 2007-04-09
}
/// <summary>
/// Adds the specified summand to the current complex
/// number.
/// </summary>
/// <param name="summand">The real number that is
/// used as summand.</param>
public void Add(float summand)
{
// TODO gr: Add the summand to the current
// complex number.
// 2007-04-10
}
/// <summary>
/// Multiplies the current complex number with the
/// specified factor.
/// </summary>
/// <param name="factor">The complex number that is
/// used as factor.</param>
public void Multiply(ComplexNumber factor)
{
// TODO gr: Multiply the factor with the current
// complex number.
// 2007-04-09
}
/// <summary>
/// Multiplies the current complex number with the
/// specified factor.
/// </summary>
/// <param name="factor">The real number that is
/// used as factor.</param>
public void Multiply(float factor)
{
// TODO gr: Multiply the factor with the current
// complex number.
// 2007-04-10
}
/// <summary>
/// Raises the current complex number to the power of
/// the specified real number.
/// </summary>
/// <param name="exponent">The real number that is
/// used as exponent.</param>
public void Pow(float exponent)
{
// TODO gr: Raise the current complex number to a
// power.
// 2007-04-09
}
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the ComplexNumber
/// type using default values.
/// </summary>
public ComplexNumber()
{
// TODO gr: Set default values for the real and
// imaginary part.
// 2007-04-25
}
/// <summary>
/// Initializes a new instance of the ComplexNumber
/// type using the specified real value.
/// </summary>
/// <param name="realPart">The real part.</param>
public ComplexNumber(float realPart)
{
// TODO gr: Set default values for the real and
// imaginary part.
// 2007-04-25
}
/// <summary>
/// Initializes a new instance of the ComplexNumber
/// type using the specified real and imaginary
/// values.
/// </summary>
/// <param name="realPart">The real part.</param>
/// <param name="imaginaryPart">The imaginary
/// part.</param>
public ComplexNumber(
float realPart, float imaginaryPart)
{
// TODO gr: Set default values for the real and
// imaginary part.
// 2007-04-25
}
#endregion
}
}
|
Das Setzen der Werte, die in den Parametern übergeben wurden, erfolgt prinzipiell
genauso wie in Eigenschaften. Der einzige Unterschied besteht darin, dass jeder Parameter
einen eigenen Namen trägt und nicht über das Schlüsselwort
value angesprochen wird.
Dabei besteht die Möglichkeit, den zu setzenden Wert dem Feld oder der Eigenschaft
zuzuweisen. In der Regel ist es gleich, welche Variante genutzt wird, allerdings sollte
die gewählte Variante durchgängig verwendet werden.
Unter Umständen kann es zu Namenskonflikten kommen, wenn beispielsweise ein Feld oder
eine Eigenschaft den gleichen Namen trägt wie ein Parameter. Obwohl solche Konflikte
nicht nur in Konstruktoren, sondern grundsätzlich in jeder Methode auftreten können,
häufen sie sich in jenen. Schließlich existiert hier potenziell für jedes Feld ein
entsprechender gleichnamiger Parameter.
Um in diesem Fall den Parameter auf der einen und das Feld oder die Eigenschaft auf
der anderen Seite unterscheiden zu können, enthält C# das Schlüsselwort
this, das eine
Referenz auf das eigene Objekt zur Verfügung stellt. Im Konfliktfall muss daher jedem
nicht eindeutigen Bezeichner, der ein Feld oder eine Eigenschaft beschreibt,
this
vorangestellt werden.
Auch wenn die Verwendung des Schlüsselwortes
this ansonsten optional ist, gilt es als
guter Stil, es bei jedem Verweis auf ein Feld, eine Eigenschaft oder eine Methode des
eigenen Objektes anzugeben.
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
|
using System;
namespace GoloRoden.GuideToCSharp
{
/// <summary>
/// Represents a complex number.
/// </summary>
public class ComplexNumber
{
#region Properties
/// <summary>
/// Gets or sets the real part.
/// </summary>
/// <value>The real part.</value>
public float RealPart
{
get;
set;
}
/// <summary>
/// Gets or sets the imaginary part.
/// </summary>
/// <value>The imaginary part.</value>
public float ImginaryPart
{
get;
set;
}
/// <summary>
/// Gets the absolute value.
/// </summary>
/// <value>The absolute value.</value>
public float AbsoluteValue
{
get
{
// TODO gr: Calculate the absolute value
// and return it to the caller.
// 2007-04-08
}
}
#endregion
#region Methods
/// <summary>
/// Calculates the conjugation.
/// </summary>
public void Conjugate()
{
// TODO gr: Calculate the conjugation.
// 2007-04-09
}
/// <summary>
/// Checks whether the complex number is zero.
/// </summary>
/// <returns><c>true</c> if the real and the
/// imaginary part are zero; <c>false</c>
/// otherwise.<returns>
public bool IsZero()
{
// TODO gr: Check whether the real and the
// imaginary part are zero and return
// the result to the caller.
// 2007-04-09
}
/// <summary>
/// Adds the specified summand to the current complex
/// number.
/// </summary>
/// <param name="summand">The complex number that is
/// used as summand.</param>
public void Add(ComplexNumber summand)
{
// TODO gr: Add the summand to the current complex
// number.
// 2007-04-09
}
/// <summary>
/// Adds the specified summand to the current complex
/// number.
/// </summary>
/// <param name="summand">The real number that is
/// used as summand.</param>
public void Add(float summand)
{
// TODO gr: Add the summand to the current
// complex number.
// 2007-04-10
}
/// <summary>
/// Multiplies the current complex number with the
/// specified factor.
/// </summary>
/// <param name="factor">The complex number that is
/// used as factor.</param>
public void Multiply(ComplexNumber factor)
{
// TODO gr: Multiply the factor with the current
// complex number.
// 2007-04-09
}
/// <summary>
/// Multiplies the current complex number with the
/// specified factor.
/// </summary>
/// <param name="factor">The real number that is used
/// as factor.</param>
public void Multiply(float factor)
{
// TODO gr: Multiply the factor with the current
// complex number.
// 2007-04-10
}
/// <summary>
/// Raises the current complex number to the power of
/// the specified real number.
/// </summary>
/// <param name="exponent">The real number that is
/// used as exponent.</param>
public void Pow(float exponent)
{
// TODO gr: Raise the current complex number to a
// power.
// 2007-04-09
}
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the ComplexNumber
/// type using default values.
/// </summary>
public ComplexNumber()
{
// Set default values for the real and
// imaginary part.
this.RealPart = 0;
this.ImaginaryPart = 0;
}
/// <summary>
/// Initializes a new instance of the ComplexNumber
/// type using the specified real value.
/// </summary>
/// <param name="realPart">The real part.</param>
public ComplexNumber(float realPart)
{
// Set default values for the real and
// imaginary part.
this.RealPart = realPart;
this.ImaginaryPart = 0;
}
/// <summary>
/// Initializes a new instance of the ComplexNumber
/// type using the specified real and imaginary
/// values.
/// </summary>
/// <param name="realPart">The real part.</param>
/// <param name="imaginaryPart">The imaginary
/// part.</param>
public ComplexNumber(
float realPart, float imaginaryPart)
{
// Set default values for the real and
// imaginary part.
this.RealPart = realPart;
this.ImaginaryPart = imaginaryPart;
}
#endregion
}
}
|
Ein unschöner Aspekt überladener Konstruktoren ist, dass sie unter Umständen redundanten
Code enthalten. Daher können Konstruktoren andere Konstruktoren aufrufen, so dass sämtliche
gemeinsam genutzte Funktionalität nur in einem Konstruktor enthalten sein muss. Der Aufruf
erfolgt, indem hinter der Parameterliste durch einen Doppelpunkt getrennt das Schlüsselwort
this mit den entsprechenden Parametern angegeben wird.
Jeder Konstruktor kann zusätzlich eigene Anweisungen enthalten, wobei diese erst dann
ausgeführt werden, wenn sämtliche anderen Konstruktoraufrufe abgeschlossen sind. Wird ein
Feld so wohl direkt wie auch im Konstruktor mit einem Wert versehen, so überschreibt die
Zuweisung im Konstruktor die direkte Zuweisung.
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
|
using System;
namespace GoloRoden.GuideToCSharp
{
/// <summary>
/// Represents a complex number.
/// </summary>
public class ComplexNumber
{
#region Properties
/// <summary>
/// Gets or sets the real part.
/// </summary>
/// <value>The real part.</value>
public float RealPart
{
get;
set;
}
/// <summary>
/// Gets or sets the imaginary part.
/// </summary>
/// <value>The imaginary part.</value>
public float ImginaryPart
{
get;
set;
}
/// <summary>
/// Gets the absolute value.
/// </summary>
/// <value>The absolute value.</value>
public float AbsoluteValue
{
get
{
// TODO gr: Calculate the absolute value
// and return it to the caller.
// 2007-04-08
}
}
#endregion
#region Methods
/// <summary>
/// Calculates the conjugation.
/// </summary>
public void Conjugate()
{
// TODO gr: Calculate the conjugation.
// 2007-04-09
}
/// <summary>
/// Checks whether the complex number is zero.
/// </summary>
/// <returns><c>true</c> if the real and the
/// imaginary part are zero; <c>false</c>
/// otherwise.<returns>
public bool IsZero()
{
// TODO gr: Check whether the real and the
// imaginary part are zero and return
// the result to the caller.
// 2007-04-09
}
/// <summary>
/// Adds the specified summand to the current complex
/// number.
/// </summary>
/// <param name="summand">The complex number that is
/// used as summand.</param>
public void Add(ComplexNumber summand)
{
// TODO gr: Add the summand to the current complex
// number.
// 2007-04-09
}
/// <summary>
/// Adds the specified summand to the current complex
/// number.
/// </summary>
/// <param name="summand">The real number that is
/// used as summand.</param>
public void Add(float summand)
{
// TODO gr: Add the summand to the current
// complex number.
// 2007-04-10
}
/// <summary>
/// Multiplies the current complex number with the
/// specified factor.
/// </summary>
/// <param name="factor">The complex number that is
/// used as factor.</param>
public void Multiply(ComplexNumber factor)
{
// TODO gr: Multiply the factor with the current
// complex number.
// 2007-04-09
}
/// <summary>
/// Multiplies the current complex number with the
/// specified factor.
/// </summary>
/// <param name="factor">The real number that is
/// used as factor.</param>
public void Multiply(float factor)
{
// TODO gr: Multiply the factor with the current
// complex number.
// 2007-04-10
}
/// <summary>
/// Raises the current complex number to the power of
/// the specified real number.
/// </summary>
/// <param name="exponent">The real number that is
/// used as exponent.</param>
public void Pow(float exponent)
{
// TODO gr: Raise the current complex number to a
// power.
// 2007-04-09
}
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the ComplexNumber
/// type using default values.
/// </summary>
public ComplexNumber()
: this(0, 0)
{
}
/// <summary>
/// Initializes a new instance of the ComplexNumber
/// type using the specified real value.
/// </summary>
/// <param name="realPart">The real part.</param>
public ComplexNumber(float realPart)
: this(realPart, 0)
{
}
/// <summary>
/// Initializes a new instance of the ComplexNumber
/// type using the specified real and imaginary
/// values.
/// </summary>
/// <param name="realPart">The real part.</param>
/// <param name="imaginaryPart">The imaginary
/// part.</param>
public ComplexNumber(
float realPart, float imaginaryPart)
{
// Set default values for the real and
// imaginary part.
this.RealPart = realPart;
this.ImaginaryPart = imaginaryPart;
}
#endregion
}
}
|
Im Zusammenhang mit Konstruktoren verfügen Felder zudem über eine Besonderheit - um Felder
als konstant zu definieren, kann an Stelle des Schlüsselwortes
const das Schlüsselwort
readonly verwendet werden. Wird
readonly der Definition eines Feldes vorangestellt, kann
dessen Wert wie bei
const nur direkt und zusätzlich noch im Konstruktor gesetzt werden.
Alle weiteren Zugriffe danach können aber nur noch lesend stattfinden.
Konstruktoren können jedoch nicht nur dazu genutzt werden, um Objekte zu initialisieren.
Gelegentlich kann es notwendig sein, eine Klasse an sich zu initialisieren, wobei dies
insbesondere bei der Verwendung statischer Felder, Eigenschaften oder Methoden vorkommt.
Dazu dienen statische Konstruktoren, die auch als Klassenkonstruktoren bezeichnet werden,
und sich von den übrigen Konstruktoren durch das zusätzliche Schlüsselwort
static unterscheiden.
Außerdem können statische Konstruktoren weder überladen noch parametrisiert werden, zudem
verfügen sie nicht über einen Zugriffsmodifizierer. Ausgeführt werden statische
Konstruktoren beim ersten Zugriff auf die Klasse - unabhängig von der Art des Zugriffs.
Strukturen
Neben Klassen verfügt C# über ein weiteres Konzept zur Definition von Typen, das Klassen
sehr ähnlich ist, nämlich Strukturen. Der wesentliche Unterschied zwischen beiden ist,
dass Klassen Verweistypen sind, Strukturen hingegen Wertetypen.
Dementsprechend bietet sich der Einsatz von Strukturen in der Regel dann an, wenn die
enthaltenen Felder und Eigenschaften ausschließlich oder zumindest nahezu nur auf Wertetypen
basieren.
Häufig werden Strukturen eingesetzt, wenn Daten per COM mit nicht verwalteten Anwendungen
ausgetauscht werden, wobei deren Aufbau dann von der über COM angesprochenen Anwendung
vorgegeben ist. In diesem Zusammenhang kann mit dem
sizeof-Operator
der Speicherbedarf einer Struktur in Bytes ermittelt werden, worauf allerdings an dieser
Stelle nicht weiter eingegangen wird.
Für Strukturen kann kein parameterloser Konstruktor definiert werden, dieser existiert
hingegen implizit immer und initialisiert alle Felder mit den Standardwerten der
entsprechenden Typen. Sofern allerdings ein eigener Konstruktor definiert wird, muss dieser
zum einen über mindestens einen Parameter verfügen, zum anderen müssen in ihm alle Felder
der Struktur mit einem Wert initialisiert werden.
Die Definition einer Struktur erfolgt prinzipiell zu der einer Klasse, außer dass das
entsprechende Schlüsselwort
struct statt
class lautet. In der Entwicklung rein
objektorientierter Anwendungen sind Strukturen allerdings verhältnismäßig selten
geworden, da in der Regel statt dessen eine Klasse definiert wird, die deutlich mehr
Flexibilität bietet.