Peter Bucher - Mein Experiment, meine Spielereien, meine Welt...   ·   Stefan Falz   ·   Jürgen Gutsch   ·   Golo Roden   ·   ASP.NET Zone   ·   Microsoft ASP.NET
Willkommen bei ASP.NET Zone. Anmelden | Registrieren | Hilfe

Artikel: Parameterübergabe in C# (by value, by reference und out)

Einleitung

Über diesen Artikel:
Ich wollte genau wissen, wie die Parameterübergabe in C# funktioniert. So bin im Netz auf die Suche gegangen und habe Tests gemacht.
Im Artikel von myCSharp wurde das Vorgehen und Verhalten gut erklärt, aber bei mir hat es dann immer noch nicht ganz "Klick" gemacht. Knifflig fand ich vor allem, wie die standardmässige Übergabe ohne irgendein Schlüsselwort funktioniert.

Zum Thema Paramterübergabe habe ich nirgends einen Artikel gefunden, der leicht zu verstehen ist und alles übersichtlich darstellt.
Nach dem Zusammensuchen von verschiedensten Informationen versuche ich nun, die gewonnene Einsicht auch für Anfänger verständlich zu erklären. Ich hoffe, dass mir dies gelungen ist und dass euch dieser Artikel gefällt.

Grundlagen zum Artikel:
- Wertetypen / Referenztypen / Stack / Heap

Worterklärungen:
Scope: Anderes Wort für "Bereich" im besonderen Fall "Gültigkeitsbereich".
Referenz: Adresse der Speicherstelle, wo der Wert einer Variable gespeichert ist.

Artikel

Parameterübergabe:
Als Parameterübergabe wird der Vorgang bezeichnet, Daten an eine Methode weiterzureichen um diese in einer Methode zur Verfügung zu haben.
Entweder benutzt man diese Daten, um damit zu zu arbeiten und einen daraus folgenden Wert per "return" zurückzugeben oder auch um innerhalb der Methode ein Objekt zu benutzen, z.B. eine Node in einem TreeView einzufügen.

Parameter / Argumente:
Als Parameter wird der Weg bezeichnet, der im Methodenaufruf übergeben wird.
Die Werte, die in der Methode entgegengenommen werden, sind Argumente.

Übergabearten
:
In C# gibt es drei verschiedene Möglichkeiten, um eine, bzw. mehrere Variablen zu übergeben.


Standardmässig wird vor dem Parameter kein Schlüsselwort angegeben. Dies ist eine Übergabe als Wert (by value):

// Aufruf der Methode mit Parameterübergabe (by value)
MeineMethode(meinParameter);


Übergabe als Referenz (by reference):

// Aufruf der Methode mit Parameterübergabe (by reference)
MeineMethode(ref meineVariable);


Übergabe als Ergebnisparameter:

// Aufruf der Methode mit Ergebnisparameterübergabe (Schlüsselwort: out)
MeineMethode(out meinErgebnisParameter);



Hinweis:
Die Schlüsselwörter "ref" und "out" müssen bei der Methodendefinition sowie auch beim Methodenaufruf deklariert werden.


Viele von euch werden vor allem von der ersten Übergabeart Gebrauch gemacht haben.
Ihr werdet euch jetzt evt. denken "Sieht doch alles praktisch gleich aus, wo liegt da der Unterschied?"
Es sieht wirklich fast gleich aus, die Unterschiede sind aber enorm und es ist gut sich über die Hintergründe zu informieren.

Verhalten bei Wertetypen:
Bei Wertetypen wird der Parameter im Normalfall, also ohne ein zusätzliches Schlüsselwort als Kopie des Wertes übergeben.
Mit dem Schlüsselwort "ref" vor dem Parameter wird die Referenz auf die Variable übergeben.
Das Argument kann anschliessend in der Methode verändert werden und  die Änderung wirken sich auch auf die ursprüngliche Variable in der höheren Ebene (Scope) aus.
Wird hingegen das Schlüsselwort "out" verwendet, passiert das gleiche wie beim "ref" Schlüsselwort, mit dem Unterschied dass der übergebenen Variable beim Prozeduraufruf noch kein Wert zugewiesen werden muss, diese also noch nicht initialisiert sein muss.

