Parametry metody

Domyślnie argumenty w języku C# są przekazywane do funkcji według wartości. Oznacza to, że kopia zmiennej jest przekazywana do metody . W przypadku typów wartości (struct) kopia wartości jest przekazywana do metody . W przypadku typów referencyjnych (class) kopia odwołania jest przekazywana do metody . Modyfikatory parametrów umożliwiają przekazywanie argumentów według odwołania. Poniższe pojęcia pomagają zrozumieć te różnice i jak używać modyfikatorów parametrów:

  • Przekazywanie według wartości oznacza przekazanie kopii zmiennej do metody .
  • Przekazywanie przez odwołanie oznacza przekazanie dostępu do zmiennej do metody .
  • Zmienna typu odwołania zawiera odwołanie do jego danych.
  • Zmienna typu wartości zawiera dane bezpośrednio.

Ponieważ struktura jest typem wartości, metoda odbiera i działa na kopii argumentu struktury podczas przekazywania struktury według wartości do metody. Metoda nie ma dostępu do oryginalnej struktury w metodzie wywołującej i dlatego nie może zmienić jej w żaden sposób. Metoda może zmienić tylko kopię.

Wystąpienie klasy jest typem referencyjnym, a nie typem wartości. Gdy typ odwołania jest przekazywany przez wartość do metody, metoda otrzymuje kopię odwołania do wystąpienia klasy. Obie zmienne odwołują się do tego samego obiektu. Parametr jest kopią odwołania. Wywołana metoda nie może ponownie przypisać wystąpienia w metodzie wywołującej. Jednak wywołana metoda może użyć kopii odwołania, aby uzyskać dostęp do elementów członkowskich wystąpienia. Jeśli wywołana metoda zmienia element członkowski wystąpienia, metoda wywołująca również widzi te zmiany, ponieważ odwołuje się do tego samego wystąpienia.

Dane wyjściowe poniższego przykładu ilustrują różnicę. Metoda ClassTaker zmienia wartość willIChange pola, ponieważ metoda używa adresu w parametrze w celu znalezienia określonego pola wystąpienia klasy. Pole willIChange struktury w metodzie wywołującej nie zmienia się z wywołania StructTaker , ponieważ wartość argumentu jest kopią samej struktury, a nie kopią jej adresu. StructTaker zmienia kopię, a kopia zostanie utracona po zakończeniu wywołania StructTaker .

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
*/

Kombinacje typu parametru i trybu argumentu

Sposób przekazywania argumentu i określa, czy jest to typ odwołania lub typ wartości, kontroluje, jakie modyfikacje wprowadzone w argumencie są widoczne dla obiektu wywołującego:

  • Po przekazaniu typu wartości według wartości:
    • Jeśli metoda przypisuje parametr do odwoływania się do innego obiektu, te zmiany nie są widoczne z obiektu wywołującego.
    • Jeśli metoda modyfikuje stan obiektu, do której odwołuje się parametr , te zmiany nie są widoczne w obiekcie wywołującym.
  • Po przekazaniu typu odwołania według wartości:
    • Jeśli metoda przypisuje parametr do odwoływania się do innego obiektu, te zmiany nie są widoczne z obiektu wywołującego.
    • Jeśli metoda modyfikuje stan obiektu, do której odwołuje się parametr , te zmiany widoczne z obiektu wywołującego.
  • Po przekazaniu typu wartości według odwołania:
    • Jeśli metoda przypisuje parametr do odwoływania się do innego obiektu, te zmiany nie są widoczne z obiektu wywołującego.
    • Jeśli metoda modyfikuje stan obiektu, do której odwołuje się parametr , te zmiany widoczne z obiektu wywołującego.
  • Po przekazaniu typu odwołania według odwołania:
    • Jeśli metoda przypisuje parametr do odwoływania się do innego obiektu, te zmiany widoczne z obiektu wywołującego.
    • Jeśli metoda modyfikuje stan obiektu, do której odwołuje się parametr , te zmiany widoczne z obiektu wywołującego.

Przekazywanie typu odwołania przez odwołanie umożliwia wywołaniu metodę zastępowania obiektu, do którego odwołuje się parametr odwołania w obiekcie wywołującym. Lokalizacja magazynu obiektu jest przekazywana do metody jako wartość parametru odwołania. Jeśli zmienisz wartość w lokalizacji przechowywania parametru (aby wskazać nowy obiekt), zmienisz również lokalizację przechowywania, do której odwołuje się obiekt wywołujący. Poniższy przykład przekazuje wystąpienie typu odwołania jako ref parametr.

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

