Methodenparameter

Standardmäßig werden Argumente in C# nach Wert an Funktionen übergeben. Das bedeutet, dass eine Kopie der Variablen an die Methode übergeben wird. Bei Werttypen (struct) wird eine Kopie des Werts an die Methode übergeben. Für Verweistypen (class) wird eine Kopie des Verweises an die Methode übergeben. Parametermodifizierer ermöglichen es Ihnen, Argumente nach Verweis zu übergeben. Die folgenden Konzepte helfen Ihnen, diese Unterschiede und die Verwendung der Parametermodifizierer zu verstehen:

  • Nach Wert übergeben bedeutet, dass eine Kopie der Variablen an die Methode übergeben wird.
  • Durch Verweis übergeben bedeutet, dass der Zugriff auf die Variable an die Methode übergeben wird.
  • Eine Variable eines Verweistyps enthält einen Verweis auf ihre Daten.
  • Eine Variable eines Werttyps enthält ihre Daten direkt.

Da eine Struktur ein Werttyp ist, erhält und funktioniert die Methode nur mit einer Kopie des Strukturarguments, wenn Sie eine Struktur nach Wert an eine Methode übergeben. Die Methode hat keinen Zugriff auf die ursprüngliche Struktur in der aufrufenden Methode und kann sie deshalb nicht ändern. Die Methode kann nur die Kopie ändern.

Eine Klasseninstanz ist ein Verweistyp, kein Werttyp. Wenn ein Verweistyp als Wert an eine Methode übergeben wird, erhält die Methode eine Kopie des Verweises auf die Klasseninstanz. Beide Variablen beziehen sich auf dasselbe Objekt. Der Parameter ist eine Kopie des Verweises. Die aufgerufene Methode kann die Instanz in der aufrufenden Methode nicht neu zuweisen. Die aufgerufene Methode kann jedoch die Kopie des Verweises verwenden, um auf die Instanzmember zuzugreifen. Wenn die aufgerufene Methode ein Instanzmember ändert, sieht die aufrufende Methode auch diese Änderungen, da sie auf dieselbe Instanz verweist.

Die Ausgabe des folgenden Beispiels veranschaulicht den Unterschied. Die Methode „ClassTaker“ ändert den Wert des Felds „willIChange“, da die Methode die Adresse im Parameter verwendet, um das angegebene Feld der Klasseninstanz zu finden. Das Feld „willIChange“ der Struktur in der aufrufenden Methode wird nicht durch den Aufruf auf die Methode „StructTaker“ geändert, da der Wert des Arguments eine Kopie der Struktur selbst ist und keine Kopie deren Adresse. StructTaker ändert die Kopie, und die Kopie wird abgebrochen, wenn der Aufruf auf StructTaker abgeschlossen ist.

class TheClass
{
    public string? willIChange;
}

struct TheStruct
{
    public string willIChange;
}

class TestClassAndStruct
{
    static void ClassTaker(TheClass c)
    {
        c.willIChange = "Changed";
    }

    static void StructTaker(TheStruct s)
    {
        s.willIChange = "Changed";
    }

    public static void Main()
    {
        TheClass testClass = new TheClass();
        TheStruct testStruct = new TheStruct();

        testClass.willIChange = "Not Changed";
        testStruct.willIChange = "Not Changed";

        ClassTaker(testClass);
        StructTaker(testStruct);

        Console.WriteLine("Class field = {0}", testClass.willIChange);
        Console.WriteLine("Struct field = {0}", testStruct.willIChange);
    }
}
/* Output:
    Class field = Changed
    Struct field = Not Changed
*/

Kombinationen von Parametertyp und Argumentmodus