Die Übergabe mit dem Schlüsselwort "out" wird verwendet, um mehrere Methodenergebnisse zurückzugeben, was anders nicht möglich bzw. nur mit Umwegen über eine Datenhaltungsklasse / -Struktur möglich wäre. Die übergebene Variable muss in diesem Fall nicht initialisiert werden.
Das Argument ist, wie bei der Übergabe per Referenz, eine Referenz auf den Wert.
Viele Funktionen der WinAPI machen z.B. von dieser Übergabeart Gebrauch.

Übergabe als Wert (by value)

// Variable deklarieren und initialisieren:
int meineVariable = 1;


// Aufruf unserer Methode und Ausgabe unserer Variable in der Konsole
meineMethode(meineVariable);
Console.WriteLine(meineVariable.ToString()); // Gibt 1 aus

// Deklarierung unserer Methode und Gebrauch des Arguments
public void MeineMethode(int meinArgument) {
   // Wir weisen unserem Argument einen neuen Wert zu.
  // Diese Änderung ist nur innerhalb unserer Methode gültig und hat keine Auswirkungen auf unsere ursprüngliche Variable
  // da nur eine Kopie des Wertes übergeben wird, die ausser dem gleichen Wert nichts mit unserer ursprünglichen Variable zu tun hat!
  meinArgument = 4;
}

Übergabe als Referenz (by reference)

// Variable deklarieren und initialisieren:
int meineVariable = 1;


// Aufruf unserer Methode und anschliessende Ausgabe unserer Variable in der Konsole meineMethode(ref meineVariable);
MeineMethode(ref meineVariable);
Console.WriteLine(meineVariable.ToString()); // Gibt 4 aus

// Deklarierung unserer Methode und Veränderung des Arguments
public void MeineMethode(ref int meinArgument) {
  // Wir weisen unserem Argument den Wert 4 zu. Da per "ref" Schlüsselwort die Referenz (Zeiger) auf unsere Variable "meineVariable" übergeben wird,
  // ändert sich auch der Wert unserer Variable.
  meinArgument = 4;
}

Übergabe mit Ergebnisparameter (Schlüsselwort: out)

// Deklarierung, jedoch keine Initialisierung unserer Ergebnisparameter
string meinName;
int meinAlter;


// Aufruf unserer Methode
MeineMethode(out meinName, out meinAlter);
Console.WriteLine(meinName); // Gibt Peter aus
Console.WriteLine(meinAlter); // Gibt 23 aus

// Deklarierung unserer Methode und Zuweisung der gewünschten Werte
public void MeineMethode(out string meinNameArgument, out int meinAlterArgument) {
  // Zuweisen der Werte an unsere Argumente
  // Wir weisen unseren Argumenten jeweils einen Wert zu. Unsere Variablen, die wir vorher definiert haben, übernehmen diesen Wert,
  // da die Referenz übergeben worden ist
  meinNameArgument= "Peter";
  meinAlterArgument = 23;
}


Zusammenfassung
:

Übergabe als Wert (by value) "ohne Schlüsselwort":
- Kopie des Wertes wird übergeben
- Keine Auswirkungen auf den Ursprünglichen Wert unserer Variable
- Wird am häufigsten benutzt, nämlich dann, wenn die Argumente von der Methode nicht verändert werden, oder um mit einem Objekt zu arbeiten, ohne die Referenz zu überschreiben (siehe unten).

Übergabe als Referenz (by reference) "ref":
- Referenz auf Variable wird übergeben
- Ursprüngliche Variable ändert sich, wenn wir das Argument in der Methode ändern
- Die Referenz unserer Variable und unseres Arguments zeigen auf den gleichen Wert.
- Wird für Argumente gebraucht, die von der aufgerufenen Methode verändert werden sollen.
- Übergabe als Referenz ist schneller, da hier die Daten an sich nicht kopiert werden müssen, sondern nur die Referenz.

Übergabe als Ergebnisparameter "out":
- Alle genannten Punkte bei "Übergabe als Referenz" stimmen auch hier
- Übergebene Variable muss nicht initialisiert sein
- Gebrauch als Ergebnisparameter

Eine Methode kann nur einen Rückgabetyp und einen Rückgabewert haben.
Mit Hilfe der Ergebnisparameter können wir, wie im Beispiel oben gezeigt,  mehrere Werte verschiedener Typen auf einen Schlag zurückgeben.

