Udostępnij za pośrednictwem


Parametry metody i modyfikatory

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.

Ponieważ struktura jest typem wartości , metoda przyjmuje i pracuje na kopii argumentu, gdy przekazujesz strukturę przez wartość 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. 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.

Przekazywanie wartości i przekazywanie przez odwołanie

Wszystkie przykłady w tej sekcji używają następujących dwóch typów record, aby zilustrować różnice między typami class a typami struct:

public record struct Point(int X, int Y);
// This doesn't use a primary constructor because the properties implemented for `record` types are 
// readonly in record class types. That would prevent the mutations necessary for this example.
public record class Point3D
{
    public int X { get; set; }
    public int Y { get; set; }
    public int Z { get; set; }
}

Dane wyjściowe poniższego przykładu ilustrują różnicę między przekazywaniem typu struktury według wartości i przekazywaniem typu klasy według wartości. Obie metody Mutate zmieniają wartości właściwości argumentu. Gdy parametr jest typem struct, te zmiany są wprowadzane do kopii danych argumentu. Gdy parametr jest typem class, te zmiany są wprowadzane do wystąpienia, do których odwołuje się argument :

public class PassTypesByValue
{
    public static void Mutate(Point pt)
    {
        Console.WriteLine($"\tEnter {nameof(Mutate)}:\t\t{pt}");
        pt.X = 19;
        pt.Y = 23;

        Console.WriteLine($"\tExit {nameof(Mutate)}:\t\t{pt}");
    }
    public static void Mutate(Point3D pt)
    {
        Console.WriteLine($"\tEnter {nameof(Mutate)}:\t\t{pt}");
        pt.X = 19;
        pt.Y = 23;
        pt.Z = 42;

        Console.WriteLine($"\tExit {nameof(Mutate)}:\t\t{pt}");
    }

    public static void TestPassTypesByValue()
    {
        Console.WriteLine("===== Value Types =====");

        var ptStruct = new Point { X = 1, Y = 2 };
        Console.WriteLine($"After initialization:\t\t{ptStruct}");

        Mutate(ptStruct);

        Console.WriteLine($"After called {nameof(Mutate)}:\t\t{ptStruct}");

        Console.WriteLine("===== Reference Types =====");

        var ptClass = new Point3D { X = 1, Y = 2, Z = 3 };

        Console.WriteLine($"After initialization:\t\t{ptClass}");

        Mutate(ptClass);
        Console.WriteLine($"After called {nameof(Mutate)}:\t\t{ptClass}");

        // Output:
        // ===== Value Types =====
        // After initialization:           Point { X = 1, Y = 2 }
        //         Enter Mutate:           Point { X = 1, Y = 2 }
        //         Exit Mutate:            Point { X = 19, Y = 23 }
        // After called Mutate:            Point { X = 1, Y = 2 }
        // ===== Reference Types =====
        // After initialization:           Point3D { X = 1, Y = 2, Z = 3 }
        //         Enter Mutate:           Point3D { X = 1, Y = 2, Z = 3 }
        //         Exit Mutate:            Point3D { X = 19, Y = 23, Z = 42 }
        // After called Mutate:            Point3D { X = 19, Y = 23, Z = 42 }
    }
}

Modyfikator ref jest jednym ze sposobów przekazywania argumentów przez odwołanie do metod. Następujący kod następuje po poprzednim przykładzie, ale przekazuje parametry przez referencję. Modyfikacje wprowadzone w typie struct są widoczne w metodzie wywołującej, gdy struktura jest przekazywana przez referencję. Nie ma zmiany semantycznej, gdy typ odwołania jest przekazywany przez odwołanie:

public class PassTypesByReference
{
    public static void Mutate(ref Point pt)
    {
        Console.WriteLine($"\tEnter {nameof(Mutate)}:\t\t{pt}");
        pt.X = 19;
        pt.Y = 23;

        Console.WriteLine($"\tExit {nameof(Mutate)}:\t\t{pt}");
    }
    public static void Mutate(ref Point3D pt)
    {
        Console.WriteLine($"\tEnter {nameof(Mutate)}:\t\t{pt}");
        pt.X = 19;
        pt.Y = 23;
        pt.Z = 42;

        Console.WriteLine($"\tExit {nameof(Mutate)}:\t\t{pt}");
    }