Welche am Argument vorgenommenen Änderungen für den Aufrufer sichtbar sind, wird dadurch gesteuert, wie ein Argument übergeben wird und ob es sich um einen Verweistyp oder einen Werttyp handelt:

  • Folgendes geschieht beim Übergeben eines Werttypsnach Wert:
    • Wenn die Methode den Parameter so zuweist, dass er auf ein anderes Objekt verweist, sind diese Änderungen für den Aufrufer nicht sichtbar.
    • Wenn die Methode den Zustand des Objekts ändert, auf das durch den Parameter verwiesen wird, sind diese Änderungen für den Aufrufer nicht sichtbar.
  • Folgendes geschieht beim Übergeben eines Verweistypsnach Wert:
    • Wenn die Methode den Parameter so zuweist, dass er auf ein anderes Objekt verweist, sind diese Änderungen für den Aufrufer nicht sichtbar.
    • Wenn die Methode den Zustand des Objekts ändert, auf das durch den Parameter verwiesen wird, sind diese Änderungen für den Aufrufer sichtbar.
  • Folgendes geschieht beim Übergeben eines Werttypsdurch Verweis:
    • Wenn die Methode den Parameter so zuweist, dass er auf ein anderes Objekt verweist, sind diese Änderungen für den Aufrufer nicht sichtbar.
    • Wenn die Methode den Zustand des Objekts ändert, auf das durch den Parameter verwiesen wird, sind diese Änderungen für den Aufrufer sichtbar.
  • Folgendes geschieht beim Übergeben eines Verweistypsdurch Verweis:
    • Wenn die Methode den Parameter so zuweist, dass er auf ein anderes Objekt verweist, sind diese Änderungen für den Aufrufer sichtbar.
    • Wenn die Methode den Zustand des Objekts ändert, auf das durch den Parameter verwiesen wird, sind diese Änderungen für den Aufrufer sichtbar.

Die Übergabe eines Verweistyps als Verweis ermöglicht es der aufgerufenen Methode, das Objekt, auf die der Verweisparameter im Aufrufer verweist, zu ersetzen. Der Speicherort des Objekts wird als Wert des Verweisparameters an die Methode übergeben. Wenn Sie den Wert am Speicherort des Parameters ändern (um auf ein neues Objekt zu verweisen), ändern Sie auch den Speicherort, auf den der Aufrufer verweist. Im folgenden Beispiel wird eine Instanz eines Verweistyps als ein ref-Parameter übergeben.

class Product
{
    public Product(string name, int newID)
    {
        ItemName = name;
        ItemID = newID;
    }

    public string ItemName { get; set; }
    public int ItemID { get; set; }
}

private static void ChangeByReference(ref Product itemRef)
{
    // Change the address that is stored in the itemRef parameter.
    itemRef = new Product("Stapler", 12345);
}

private static void ModifyProductsByReference()
{
    // Declare an instance of Product and display its initial values.
    Product item = new Product("Fasteners", 54321);
    System.Console.WriteLine("Original values in Main.  Name: {0}, ID: {1}\n",
        item.ItemName, item.ItemID);

    // Pass the product instance to ChangeByReference.
    ChangeByReference(ref item);
    System.Console.WriteLine("Calling method.  Name: {0}, ID: {1}\n",
        item.ItemName, item.ItemID);
}

// This method displays the following output:
// Original values in Main.  Name: Fasteners, ID: 54321
// Calling method.  Name: Stapler, ID: 12345

Sicherer Kontext von Verweisen und Werten

Methoden können Parameterwerte in Feldern speichern. Wenn Parameter nach Wert übergeben werden, ist dies in der Regel sicher. Werte werden kopiert, und Verweistypen sind erreichbar, wenn sie in einem Feld gespeichert sind. Das sichere Übergeben von Parametern durch Verweis erfordert, dass der Compiler angibt, wann es sicher ist, einen Verweis einer neuen Variable zuzuweisen. Für jeden Ausdruck definiert der Compiler einen sicheren Kontext, der den Zugriff auf einen Ausdruck oder eine Variable begrenzt. Der Compiler verwendet zwei Bereiche: safe-context und ref-safe-context.

  • safe-context definiert den Bereich, in dem auf einen beliebigen Ausdruck sicher zugegriffen werden kann.
  • ref-safe-context definiert den Bereich, in dem auf einen Verweis auf einen beliebigen Ausdruck sicher zugegriffen oder der Verweis geändert werden kann.

Sie können sich diese Bereiche als den Mechanismus vorstellen, mit dem sichergestellt wird, dass Ihr Code niemals auf einen nicht mehr gültigen Verweis zugreift oder ihn ändert. Ein Verweis ist solange gültig, wie er auf ein gültiges Objekt oder „struct“ verweist. safe-context definiert, wann eine Variable zugewiesen oder neu zugewiesen werden kann. ref-safe-context gibt an, wann eine Variable ref zugewiesen oder ref neu zugewiesen werden kann. Durch die Zuweisung wird eine Variable einem neuen Wert zugewiesen. ref assignment weist die Variable so zu, dass sie auf einen anderen Speicherort verweist.