Es gibt natürlich, wie meistens :), noch andere Wege um mehrere Werte zurückzugeben. Man kann sich z.B. eine Struktur "Schlüsselwort: struct" oder eine Datenhaltungsklasse mit mehreren Feldern unterschiedlicher oder gleicher Typen definieren und diese/s dann als Rückgabetyp angeben.

Verhalten bei Referenztypen:
Bei den Referenztypen ist das Verhalten bei der Übergabe von Parameter kniffliger.
Wenn dieses nicht bekannt ist, kann es leicht zu logischen Fehlern im Programm kommen.
Das Verhalten bei der Übergabe als Referenz ist gleich wie bei den Wertetypen, es wird also die Referenz übergeben und es gilt das gleiche wie oben beschrieben. Man kann einem Referenztyp, der mithilfe von "ref" übergeben wird, auch ein vollkommen neues Objekt zuweisen.
Nach dieser Zuweisung zeigt die Referenz im Ursprungsscope dann auch auf das neue Objekt.
Bei der Benutzung vom Schlüsselwort "out" gilt dasselbe.

Bei der standardmässigen Übergabe eines Referenztypes an eine Methode müssen wir beachten, dass eine Kopie der Referenz auf unser Objekt übergeben wird.
Mit dem Objekt können wir noch genau gleich arbeiten wie auch im Ursprungsscope und den Eigenschaften Werte zuweisen.
Das normal erwartete Verhalten also.

Der Haken an der Geschichte ist aber folgender:
Wenn wir unserem Argument innerhalb der Methode ein anderes Objekt zuweisen, zeigt zwar die Kopie der Referenz auf das neue Objekt, jedoch nicht die ursprüngliche Referenz. Wir verlieren beim Verlassen der Methode die Referenz auf unser soeben zugewiesenes Objekt.
Denn diese Kopie der Referenz ist nur temporär innerhalb der Methode zugänglich.

Es wird also damit die ursprüngliche, originale Referenz vom Überschreiben geschützt.

Der Clou ist also: Wenn wir innerhalb der Methode ein neues Objekt zuweisen wollen, müssen wir die Referenz per Referenz "ref" oder out an die Methode übergeben. Alles klar?

Nicht?
Okay, ein kleines Konsolenprogramm als Beispiel wie man es nicht machen soll, in diesem Fall sollte man das Schlüsselwort "ref" benutzen:

using System;


namespace ParameteruebergabeInCSharp {
    class Program {
        static void Main(string[] args) {
            // Wir erstellen unser Objekt und füllen die Eigenschaft "Name"
            Mensch m = new Mensch();
            m.Name = "Peter";

            // Wir rufen unsere Funktion auf und übergeben unser Mensch Objekt
            // an die Methode
            Foo(m);

            // Hier wird "Peter" ausgegeben
            Console.WriteLine(m.Name);

            Console.Read();
        }

        static void Foo(Mensch MenschArgument) {
            /*
             das Argument "MenschArgument" ist jetzt eine Kopie der Referenz
             auf unser Mensch Objekt "m"
             Jetzt weisen wir / referenzieren wir auf ein neues Mensch Objekt
             Und weisen ihm einen neuen Wert zu.
            
             Folgendes passiert:
             Beim referenzieren zeigt die Referenz des "MenschArgument" jetzt auf unser
             soeben neu erstelltes "Mensch"-Objekt.
             Dieses Referenz ist aber nur bis zum Ende der Methode gültig und genau
             das ist jetzt unser Problem.
             Wir verlieren die Referenz und den Wert.
            */

            MenschArgument = new Mensch(); // <-- neues Objekt zuweisen, referenzieren
            MenschArgument.Name = "Stefan"; // <-- Wert zuweisen
        }
    }

    class Mensch {
        private string _name;

        public string Name {
            get { return this._name; }
            set { this._name = value; }
        }
    }
}


Die korrekte Anwendung wäre in diesem Fall:

Foo(ref m);

static void Foo(ref Mensch MenschArgument) {
    //...
}


Ich habe noch eine Bilderfolge erstellt, damit man sich besser vorstellen kann, was bei einer Parameterübergabe mit Referenztypen genau passiert.