    public static void TestPassTypesByReference()
    {
        Console.WriteLine("===== Value Types =====");

        var pStruct = new Point { X = 1, Y = 2 };
        Console.WriteLine($"After initialization:\t\t{pStruct}");

        Mutate(ref pStruct);

        Console.WriteLine($"After called {nameof(Mutate)}:\t\t{pStruct}");

        Console.WriteLine("===== Reference Types =====");

        var pClass = new Point3D { X = 1, Y = 2, Z = 3 };

        Console.WriteLine($"After initialization:\t\t{pClass}");

        Mutate(ref pClass);
        Console.WriteLine($"After called {nameof(Mutate)}:\t\t{pClass}");

        // Output:
        // ===== Value Types =====
        // After initialization:           Point { X = 1, Y = 2 }
        //         Enter Mutate:           Point { X = 1, Y = 2 }
        //         Exit Mutate:            Point { X = 19, Y = 23 }
        // After called Mutate:            Point { X = 19, Y = 23 }
        // ===== Reference Types =====
        // After initialization:           Point3D { X = 1, Y = 2, Z = 3 }
        //         Enter Mutate:           Point3D { X = 1, Y = 2, Z = 3 }
        //         Exit Mutate:            Point3D { X = 19, Y = 23, Z = 42 }
        // After called Mutate:            Point3D { X = 19, Y = 23, Z = 42 }
    }
}

Powyższe przykłady zmodyfikowały właściwości parametru. Metoda może również ponownie przypisać parametr do nowej wartości. Ponowne przypisywanie działa inaczej w przypadku typów struktur i klas, gdy są przekazywane przez wartość lub przez odwołanie. W poniższym przykładzie pokazano, jak zachowują się typy struktur i typy klas, gdy parametry przekazywane przez wartość są ponownie przypisywane:

public class PassByValueReassignment
{
    public static void Reassign(Point pt)
    {
        Console.WriteLine($"\tEnter {nameof(Reassign)}:\t\t{pt}");
        pt = new Point { X = 13, Y = 29 };

        Console.WriteLine($"\tExit {nameof(Reassign)}:\t\t{pt}");
    }

    public static void Reassign(Point3D pt)
    {
        Console.WriteLine($"\tEnter {nameof(Reassign)}:\t\t{pt}");
        pt = new Point3D { X = 13, Y = 29, Z = -42 };

        Console.WriteLine($"\tExit {nameof(Reassign)}:\t\t{pt}");
    }

    public static void TestPassByValueReassignment()
    {
        Console.WriteLine("===== Value Types =====");

        var ptStruct = new Point { X = 1, Y = 2 };
        Console.WriteLine($"After initialization:\t\t{ptStruct}");

        Reassign(ptStruct);

        Console.WriteLine($"After called {nameof(Reassign)}:\t\t{ptStruct}");

        Console.WriteLine("===== Reference Types =====");

        var ptClass = new Point3D { X = 1, Y = 2, Z = 3 };

        Console.WriteLine($"After initialization:\t\t{ptClass}");

        Reassign(ptClass);
        Console.WriteLine($"After called {nameof(Reassign)}:\t\t{ptClass}");

        // Output:
        // ===== Value Types =====
        // After initialization:           Point { X = 1, Y = 2 }
        //         Enter Reassign:         Point { X = 1, Y = 2 }
        //         Exit Reassign:          Point { X = 13, Y = 29 }
        // After called Reassign:          Point { X = 1, Y = 2 }
        // ===== Reference Types =====
        // After initialization:           Point3D { X = 1, Y = 2, Z = 3 }
        //         Enter Reassign:         Point3D { X = 1, Y = 2, Z = 3 }
        //         Exit Reassign:          Point3D { X = 13, Y = 29, Z = -42 }
        // After called Reassign:          Point3D { X = 1, Y = 2, Z = 3 }
    }
}