Sejf kontekst odwołań i wartości

Metody mogą przechowywać wartości parametrów w polach. Gdy parametry są przekazywane przez wartość, zwykle jest to bezpieczne. Wartości są kopiowane, a typy odwołań są osiągalne w przypadku przechowywania w polu. Bezpieczne przekazywanie parametrów przez odwołanie wymaga, aby kompilator zdefiniował, kiedy można bezpiecznie przypisać odwołanie do nowej zmiennej. Dla każdego wyrażenia kompilator definiuje bezpieczny kontekst , który jest powiązany z dostępem do wyrażenia lub zmiennej. Kompilator używa dwóch zakresów: bezpiecznego kontekstu i kontekstu bezpiecznego ref.

  • Bezpieczny kontekst definiuje zakres, w którym można bezpiecznie uzyskać dostęp do dowolnego wyrażenia.
  • Kontekst bezpieczny ref definiuje zakres, w którym można bezpiecznie uzyskać dostęp do dowolnego wyrażenia lub zmodyfikować odwołanie do dowolnego wyrażenia.

Nieformalnie można traktować te zakresy jako mechanizm zapewniający, że kod nigdy nie uzyskuje dostępu do kodu lub modyfikuje odwołanie, które nie jest już prawidłowe. Odwołanie jest prawidłowe, o ile odwołuje się do prawidłowego obiektu lub struktury. Kontekst bezpieczny definiuje, kiedy można przypisać lub ponownie przypisać zmienną. Kontekst bezpieczny ref definiuje, kiedy zmienna może zostać przypisana ponownie lub ponownie przypisana. Przypisanie przypisuje zmienną do nowej wartości; Przypisanie ref przypisuje zmienną, aby odwoływać się do innej lokalizacji przechowywania.

Parametry odwołania

Do deklaracji parametru należy zastosować jeden z następujących modyfikatorów, aby przekazać argumenty za pomocą odwołania zamiast wartości:

  • ref: Argument musi zostać zainicjowany przed wywołaniem metody . Metoda może przypisać nową wartość do parametru, ale nie jest wymagana do tego celu.
  • out: Metoda wywołująca nie jest wymagana do zainicjowania argumentu przed wywołaniem metody. Metoda musi przypisać wartość do parametru.
  • readonly ref: Argument musi zostać zainicjowany przed wywołaniem metody . Metoda nie może przypisać nowej wartości do parametru.
  • in: Argument musi zostać zainicjowany przed wywołaniem metody . Metoda nie może przypisać nowej wartości do parametru. Kompilator może utworzyć zmienną tymczasową do przechowywania kopii argumentu do in parametrów.

Składowe klasy nie mogą mieć podpisów, które różnią się tylko od ref, ref readonly, inlub out. Błąd kompilatora występuje, jeśli jedyną różnicą między dwoma elementami członkowskimi typu jest to, że jeden z nich ma ref parametr , a drugi ma outparametr , ref readonlylub in . Jednak metody mogą być przeciążone, gdy jedna metoda ma refparametr , ref readonly, inlub out , a drugi ma parametr przekazywany przez wartość, jak pokazano w poniższym przykładzie. W innych sytuacjach, które wymagają dopasowania podpisu, takie jak ukrywanie lub zastępowanie, in, ref, ref readonlyi out są częścią podpisu i nie są ze sobą zgodne.

Jeśli parametr ma jeden z powyższych modyfikatorów, odpowiedni argument może mieć zgodny modyfikator:

  • Argument parametru ref musi zawierać ref modyfikator.
  • Argument parametru outout musi zawierać modyfikator.
  • Argument parametru in może opcjonalnie zawierać in modyfikator. ref Jeśli modyfikator jest używany w argumencie, kompilator wystawia ostrzeżenie.
  • Argument parametru ref readonly powinien zawierać modyfikatory in lub ref , ale nie oba te elementy. Jeśli żaden modyfikator nie zostanie uwzględniony, kompilator wyświetla ostrzeżenie.

W przypadku używania tych modyfikatorów opisują sposób użycia argumentu:

  • ref oznacza, że metoda może odczytywać lub zapisywać wartość argumentu.
  • out oznacza, że metoda ustawia wartość argumentu.
  • ref readonly oznacza, że metoda odczytuje, ale nie może zapisać wartości argumentu. Argument powinien zostać przekazany przez odwołanie.
  • in oznacza, że metoda odczytuje, ale nie może zapisać wartości argumentu. Argument zostanie przekazany przez odwołanie lub przez zmienną tymczasową.