Parameter referenzieren

Sie wenden einen der folgenden Modifizierer auf eine Parameterdeklaration an, um Argumente anhand des Verweises anstelle des Werts zu übergeben:

  • ref: Das Argument muss vor dem Aufrufen der Methode initialisiert werden. Die Methode kann dem Parameter einen neuen Wert zuweisen, es ist jedoch nicht erforderlich.
  • out: Die aufrufende Methode ist nicht erforderlich, um das Argument vor dem Aufrufen der Methode zu initialisieren. Die Methode muss dem Parameter einen Wert zuweisen.
  • readonly ref: Das Argument muss vor dem Aufrufen der Methode initialisiert werden. Die Methode kann dem Parameter keinen neuen Wert zuweisen.
  • in: Das Argument muss vor dem Aufrufen der Methode initialisiert werden. Die Methode kann dem Parameter keinen neuen Wert zuweisen. Der Compiler erstellt möglicherweise eine temporäre Variable, um eine Kopie des Arguments in Parameter „in“ zu enthalten.

Member einer Klasse können keine Signaturen haben, die sich nur durch ref, ref readonly, in oder out voneinander unterscheiden. Es tritt ein Compilerfehler auf, wenn der einzige Unterschied zwischen beiden Member eines Typs der ist, dass einer von ihnen über einen ref-Parameter und der andere über einen out-, ref readonly- oder in-Parameter verfügt. Allerdings können Methoden überladen werden, wenn eine Methode einen ref-, ref readonly-, in- oder out-Parameter hat und die andere wie im folgenden Beispiel dargestellt über einen Parameter verfügt, der als Wert übergeben wird. In anderen Situationen, die eine Signaturabstimmung benötigen, z.B. beim Ausblenden oder Überschreiben, sind in, ref, ref readonly und out Bestandteil der Signatur und passen nicht zueinander.

Wenn ein Parameter über einen der vorherigen Modifizierer verfügt, kann das entsprechende Argument einen kompatiblen Modifizierer haben:

  • Ein Argument für einen ref-Parameter muss den ref-Modifizierer enthalten.
  • Ein Argument für einen out-Parameter muss den out-Modifizierer enthalten.
  • Ein Argument für einen in-Parameter kann optional den in-Modifizierer enthalten. Wenn der ref-Modifizierer stattdessen für das Argument verwendet wird, gibt der Compiler eine Warnung aus.
  • Ein Argument für einen ref readonly-Parameter sollte entweder den in-Modifizierer oder den ref-Modifizierer enthalten, aber nicht beide. Wenn kein Modifizierer enthalten ist, gibt der Compiler eine Warnung aus.

Wenn Sie diese Modifizierer verwenden, beschreiben sie, wie das Argument verwendet wird:

  • ref“ bedeutet, dass die Methode den Wert des Arguments lesen oder schreiben kann.
  • out“ bedeutet, dass die Methode den Wert des Arguments festlegt.
  • ref readonly“ bedeutet, dass die Methode liest, aber den Wert des Arguments nicht schreiben kann. Das Argument sollte nach Verweis übergeben werden.
  • in“ bedeutet, dass die Methode liest, aber den Wert des Arguments nicht schreiben kann. Das Argument wird nach Verweis oder über eine temporäre Variable übergeben.

Eigenschaften sind keine Variablen. Sie sind Methoden und können nicht an ref-Parameter übergeben werden. Sie können die vorherigen Parametermodifizierer nicht in den folgenden Methodenarten verwenden:

  • Asynchrone Methoden, die Sie mit dem async-Modifizierer definieren.
  • Iterator-Methoden, die eine yield return- oder yield break-Anweisung enthalten.

