ein Projekt von goloroden.de

Variablen

Was sind Variablen?

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.

Zuweisungen an Variablen

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.