Właściwości nie są zmiennymi. Są to metody i nie można ich przekazać do ref parametrów. Nie można używać poprzednich modyfikatorów parametrów w następujących rodzajach metod:

  • Metody asynchroniczne definiowane przy użyciu modyfikatora asynchronicznego .
  • Metody iteracyjne, które obejmują zwrot lub instrukcję zwrotu yield break wydajności.

Metody rozszerzeń mają również ograniczenia dotyczące używania tych słów kluczowych argumentów:

  • Nie out można użyć słowa kluczowego w pierwszym argumencie metody rozszerzenia.
  • Słowo ref kluczowe nie może być używane w pierwszym argumencie metody rozszerzenia, gdy argument nie jest typem , ani typem structogólnym, który nie jest ograniczony do struktury.
  • Słowa ref readonly kluczowe i in nie mogą być używane, chyba że pierwszym argumentem structjest .
  • Słowa ref readonly kluczowe i in nie mogą być używane w żadnym typie ogólnym, nawet jeśli ograniczenie jest strukturą.

ref modyfikator parametrów

Aby użyć parametru ref , zarówno definicja metody, jak i metoda wywołująca muszą jawnie używać ref słowa kluczowego, jak pokazano w poniższym przykładzie. (Z wyjątkiem tego, że metoda wywołująca może pominąć ref podczas wykonywania wywołania COM).

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

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

Przed przekazaniem argumentu przekazanego do parametru ref należy zainicjować.

out modyfikator parametrów

Aby użyć parametru out , zarówno definicja metody, jak i metoda wywołująca muszą jawnie używać słowa kluczowego out . Na przykład:

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

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

Zmienne przekazywane jako out argumenty nie muszą być inicjowane przed przekazaniem w wywołaniu metody. Jednak wywołana metoda jest wymagana do przypisania wartości przed zwróceniem metody.

Metody dekonstrukcji deklarują swoje parametry za pomocą out modyfikatora, aby zwrócić wiele wartości. Inne metody mogą zwracać krotki wartości dla wielu zwracanych wartości.

Zmienną można zadeklarować w osobnej instrukcji przed przekazaniem jej jako argumentu out . Można również zadeklarować zmienną out na liście argumentów wywołania metody, a nie w oddzielnej deklaracji zmiennej. out deklaracje zmiennych tworzą bardziej kompaktowy, czytelny kod, a także uniemożliwiają przypadkowo przypisanie wartości do zmiennej przed wywołaniem metody. Poniższy przykład definiuje zmienną number w wywołaniu metody 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

Można również zadeklarować niejawnie typizowanej zmiennej lokalnej.

ref readonly Modyfikator

Modyfikator ref readonly musi być obecny w deklaracji metody. Modyfikator w lokacji wywołania jest opcjonalny. in Można użyć modyfikatora lub ref . Modyfikator ref readonly nie jest prawidłowy w lokacji wywołania. Który modyfikator używany w lokacji wywołania może pomóc opisać cechy argumentu. Można użyć ref tylko wtedy, gdy argument jest zmienną i jest zapisywalny. Można użyć in tylko wtedy, gdy argument jest zmienną. Może być zapisywalny lub czytelny. Nie można dodać ani modyfikatora, jeśli argument nie jest zmienną, ale jest wyrażeniem. W poniższych przykładach przedstawiono te warunki. Poniższa metoda używa ref readonly modyfikatora, aby wskazać, że duża struktura powinna zostać przekazana przez odwołanie ze względów wydajności:

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

Metodę można wywołać przy użyciu ref modyfikatora lub in . Jeśli pominiesz modyfikator, kompilator wyświetla ostrzeżenie. Gdy argument jest wyrażeniem, a nie zmienną, nie można dodać in modyfikatora lub ref więc należy pominąć ostrzeżenie:

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

Jeśli zmienna jest zmienną readonly , należy użyć in modyfikatora. Kompilator zgłasza błąd, jeśli zamiast tego używasz ref modyfikatora.

ref readonly Modyfikator wskazuje, że metoda oczekuje, że argument będzie zmienną, a nie wyrażeniem, które nie jest zmienną. Przykłady wyrażeń, które nie są zmiennymi, to stałe, wartości zwracane przez metodę i właściwości. Jeśli argument nie jest zmienną, kompilator wyświetla ostrzeżenie.

in modyfikator parametrów