Erweiterungsmethoden unterliegen auch Einschränkungen hinsichtlich der Verwendung dieser Argumentschlüsselwörter:

  • Das out-Schlüsselwort kann nicht für das erste Argument einer Erweiterungsmethode verwendet werden.
  • Das ref-Schlüsselwort kann nicht für das erste Argument einer Erweiterungsmethode verwendet werden, wenn es sich bei dem Argument nicht um eine struct handelt oder wenn es einen generischen Typ aufweist, der nicht auf eine Struktur beschränkt ist.
  • Das ref readonly-Schlüsselwort und das in-Schlüsselwort können nur verwendet werden, wenn das erste Argument eine struct ist.
  • Das ref readonly-Schlüsselwort und das in-Schlüsselwort können nicht für einen generischen Typ verwendet werden – selbst bei einer Beschränkung auf eine Struktur.

Modifizierer für ref-Parameter

Um einen ref-Parameter zu verwenden, müssen sowohl die Methodendefinition als auch die aufrufende Methode explizit das Schlüsselwort ref verwenden, wie im folgenden Beispiel gezeigt. (Mit der Ausnahme, dass die aufrufende Methode ref auslassen kann, wenn ein COM-Aufruf getätigt wird.)

void Method(ref int refArgument)
{
    refArgument = refArgument + 44;
}

int number = 1;
Method(ref number);
Console.WriteLine(number);
// Output: 45

Ein Argument, das an einen ref-Parameter übergeben wird, muss vor der Übergabe initialisiert werden.

Modifizierer für out-Parameter

Um einen Parameter out zu verwenden, müssen sowohl die Methodendefinition als auch die aufrufende Methode das Schlüsselwort out explizit verwenden. Beispiel:

int initializeInMethod;
OutArgExample(out initializeInMethod);
Console.WriteLine(initializeInMethod);     // value is now 44

void OutArgExample(out int number)
{
    number = 44;
}

Variablen, die als out-Argumente übergeben wurden, müssen nicht initialisiert werden, bevor sie in einen Methodenaufruf übergeben werden. Die aufgerufene Methode ist jedoch erforderlich, um einen Wert zuzuweisen, bevor die Methode zurückgegeben wird.

Dekonstruktionsmethoden deklarieren ihre Parameter mit dem out-Modifizierer, um mehrere Werte zurückzugeben. Andere Methoden können Werttupel für mehrere Rückgabewerte zurückgeben.

Sie können eine Variable in einer separaten Anweisung deklarieren, bevor Sie sie als out-Argument übergeben. Sie können die out-Variable auch in der Argumentliste des Methodenaufrufs deklarieren anstatt in einer separaten Variablendeklaration. out Variablendeklarationen erzeugen kompakteren, lesbaren Code und verhindern, dass Sie der Variablen versehentlich vor dem Methodenaufruf einen Wert zuweisen. Das folgende Beispiel definiert die number-Variable im Aufruf der Methode Int32.TryParse.

string numberAsString = "1640";

if (Int32.TryParse(numberAsString, out int number))
    Console.WriteLine($"Converted '{numberAsString}' to {number}");
else
    Console.WriteLine($"Unable to convert '{numberAsString}'");
// The example displays the following output:
//       Converted '1640' to 1640

Sie können auch eine implizit typierte lokale Variable deklarieren.

ref readonly-Modifizierer

Der ref readonly-Modifizierer muss in der Methodendeklaration vorhanden sein. Ein Modifizierer an der Aufrufstelle ist optional. Entweder kann der in- oder der ref-Modifizierer verwendet werden. Der ref readonly-Modifizierer ist an der Aufrufstelle nicht gültig. Welchen Modifizierer Sie an der Aufrufstelle verwenden, kann dabei helfen, die Merkmale des Arguments zu beschreiben. Sie können nur ref verwenden, wenn das Argument eine Variable und schreibbar ist. Sie können nur in verwenden, wenn das Argument eine Variable ist. Es kann schreibbar oder schreibgeschützt sein. Sie können keinen Modifizierer hinzufügen, wenn das Argument keine Variable, aber ein Ausdruck ist. In den folgenden Beispielen werden diese Bedingungen veranschaulicht. Die folgende Methode verwendet den ref readonly-Modifizierer, um anzugeben, dass eine große Struktur aus Leistungsgründen nach Verweis übergeben werden soll:

public static void ForceByRef(ref readonly OptionStruct thing)
{
    // elided
}

Sie können die Methode mithilfe des ref- oder des in-Modifizierers aufrufen. Wenn Sie den Modifizierer weglassen, gibt der Compiler eine Warnung aus. Wenn es sich bei dem Argument um einen Ausdruck und nicht um eine Variable handelt, können Sie die Modifizierer „in“ oder „ref“ nicht hinzufügen, daher sollten Sie die Warnung unterdrücken:

