Freitag, 3. September 2010

Unterschied zwischen Wert- und Verweistypen

In der Programmiersprache C# gibt es, wie bei vielen Programmiersprachen, grundsätzlich 2 Datentypvarianten: Es gibt Werttypen und Verweistypen (oder Referenztypen).
Variablen, die auf Werttypen basieren, speichern den tatsächlichen Wert dieser Variablen. Im Gegensatz dazu speichern Variablen, die auf Verweistypen basieren, nur einen Verweis auf den Speicherplatz des Wertes der Variablen.
Weiterhin werden diese beiden Typvarianten auf unterschiedliche Art gespeichert: Werttypen werden im Stapel gespeichert, Verweistypen in einem eigenen Speicherbereich, dem Heap.
Kopiert man den Inhalt eines Werttyps, wird der Wert kopiert, danach existieren 2 Variablen mit dem gleichen Wert. Kopiert man den Inhalt eines Verweistyps, wird lediglich der Verweis kopiert, es existieren dann 2 Verweise, die auf den gleichen Wert im Heap verweisen. Ändert man also den Inhalt der ursprünglichen Verweisvariablen, ändert sich auch der Inhalt der kopierten Verweisvariablen, da sie auf den gleichen Speicherplatz zeigt.
Die meisten Typen in C# sind Werttypen, wie zum Beispiel byte, int und char. Verweistypen sind generell nur Klassen und Arrays. Wofür braucht man nun diese Unterscheidung beim Programmieren?
Unvermeidlich wird ein Wissen über Datentypvarianten, wenn man Methoden mit Parametern benutzt. Folgendes einfaches Beispiel sollte dies verdeutlichen:

        private void Form1_Load(object sender, EventArgs e)
        {
            // Werttypen
            int NumberA = 8;
            int NumberB = 3;

            // Verweistyp
            int[] Numbers = { 8, 3 };

            int NumberC = ResultValue(NumberA, NumberB);
            int NumberD = ResultReference(Numbers);
        }

        private int ResultValue(int numberA, int numberB)
        {
            numberA /= 2; // numberA halbieren
            return numberA * numberB;
        }

        private int ResultReference(int[] numbers)
        {
            numbers[0] /= 2; // 1. Zahl im Array halbieren
            return numbers[0] * numbers[1];
        }

Es gibt 2 Methoden, ResultValue() und ResultReference(), welche aus Form_Load() aufgerufen werden. Erstere erhält 2 Variablen vom Typ Integer als Parameter, die Zahlen 8 und 3. In der Methode wird die 1. Zahl (8) halbiert, und das Produkt beider Zahlen (4 * 3 = 12) zurückgegeben.
In der 2. Methode ResultReference() wird ein Array vom Typ Integer als Parameter übergeben, in welchem auch 2 Zahlen (8 und 3) gespeichert sind.
Es wird ebenfalls die erste Zahl halbiert und das Produkt zurückgegeben (4 * 3 = 12). Allerdings sind die Integer - Parameter der ersten Methode Werttypen, bei der Übergabe an ResultValue() wird also von ihnen eine Kopie im Speicher erzeugt, die Halbierung der ersten Zahl wird also nur bei der Kopie vorgenommen, die Originalzahl, NumberA, ist nach Rücksprung aus der Methode immer noch 8.
Das an ResultReference() übergebene Array ist allerdings ein Verweistyp, bei der Übergabe des Parameters an die Methode wird lediglich der Verweis kopiert, er zeigt aber auf die gleiche Speicherposition im Heap. Wird dann in der Methode die erste Zahl im Array halbiert, ist das eine Änderung am Original, nach Rücksprung der Methode ist der Wert der ersten Zahl in Numbers gleich 4.

Um dieses Verhalten zu ändern, gibt es das Schlüsselwort ref. Wird dieses vor einen Werttyp - Parameter gestellt, wird dieser als Verweistyp behandelt und bei der Übergabe keine Wertkopie erstellt, sondern der Verweis auf das Original übergeben:

            // Werttypen
            int NumberA = 8;
            int NumberB = 3;

            int NumberC = ResultValue(ref NumberA, ref NumberB);

Im obigen Beispiel wird dann in der Methode das Original, NumberA, halbiert.
Um Verweistypen in Werttypen umzuwandeln gibt es kein entsprechendes Schlüsselwort, bei der Parameterübergabe muss manuell vom zu übergebenden Objekt eine echte Kopie, eine deep copy, erstellt werden.

Keine Kommentare:

Kommentar veröffentlichen