Modyfikator in jest wymagany w deklaracji metody, ale niepotrzebny w lokacji wywołania.

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;
}

Modyfikator in umożliwia kompilatorowi utworzenie tymczasowej zmiennej dla argumentu i przekazanie odwołania readonly do tego argumentu. Kompilator zawsze tworzy zmienną tymczasową, gdy argument musi zostać przekonwertowany, gdy istnieje niejawna konwersja z typu argumentu lub gdy argument jest wartością, która nie jest zmienną. Na przykład gdy argument jest wartością literału lub wartością zwracaną z metody dostępu właściwości. Jeśli interfejs API wymaga przekazania argumentu przez odwołanie, wybierz ref readonly modyfikator zamiast in modyfikatora.

Metody zdefiniowane przy użyciu in parametrów mogą potencjalnie uzyskać optymalizację wydajności. Niektóre struct argumenty typu mogą być duże, a gdy metody są wywoływane w ciasnych pętlach lub krytycznych ścieżkach kodu, koszt kopiowania tych struktur jest znaczny. Metody deklarują in parametry, aby określić, że argumenty mogą być przekazywane przez odwołanie bezpiecznie, ponieważ wywołana metoda nie modyfikuje stanu tego argumentu. Przekazywanie tych argumentów przez odwołanie pozwala uniknąć (potencjalnie) kosztownej kopii. Modyfikator jawnie dodaje in się w lokacji wywołania, aby upewnić się, że argument jest przekazywany przez odwołanie, a nie przez wartość. Jawne użycie in ma następujące dwa efekty:

  • Określenie in w lokacji wywołania wymusza, aby kompilator wybrał metodę zdefiniowaną z pasującym in parametrem. W przeciwnym razie, gdy dwie metody różnią się tylko w obecności in, przeciążenie wartości według jest lepszym dopasowaniem.
  • inOkreślając wartość , należy zadeklarować zamiar przekazania argumentu przy użyciu odwołania. Argument używany z elementem in musi reprezentować lokalizację, do której można się bezpośrednio odwoływać. Mają zastosowanie te same ogólne reguły i outref argumenty: nie można używać stałych, zwykłych właściwości ani innych wyrażeń, które generują wartości. W przeciwnym razie pominięcie in w lokacji wywołania informuje kompilator, że można utworzyć zmienną tymczasową do przekazania przez odwołanie tylko do odczytu do metody. Kompilator tworzy zmienną tymczasową, aby przezwyciężyć kilka ograniczeń z argumentami in :
    • Zmienna tymczasowa umożliwia stałe czasu kompilacji jako in parametry.
    • Zmienna tymczasowa zezwala na właściwości lub inne wyrażenia dla in parametrów.
    • Zmienna tymczasowa umożliwia argumenty, w których istnieje niejawna konwersja typu argumentu na typ parametru.

We wszystkich poprzednich wystąpieniach kompilator tworzy zmienną tymczasową, która przechowuje wartość stałej, właściwości lub innego wyrażenia.

Poniższy kod ilustruje następujące reguły:

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`

Teraz załóżmy, że była dostępna inna metoda używająca argumentów by-value. Wyniki zmieniają się, jak pokazano w poniższym kodzie:

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`

Jedynym wywołaniem metody, w którym argument jest przekazywany przez odwołanie, jest ostatnim wywołaniem.

Uwaga

Powyższy kod używa int jako typu argumentu dla uproszczenia. Ponieważ int nie jest większe niż odwołanie w większości nowoczesnych maszyn, nie ma korzyści z przekazania pojedynczego int jako odwołania do odczytu.

params Modyfikator

Żadne inne parametry nie są dozwolone po słowie params kluczowym w deklaracji metody, a tylko jedno params słowo kluczowe jest dozwolone w deklaracji metody.

Jeśli zadeklarowany typ parametru params nie jest tablicą jednowymiarową, wystąpi błąd kompilatora CS0225 .

Podczas wywoływania metody za pomocą parametru params można przekazać następujące elementy:

  • Rozdzielona przecinkami lista argumentów typu elementów tablicy.
  • Tablica argumentów określonego typu.
  • Brak argumentów. Jeśli nie wyślesz żadnych argumentów, długość params listy wynosi zero.

W poniższym przykładzie pokazano różne sposoby wysyłania argumentów do parametru params .

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[]
*/
  • Listy argumentów w specyfikacji języka C#. Specyfikacja języka jest ostatecznym źródłem informacji o składni i użyciu języka C#.