Die bisher einzige Möglichkeit, Daten zu speichern, besteht in der Verwendung von Feldern.
Diese sind dann nützlich, wenn die entsprechenden Daten relevant für den Status des
Objektes an sich sind. Allerdings besteht manchmal die Notwendigkeit, Daten temporär zu
speichern, wenn diese beispielsweise als Zwischenergebnis einer Berechnung für eine spätere
Verarbeitung zur Verfügung stehen sollen, nach dem Abschluss der Berechnung aber nicht
mehr benötigt werden.
Für diese Fälle verfügt C# über ein ähnliches Konzept wie Felder, nämlich Variablen. Im
Gegensatz zu Feldern werden Variablen allerdings nicht innerhalb eines Typs, sondern
innerhalb einer Methode definiert und stehen dort auch nur so lange zur Verfügung, wie
die Methode ausgeführt wird. Deshalb werden sie auch als lokale Variablen bezeichnet.
Nachdem die Ausführung der Methode, welche die lokalen Variablen enthält, beendet wurde,
wird der Speicher der lokalen Variablen wieder freigegeben, wodurch diese ihren Wert
verlieren und sich beim nächsten Aufruf der Methode wieder derart verhalten, als wären sie
noch nie verwendet worden.
Da der Zugriff auf lokale Variablen nur aus der Methode möglich ist, welche die lokalen
Variablen enthält, wird bei deren Deklaration auf die Angabe eines Zugriffsmodifizierers
verzichtet. Wie bei Feldern kann auch lokalen Variablen ein Standardwert zugewiesen
werden. Falls ein Standardwert für eine lokale Variable angegeben wird, wird ihre Erzeugung
als Definition bezeichnet, andernfalls als Deklaration.
Als Namenskonventionen gelten die Regeln von Feldern, mit der Ausnahme, dass auf den
führenden Unterstrich verzichtet wird.
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
using System;
namespace GoloRoden.GuideToCSharp
{
/// <summary>
/// Represents the application class.
/// </summary>
public class Program
{
/// <summary>
/// Executes the application.
/// </summary>
public static void Main()
{
// Declare the mathematical constant pi.
double pi;
}
}
}
|
Nachdem eine Variable deklariert wurde, kann auf sie und damit auf ihren Wert zugegriffen
werden. Die Zuweisung eines neuen Wertes erfolgt analog der Zuweisung eines Wertes an ein
Feld mit Hilfe des Operators =.
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
using System;
namespace GoloRoden.GuideToCSharp
{
/// <summary>
/// Represents the application class.
/// </summary>
public class Program
{
/// <summary>
/// Executes the application.
/// </summary>
public static void Main()
{
// Define the mathematical constant pi.
double pi = 3.1415926;
}
}
}
|
Mit Variablen ist es nun auch möglich, den Rückgabewert von Methoden zu verarbeiten, indem
bei der Zuweisung an Stelle eines konkreten Wertes der Methodenaufruf angegeben wird. In
diesem Fall wird zunächst die Methode aufgerufen und ausgeführt und anschließend ihr Rückgabewert
der Variablen zugewiesen.
| 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
|
using System;
namespace GoloRoden.GuideToCSharp
{
/// <summary>
/// Represents the application class.
/// </summary>
public class Program
{
/// <summary>
/// Executes the application.
/// </summary>
public static void Main()
{
// TODO gr: Create a complex number.
// 2007-07-10
// Determine the absolute value and assign it
// to a local variable.
float absoluteValue =
complexNumber.AbsoluteValue;
}
}
}
|
Die lokale Variable kann im folgenden Verlauf der Methode verwendet werden, um weitere
Berechnungen auszuführen, oder um ihren Wert auf die Konsole auszugeben. Zu diesem Zweck
enthält die Framework Class Library die Klasse Console im Namensraum System, deren Methode
WriteLine den Wert des übergebenen Parameters auf der Konsole ausgibt.
| 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
|
using System;
namespace GoloRoden.GuideToCSharp
{
/// <summary>
/// Represents the application class.
/// </summary>
public class Program
{
/// <summary>
/// Executes the application.
/// </summary>
public static void Main()
{
// TODO gr: Create a complex number.
// 2007-07-10
// Determine the absolute value and assign it
// to a local variable.
float absoluteValue =
complexNumber.AbsoluteValue;
// Print the absolute value to the console.
Console.WriteLine(absoluteValue);
}
}
}
|
Variablen eignen sich jedoch nicht nur dazu, den Rückgabewert eines einfachen
Methodenaufrufs aufzunehmen. Mit ihrer Hilfe kann eine weitere, neue Art von Methoden
definiert werden, die vorher nicht möglich war: Rekursive Methoden. Dabei handelt es
sich um Methoden, die sich intern selbst aufrufen, um ihren Rückgabewert zu berechnen.
Ein bekanntes Beispiel für eine rekursive Berechnung ist die Folge der Fibonacci-Zahlen. In
dieser Folge werden nur für die beiden ersten Elemente die Werte 0 und 1 vorgegeben, alle
folgenden Elemente berechnen sich aus der Summe ihrer beiden Vorgänger. Das dritte Element
entspricht also der Summe aus 0 und 1, das vierte Element der Summe aus 1 und 1, das fünfte
der Summe aus 1 und 2, ...
Hierdurch ergibt sich die Folge:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, ...
Diese Folge lässt sich rekursiv berechnen, da die n-te Fibonacci-Zahl der Summe aus der
n-1-ten und n-2-ten Fibonacci-Zahl entspricht, wobei diese wiederum aus ihren Vorgängern
berechnet werden können. Prinzipiell folgt eine Methode zur Berechnung der Fibonacci-Zahlen
also dem folgenden Schema:
| 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
|
using System;
namespace GoloRoden.GuideToCSharp
{
/// <summary>
/// Represents a foo class.
/// </summary>
public class Foo
{
/// <summary>
/// Calculates the n-th Fibonacci number.
/// </summary>
public int CalculateFibonacci(int n)
{
// Declare a variable for the sum of the
// predecessors.
int sum;
// TODO gr: Calculate the number by adding
// its predecessors.
// 2007-07-17
// Return the sum to the caller.
return sum;
}
}
}
|
Würde man diese Methode allerdings in dieser Form aufrufen, käme es zu einem Überlauf im
Methodenstapel, da sich die Methode ohne Abbruch immer wieder selbst aufriefe und somit in
eine endlose Schleife geriete. Die Lösung stellt ein Abbruchkriterium dar, das im Fall von
n gleich 1 oder 2 die entsprechend definierten Startwerte der Fibonacci-Folge zurückgibt.
| 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>
/// Calculates the n-th Fibonacci number.
/// </summary>
public int CalculateFibonacci(int n)
{
// TODO gr: Check whether the first or the
// second Fibonacci number is requested.
// If so, return the appropriate values.
// 2007-07-17
// Declare a variable for the sum of the
// predecessors.
int sum;
// TODO gr: Calculate the number by adding its
// predecessors.
// 2007-07-17
// Return the sum to the caller.
return sum;
}
}
}
|
Prinzipiell kann eine lokale Variable an jeder beliebigen Stelle einer Methode definiert
werden, sofern die Deklaration vor der ersten Verwendung der Variablen statt findet. Es
gilt allerdings als guter Stil, eine lokale Variable so spät wie möglich vor ihrer ersten
Verwendung zu definieren.
Da die Variable sum, welche die Summe der beiden vorangegangenen Fibonacci-Zahlen aufnimmt,
erst ab der Berechnung der dritten Fibonacci-Zahl benötigt wird, wird sie erst nach der
entsprechenden Prüfung definiert.
Bislang wurde bereits einige Male der Operator = verwendet, um einem Feld oder einer
Variablen einen Wert zuzuweisen, weshalb dieser Operator als Zuweisungsoperator bezeichnet
wird. Bei einer Zuweisung wird immer der Wert rechts des Operators dem Element zu seiner
Linken zugeordnet. Bisher wurde bei den Zuweisungen allerdings immer nur auf Wertetypen
zugegriffen.
Die Zuweisung an Verweistypen funktioniert prinzipiell gleich: Einem Element auf der
linken Seite kann ein auf der rechten Seite des Operators stehendes Objekt zugewiesen
werden. Allerdings muss - damit ein Objekt zugewiesen werden kann - zunächst ein Objekt
erzeugt werden. Es wurde bereits erwähnt, dass die entsprechende Methode, die bei der
sogenannten Instanziierung von Objekten ausgeführt wird, der Konstruktor der entsprechenden
Klasse ist.
Um also eine neue Instanz zu erzeugen, muss der Konstruktor aufgerufen werden, dem
allerdings zusätzlich das Schlüsselwort
new vorangestellt wird.
Obwohl ein Konstruktor nicht über einen Rückgabewert verfügt, wird durch das Schlüsselwort
new ein Verweis auf das neu erzeugte Objekt zurückgegeben, der in
einem Element gespeichert werden kann.
Um also ein Objekt der Klasse ComplexNumber zu erzeugen, müsste der Aufruf folgendermaßen
lauten:
| C# |
1
|
new ComplexNumber();
|
Falls der neu erzeugten komplexen Zahl direkt Werte für den Real- und den Imaginärteil
zugewiesen werden sollen, können diese als Parameter übergeben werden - vorausgesetzt, es
wurde ein entsprechender Konstruktor definiert.
| C# |
1
|
new ComplexNumber(23, 42);
|
Damit schließlich der Verweis auf das neu erzeugte Objekt gespeichert wird, muss die
Anweisung noch um eine Zuweisung an eine Variable ergänzt werden, die zuvor deklariert
werden muss.
| C# |
1
2
|
ComplexNumber myNumber;
myNumber = new ComplexNumber(23, 42);
|
Alternativ kann, wie bei Wertetypen auch, die Deklaration mit einer Zuweisung zu einer
Definition verbunden werden:
| C# |
1
|
ComplexNumber myNumber = new ComplexNumber(23, 42);
|
Mit der Möglichkeit, Objekte erzeugen zu können, lässt sich die Klasse ComplexNumber nun
auch in einer Anwendung nutzen, um beispielsweise die Summe zweier komplexer Zahlen zu
berechnen.
| 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
|
using System
namespace GoloRoden.GuideToCSharp
{
/// <summary>
/// Represents the application class.
/// </summary>
public static class Program
{
/// <summary>
/// Executes the application.
/// </summary>
public static void Main()
{
// Create two complex numbers.
ComplexNumber first = new ComplexNumber(23, 42);
ComplexNumber second = new ComplexNumber(17, 2);
// Add the second number to the first one.
first.Add(second);
// Print the result to the console.
Console.WriteLine(first.Real);
Console.WriteLine(first.Imaginary);
}
}
}
|
Die Zuweisung an nullbare Wertetypen hingegen funktioniert wiederum so wie die Zuweisung
an normale Wertetypen. Der einzige Unterschied liegt darin, dass nullbaren Wertetypen das
Literal
null zugewiesen werden kann, was bei normalen Wertetypen
nicht möglich ist.
Seit der Version 3.0 von C# gibt es mit Hilfe der sogenannten Objektinitialisierer eine
weitere Möglichkeit, Objekte zu erzeugen. Mit Objektinitialisierern ist es nicht mehr nötig,
für jede potenzielle Initialisierung einen eigenen Konstruktur bereitzustellen. Statt
dessen werden die zu initialisierenden Eigenschaften und ihre Werte direkt beim Aufruf
von
new mit angegeben. An Stelle von
| C# |
1
2
3
4
5
6
7
|
// Create an instance of the Person type.
Person person = new Person();
// Set the values.
person.LastName = "Roden";
person.FirstName = "Golo";
person.EMail = "webmaster@goloroden.de";
|
kann mit Hilfe von Objektinitialisierern also auch
| C# |
1
2
3
4
5
|
// Create an instance of the Person type and set its
// values.
Person person = new Person {
LastName = "Roden", FirstName = "Golo",
EMail = "webmaster@goloroden.de" };
|
geschrieben werden. Um das Ganze noch weiter zu vereinfachen, kann sogar die Angabe des
Typs entfallen. C# erzeugt in diesem Fall im Hintergrund einen passenden Typ, dessen Name
dem Entwickler nicht bekannt ist, und der deshalb als anonymer Typ bezeichnet wird. Um ein
solches Objekt eines anonymen Typs in einer Variablen speichern zu können, gibt es das
Schlüsselwort
var.
| C# |
1
2
3
4
5
|
// Create a new instance of an anonymous type for persons
// and set its values.
var person =
new { LastName = "Roden", FirstName = "Golo",
EMail = "webmaster@goloroden.de" };
|
Obwohl der Typ in diesem Beispiel dem Entwickler nicht bekannt ist, ist der Zugriff auf
das Objekt trotzdem typsicher.
var steht also nicht austauschbar
für jeden beliebigen Typ, sondern leitet den zu verwendenden Typ aus dem Ausdruck auf der
rechten Seite des Zuweisungsoperators ab.
Wird ein Typ hergeleitet, dessen Eigenschaften namentlich und von ihrem Typ einem bestehenden
Typ entsprechen, wird dieser Typ verwendet. Es wird also nicht bei jedem Aufruf von
new ohne Angabe eines Typs ein neuer Typ erzeugt, sondern nur dann,
wenn kein passender Typ gefunden wird.
Das Schlüsselwort
var kann prinzipiell auch für eingebaute Typen
verwendet werden, so kann an Stelle der Zeile
| C# |
1
2
|
// Initialize a variable of type int.
int i = 23;
|
auch die Zeile
| C# |
1
2
3
|
// Initialize a variable of type int by using type
// inference.
var i = 23;
|
verwendet werden. In beiden Fällen wird eine Variable des Typs
int
erzeugt. Zu beachten ist bei anonymen Typen, dass ihr Einsatz nur für lokale Variablen
möglich ist, sie können insbesondere nicht als Rückgabewert für Methoden verwendet werden.