W poprzednim przykładzie pokazano, że podczas ponownego przypisania parametru do nowej wartości ta zmiana nie jest widoczna w metodzie wywołującej, niezależnie od tego, czy typ jest typem wartości, czy typem odwołania. W poniższym przykładzie pokazano zachowanie podczas ponownego przypisania parametru, który został przekazany przez odwołanie:

public class PassByReferenceReassignment
{
    public static void Reassign(ref Point pt)
    {
        Console.WriteLine($"\tEnter {nameof(Reassign)}:\t\t{pt}");
        pt = new Point { X = 13, Y = 29 };

        Console.WriteLine($"\tExit {nameof(Reassign)}:\t\t{pt}");
    }

    public static void Reassign(ref Point3D pt)
    {
        Console.WriteLine($"\tEnter {nameof(Reassign)}:\t\t{pt}");
        pt = new Point3D { X = 13, Y = 29, Z = -42 };

        Console.WriteLine($"\tExit {nameof(Reassign)}:\t\t{pt}");
    }

    public static void TestPassByReferenceReassignment()
    {
        Console.WriteLine("===== Value Types =====");

        var ptStruct = new Point { X = 1, Y = 2 };
        Console.WriteLine($"After initialization:\t\t{ptStruct}");

        Reassign(ref ptStruct);

        Console.WriteLine($"After called {nameof(Reassign)}:\t\t{ptStruct}");

        Console.WriteLine("===== Reference Types =====");

        var ptClass = new Point3D { X = 1, Y = 2, Z = 3 };

        Console.WriteLine($"After initialization:\t\t{ptClass}");

        Reassign(ref ptClass);
        Console.WriteLine($"After called {nameof(Reassign)}:\t\t{ptClass}");

        // Output:
        // ===== Value Types =====
        // After initialization:           Point { X = 1, Y = 2 }
        //         Enter Reassign:         Point { X = 1, Y = 2 }
        //         Exit Reassign:          Point { X = 13, Y = 29 }
        // After called Reassign:          Point { X = 13, Y = 29 }
        // ===== Reference Types =====
        // After initialization:           Point3D { X = 1, Y = 2, Z = 3 }
        //         Enter Reassign:         Point3D { X = 1, Y = 2, Z = 3 }
        //         Exit Reassign:          Point3D { X = 13, Y = 29, Z = -42 }
        // After called Reassign:          Point3D { X = 13, Y = 29, Z = -42 }
    }
}

W poprzednim przykładzie pokazano, jak ponowne przypisanie wartości parametru przekazanego przez odwołanie jest widoczne w kontekście wywołującym.

Bezpieczny 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.
  • ref readonly: 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.

Parametr przekazywany przez odwołanie jest zmienną referencyjną . Nie ma własnej wartości. Zamiast tego, odwołuje się do innej zmiennej, nazywanej , odnoszącego się do. Zmienne referencyjne mogą być ref ponownie przydzielone, co zmienia jego odwołanie.

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ą.

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ą.

Właściwości nie są zmiennymi. Są to metody. Właściwości nie mogą być argumentami parametrów ref .

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.

Zadeklarowany typ parametru params musi być typem kolekcji. Rozpoznane typy kolekcji to:

Przed użyciem języka C# 13 parametr musi być tablicą jednowymiarową.

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

  • Rozdzielona przecinkami lista argumentów typu elementów tablicy.
  • Kolekcja 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 static void ParamsModifierExample(params int[] list)
{
    for (int i = 0; i < list.Length; i++)
    {
        System.Console.Write(list[i] + " ");
    }
    System.Console.WriteLine();
}

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

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

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

    // 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 };
    ParamsModifierExample(myIntArray);

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

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

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

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

Rozpoznawanie przeciążenia może powodować niejednoznaczność, gdy argument parametru params jest typem kolekcji. Typ kolekcji argumentu musi być konwertowany na typ kolekcji parametru. Gdy różne przeciążenia zapewniają lepszą konwersję dla tego parametru, ta metoda może być lepsza. Jeśli jednak argument parametru params jest dyskretny lub brakuje, wszystkie przeciążenia z różnymi params typami parametrów są równe dla tego parametru.

Aby uzyskać więcej informacji, zobacz sekcję dotyczącą list 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#.