ForceByRef(in options);
ForceByRef(ref options);
ForceByRef(options); // Warning! variable should be passed with `ref` or `in`
ForceByRef(new OptionStruct()); // Warning, but an expression, so no variable to reference

Wenn es sich bei der Variablen um eine readonly-Variable handelt, müssen Sie den in-Modifizierer verwenden. Der Compiler gibt einen Fehler aus, wenn Sie stattdessen den ref-Modifizierer verwenden.

Der ref readonly-Modifizierer gibt an, dass die Methode erwartet, dass es sich bei dem Argument um eine Variable und nicht um einen Ausdruck, der keine Variable ist, handelt. Beispiele für Ausdrücke, die keine Variablen sind, sind Konstanten, Methodenrückgabewerte und Eigenschaften. Wenn das Argument keine Variable ist, gibt der Compiler eine Warnung aus.

Modifizierer für in-Parameter

Der in-Modifizierer ist in der Methodendeklaration erforderlich, aber nicht an der Aufrufstelle.

int readonlyArgument = 44;
InArgExample(readonlyArgument);
Console.WriteLine(readonlyArgument);     // value is still 44

void InArgExample(in int number)
{
    // Uncomment the following line to see error CS8331
    //number = 19;
}

Mit dem in-Modifizierer kann der Compiler eine temporäre Variable für das Argument erstellen und einen schreibgeschützten Verweis an dieses Argument übergeben. Der Compiler erstellt immer eine temporäre Variable, wenn das Argument konvertiert werden muss, wenn eine implizite Konvertierung aus dem Argumenttyp vorhanden ist oder wenn das Argument ein Wert ist, der keine Variable ist. Zum Beispiel, wenn das Argument ein Literalwert oder der von einer Eigenschaftszugriffsmethode zurückgegebene Wert ist. Wenn Ihre API erfordert, dass das Argument nach Verweis übergeben wird, wählen Sie den ref readonly-Modifizierer anstelle des in-Modifizierers aus.

Methoden, die mithilfe von in-Parametern definiert werden, erzielen potenziell eine Leistungsoptimierung. Einige struct-Typargumente beanspruchen möglicherweise viel Speicherplatz. Wenn dann in Schleifen mit vielen Durchläufen oder kritischen Codepfaden Methoden aufgerufen werden, ist der Aufwand zum Kopieren dieser Strukturen enorm. Methoden deklarieren in-Parameter, um anzugeben, dass Argumente sicher nach Verweis übergeben werden können, da die aufgerufene Methode den Status dieses Arguments nicht ändert. Durch die Übergabe dieser Argumente als Verweis wird ein möglicherweise aufwendiges Kopieren vermieden. Explizit geben Sie den in-Modifizierer an der Aufrufstelle an, wenn Sie sicherstellen möchten, dass das Argument als Verweis und nicht als Wert übergeben wird. Die explizite Verwendung von in hat die folgenden beiden Auswirkungen:

  • Der Compiler wird durch die Angabe von in an der Aufrufstelle dazu gezwungen, die Methode mit dem übereinstimmenden in-Parameter auszuwählen. Wenn dies nicht der Fall ist und sich zwei Methoden nur durch die Angabe von in unterscheiden, wird die Methode überladen, für die Argumente als Wert übergeben werden.
  • Wenn Sie in festlegen, deklarieren Sie Ihre Absicht, ein Argument nach Verweis zu übergeben. Das mit in verwendete Argument muss einen Speicherort darstellen, auf den direkt verwiesen werden kann. Es gelten dieselben Regeln wie für out- und ref-Argumente: Sie können keine Konstanten, normale Eigenschaften oder andere Ausdrücke, die Werte erzeugen, verwenden. Wird in an der Aufrufstelle weggelassen, wird der Compiler darüber informiert, dass die Erstellung einer temporären Variable und deren Übergabe als schreibgeschützter Verweis an die Methode zulässig ist. Der Compiler erstellt in diesem Fall eine temporäre Variable, um mehrere Einschränkungen im Zusammenhang mit in-Argumenten zu umgehen:
    • Eine temporäre Variable ermöglicht als Konstanten zur Kompilierzeit in-Parameter.
    • Eine temporäre Variable ermöglicht Eigenschaften oder andere Ausdrücke für in-Parameter.
    • Eine temporäre Variable ermöglicht Argumente, bei denen eine implizite Konvertierung des Argumenttyps in den Parametertyp vorgenommen wird.

