Parameterübergabe

Aus Das Sopra Wiki
Wechseln zu: Navigation, Suche



Generell gibt es in Programmiersprachen unterschiedliche Konzepte zur Behandlung von zu übergebenden Parametern. In C# werden diese Konzepte Call by Value und Call by Reference genannt. Dieser Artikel erklärt die Unterschiede zwischen beiden Arten und deren Verwendung.

Call by Value

In C# geschehen Parameterübergaben bei Funktionsaufrufen (genauso wie z.B. bei Java) implizit immer in Form von Call by Value. Das heißt, dass übergebene Variablen für die Verwendung in Methoden kopiert werden.

Hier ist ein Codebeispiel dazu:

public class Test
{
  public int number;

  public Test(int number)
  {
    this.number = number;
  }
}

public class MyApp
{
  public static void Main()
  {
    Test test = new Test(7);
    ChangeNumber1(test);
    System.Console.WriteLine(test.number);  // gibt 7 aus
    ChangeNumber2(test);
    System.Console.WriteLine(test.number);  // gibt 4 aus
  }

  public void ChangeNumber1(Test test)
  {
    test = new Test(3);
  }
  public void ChangeNumber2(Test test)
  {
    test.number = 4;
  }
}

Warum ändert die Methode ChangeNumber1 nun bei der Ausgabe die Zahl nicht? Zur kurzen Wiederholung: Dieses Codebeispiel verwendet Klassen. Da deren Instanzen reference types sind, wird beim Aufruf der Methode nicht direkt die Instanz der Klasse übergeben sondern nur eine Referenz. Da Parameter implizit mit Call by Value übergeben werden, also für die Methode kopiert werden, erhält die Methode quasi eine neue Referenz, welche lediglich auf die selbe Instanz der Klasse verweist wie die ursprüngliche Referenz die beim Methodenaufruf verwendet wurde. Innerhalb der Methode wird die neue Referenz dann auf eine andere Instanz der Klasse Test geändert. Beim Verlassen der Methode wird die Referenz dann automatisch gelöscht, die neue Instanz der Klasse Test mit dem Wert 3 damit also auch, da sie nicht mehr referenziert wird.

Die Methode ChangeNumber2 greift dagegen über die kopierte Referenz auf die selbe Klasseninstanz zurück, auf die die ursprüngliche Referenz ebenfalls verweist. Daher wird hier der Zahlenwert geändert und anschließend auch entsprechend ausgegeben.

Das selbe Prinzip gilt ähnlich auch für value types. Bei diesen wird beim Übergeben allerdings nicht nur eine Referenz sondern der komplette Wert kopiert. Dies ist ein wichtiger Unterschied zwischen Structs und Klassen, da Structs value types sind. Beim Übergeben von Structs wird also der komplette Inhalt des Structs kopiert! Je nach Größe des Structs kann hierbei einiges an Arbeit anfallen, weshalb man bei der Verwendung von Structs auf die Performanz achten sollte. Hier noch ein Beispiel für das Verhalten von value types bei Übergabe mit call by value anhand eines Structs:

public class MyApp
{
  public static void Main()
  {
    Vector3 test = new Vector3(7.0f);
    ChangeVector1(test);
    System.Console.WriteLine(test);  // gibt {X:7 Y:7 Z:7} aus
    ChangeVector2(test);
    System.Console.WriteLine(test);  // gibt auch {X:7 Y:7 Z:7} aus
  }

  public void ChangeVector1(Vector3 vector)
  {
    vector = new Vector3(3.0);
  }
  public void ChangeVector2(Vector3 vector)
  {
    vector.x = 4.0;
    vector.y = 4.0;
    vector.z = 4.0;
  }
}

Zu beachten ist, dass call by value implizit auch für einfache Zuweisungen gilt:

//Klassen
Test class1 = new Test(2);
Test class2 = class1; //class2 ist jetzt eine kopierte Referenz, die auf die selbe Instanz von Test zeigt wie class1
System.Console.WriteLine(class1.number); //gibt 2 aus
System.Console.WriteLine(class2.number); //gibt 2 aus
class2.number = 4;
System.Console.WriteLine(class1.number); //gibt 4 aus
System.Console.WriteLine(class2.number); //gibt 4 aus
class2 = new Test(7); //Die Referenz class2 zeigt jetzt auf eine neue Instanz der Klasse Test
System.Console.WriteLine(class1.number); //gibt 4 aus
System.Console.WriteLine(class2.number); //gibt 7 aus