Nachtrag:
Um sich besser vorzustellen, was die ganze Geschichte bei der "täglichen Übergabe" bedeutet, habe ich noch eine kleine Tabelle erstellt.
Je nach Übergabeart der Parameter können diese in der Methode nur gelesen werden, gelesen und verändert werden oder nur verändert werden.
Besonders interessant an der unteren Darstellung ist das Verhalten bei Referenztypen bei der Übergabeart "by value".

 

Übergabe von... ...Wertetypen ...Pointer (Referenz) auf Objekt
by value Wert ist read-only Referenz ist read-only, das Objekt jedoch read-and-write
by reference Wert ist read-and-write Referenz und Objekt sind read-and-write
out Wert ist write-only Referenz und Objekt sind write-only

 

Vielen Dank für alle, die bis jetzt durchgehalten haben ;-)
Ich hoffe bei euch mit diesem Artikel evt. eine Wissenslücke ausgefüllt zu haben und freue mich über sämtliche Kommentare.
Natürlich nehme ich auch gerne konstruktive Kritik von euch an.

Weiterführende Links:
- Übergeben von Verweistypparametern (C#-Programmierhandbuch)
- Übergeben von Arrays mithilfe von "ref" und "out" (C#-Programmierhandbuch)
- http://msdn2.microsoft.com/de-de/library/14akc2c7(VS.80).aspx (ref MSDN)
- http://msdn2.microsoft.com/de-de/library/t3c3bfhx(VS.80).aspx (out MSDN)

Quellen:
myCSharp.de: C# und Übergabemechanismen: call by value vs. call by reference (ref/out)
Microsoft Newsgroups
Internet generell

Dank geht an:
Norbert Eder für Bestätigungen meiner Nachforschungen
Meine Schwester Susann für Ihre Ideen und Verbesserungsvorschläge

Bearbeitung / Korrekturen:
13.02.07 - Rechtschreibefehler, Bild bearbeitet, Text korrigiert
14.02.07 - Übergabetabelle und kurze Erklärung hinzugefügt

Veröffentlicht Montag, 12. Februar 2007 19:47 von Peter Bucher

Kommentare

# re: Artikel - Parameterübergabe in C# (by value, by reference und out)

Richtig geile Ausarbeitung, super Arbeit. Da muss ich gleich 5 Sterne geben :))

Mittwoch, 14. Februar 2007 00:41 by Stefan Falz

# re: Artikel - Parameterübergabe in C# (by value, by reference und out)

Ein sehr gelungener Grundlagenartikel der bestimmt vielen behilflich sein wird. Weiter so! Gefällt mir sehr gut.

Mittwoch, 14. Februar 2007 07:20 by Norbert Eder

# Parameter&uuml;bergabe in C#

Mittwoch, 14. Februar 2007 07:39 by BlaBlubBlog

# re: Artikel - Parameterübergabe in C# (by value, by reference und out)

Super Peter,

toller Artikel und sehr einfach erklärt.

Gruss

Andreas

Donnerstag, 15. Februar 2007 11:49 by plongo_291

# re: Artikel - Parameterübergabe in C# (by value, by reference und out)

Respekt! Dieser Artikel gefällt auch mir außergewöhnlich gut. :)

Freitag, 16. Februar 2007 01:49 by Alexander Gißibl

# re: Artikel - Parameterübergabe in C# (by value, by reference und out)

nice! eine 5!

Dienstag, 20. Februar 2007 15:01 by gabru

# Alles neu oder was?

Info &amp; Statistiken Ja, es gibt Neuigkeiten auf meinem Blog, aber es ist nicht alles neu ;-) Unter

Freitag, 24. August 2007 01:45 by Peter Bucher

# re: Artikel: Parameterübergabe in C# (by value, by reference und out)

Hi Peter,

sehr gute und vor allem super ausführliche und verständliche Erklärung :-)!

Danke dafür!

Viele Grüße,

Golo

Donnerstag, 9. Juli 2009 09:37 by Golo Roden

# re: Artikel: Parameterübergabe in C# (by value, by reference und out)

Hoi Golo

Vielen Dank, das freut mich sehr :-)

Grüsse Peter

Donnerstag, 9. Juli 2009 16:40 by Peter Bucher
Anonyme Kommentare sind nicht zugelassen