In den oben beschriebenen Fällen erstellt der Compiler eine temporäre Variable, die den Wert einer Konstante, einer Eigenschaft oder eines anderen Ausdrucks speichert.

Das folgende Codebeispiel veranschaulicht diese Regeln:

static void Method(in int argument)
{
    // implementation removed
}

Method(5); // OK, temporary variable created.
Method(5L); // CS1503: no implicit conversion from long to int
short s = 0;
Method(s); // OK, temporary int created with the value 0
Method(in s); // CS1503: cannot convert from in short to in int
int i = 42;
Method(i); // passed by readonly reference
Method(in i); // passed by readonly reference, explicitly using `in`

Gehen wir nun davon aus, dass eine weitere Methode verfügbar ist, für die Argumente als Wert übergeben werden. Wie im folgenden Codebeispiel zu sehen ist, ändern sich die Ergebnisse:

static void Method(int argument)
{
    // implementation removed
}

static void Method(in int argument)
{
    // implementation removed
}

Method(5); // Calls overload passed by value
Method(5L); // CS1503: no implicit conversion from long to int
short s = 0;
Method(s); // Calls overload passed by value.
Method(in s); // CS1503: cannot convert from in short to in int
int i = 42;
Method(i); // Calls overload passed by value
Method(in i); // passed by readonly reference, explicitly using `in`

Nur beim letzten Methodenaufruf wird das Argument als Verweis übergeben.

Hinweis

Im obigen Code wird aus Gründen der Einfachheit int als Argumenttyp verwendet. Da auf den meisten modernen Computern int nicht größer ist als ein Verweis, ergibt sich kein Vorteil daraus, einen einzelnen int-Wert als schreibgeschützten Verweis zu übergeben.

params-Modifizierer

Nach dem params-Schlüsselwort sind keine anderen Parameter in einer Methodendeklaration zugelassen. Gleichzeitig ist nur ein params-Schlüsselwort in einer Methodendeklaration zulässig.

Wenn der deklarierte Typ des params-Parameters kein eindimensionales Array ist, tritt der Compilerfehler CS0225 auf.

Wenn Sie eine Methode mit einem params-Parameter aufrufen, können Sie Folgendes übergeben:

  • Eine durch Trennzeichen getrennte Liste von Argumenten des Typs der Arrayelemente
  • Ein Array aus Argumenten des angegebenen Typs
  • Keine Argumente. Wenn Sie keine Argumente senden, ist die Länge der params-Liste 0 (null).

Im folgenden Beispiel werden verschiedene Methoden veranschaulicht, in denen Argumente an einen params-Parameter gesendet werden können.

public class MyClass
{
    public static void UseParams(params int[] list)
    {
        for (int i = 0; i < list.Length; i++)
        {
            Console.Write(list[i] + " ");
        }
        Console.WriteLine();
    }

    public static void UseParams2(params object[] list)
    {
        for (int i = 0; i < list.Length; i++)
        {
            Console.Write(list[i] + " ");
        }
        Console.WriteLine();
    }

    static void Main()
    {
        // You can send a comma-separated list of arguments of the
        // specified type.
        UseParams(1, 2, 3, 4);
        UseParams2(1, 'a', "test");

        // A params parameter accepts zero or more arguments.
        // The following calling statement displays only a blank line.
        UseParams2();

        // An array argument can be passed, as long as the array
        // type matches the parameter type of the method being called.
        int[] myIntArray = { 5, 6, 7, 8, 9 };
        UseParams(myIntArray);

        object[] myObjArray = { 2, 'b', "test", "again" };
        UseParams2(myObjArray);

        // The following call causes a compiler error because the object
        // array cannot be converted into an integer array.
        //UseParams(myObjArray);

        // The following call does not cause an error, but the entire
        // integer array becomes the first element of the params array.
        UseParams2(myIntArray);
    }
}
/*
Output:
    1 2 3 4
    1 a test

    5 6 7 8 9
    2 b test again
    System.Int32[]
*/