//Structs
Vector3 struct1 = new Vector3(2.0f);
Vector3 struct2 = struct1; //struct2 ist jetzt eine neue Struct mit den selben Werten wie struct1
System.Console.WriteLine(struct1); //gibt {X:2 Y:2 Z:2} aus
System.Console.WriteLine(struct2); //gibt {X:2 Y:2 Z:2} aus
struct2.x = 4.0f;
System.Console.WriteLine(struct1); //gibt {X:2 Y:2 Z:2} aus
System.Console.WriteLine(struct2); //gibt {X:4 Y:2 Z:2} aus

Call by Reference

Implizit gilt in C# also call by value für Parameterübergaben, es kann allerdings auch explizit ein call by reference erzwungen werden. Dazu gibt es das Schlüsselwort ref, welches vor die Parameter einer Methode geschrieben werden kann. Dies hat dann zur Folge dass keine Kopie, sondern direkt die Referenz auf die entsprechende Klasse oder auch den value type (z.B. ein Struct) übergeben wird. Schauen wir uns die call by value Code-Beispiele mit der Verwendung von call by reference an:

public class Test
{
  public int number;

  public Test(int number)
  {
    this.number = number;
  }
}

public class MyApp
{
  public static void Main()
  {
    Test test = new Test(7);
    ChangeNumber1(ref test);
    System.Console.WriteLine(test.number);  // gibt 3 aus
    ChangeNumber2(ref test);
    System.Console.WriteLine(test.number);  // gibt 4 aus
  }

  public void ChangeNumber1(ref Test test)
  {
    test = new Test(3);
  }
  public void ChangeNumber2(ref Test test)
  {
    test.number = 4;
  }
}
public class MyApp
{
  public static void Main()
  {
    Vector3 test = new Vector3(7.0f);
    ChangeVector1(ref test);
    System.Console.WriteLine(test);  // gibt {X:3 Y:3 Z:3} aus
    ChangeVector2(ref test);
    System.Console.WriteLine(test);  // gibt auch {X:4 Y:4 Z:4} aus
  }

  public void ChangeVector1(ref Vector3 vector)
  {
    vector = new Vector3(3.0);
  }
  public void ChangeVector2(ref Vector3 vector)
  {
    vector.x = 4.0;
    vector.y = 4.0;
    vector.z = 4.0;
  }
}

Call by reference funktioniert also für value types genauso wie für reference types. Value types lassen sich somit effizienter übergeben, da keine Kopien mehr erstellt werden müssen. Allerdings muss man aufpassen dass man beim Ändern eines so übergebenen value types direkt das ursprüngliche Objekt ändert, was standardmäßig nicht der Fall ist.

Rückgabeparameter

Da die Rückgabewerte von Methoden einer Variablen zugewiesen werden müssen gilt auch für diese call by value, also dass sie kopiert werden. Zudem kann nur ein Wert von einer Methode zurückgegeben werden. Um dies umgehen zu können gibt es in C# zusätzlich zu ref auch noch das Schlüsselwort out für Parameter. Dieses dient zur Deklaration von Rückgabeparametern. Damit ist es nicht nur möglich neben der Parameterübergabe auch die Werterückgabe performanter zu gestalten sondern zusätzlich können auch beliebig viele Werte von einer Methode zurückgegeben werden, ohne dass eine spezielle Klasse zur Datenhaltung benötigt wird.

Theoretisch können auch Parameter, die mit ref übergeben werden, zur Rückgabe von Werten verwendet werden. Der Unterschied zu out Parametern ist allerdings, dass ref Parameter im Voraus immer initialisiert sein müssen, während out Parameter auch ohne Initialisierung übergeben werden können:

public class Test
{
  public int number;

  public Test(int number)
  {
    this.number = number;
  }
}

public class MyApp
{
  public static void Main()
  {
    Test test;

    GiveMeANewTest1(ref test); // Compile error: Use of unassigned local variable 'test'

    GiveMeANewTest2(out test); // Ok

    System.Console.WriteLine(test.number);  // gibt 5 aus, wenn keine Compiler-Fehler mehr vorhanden sind
  }

  public void GiveMeANewTest1(ref Test result)
  {
    result = new Test(5);
  }
  public void GiveMeANewTest2(out Test result)
  {
    result = new Test(5);
  }
}