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 są 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 przy użyciu
ref =
metody , te zmiany nie są widoczne w obiekcie wywołującym. - Jeśli metoda modyfikuje stan obiektu, do której odwołuje się parametr , te zmiany są widoczne z obiektu wywołującego.
- Jeśli metoda przypisuje parametr do odwoływania się do innego obiektu przy użyciu
- Po przekazaniu typu odwołania według odwołania:
- Jeśli metoda przypisuje parametr do odwoływania się do innego obiektu, te zmiany są widoczne z obiektu wywołującego.
- Jeśli metoda modyfikuje stan obiektu, do której odwołuje się parametr , te zmiany są 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
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 doin
parametrów.
Składowe klasy nie mogą mieć podpisów, które różnią się tylko od ref
, ref readonly
, in
lub 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 out
parametr , ref readonly
lub in
. Jednak metody mogą być przeciążone, gdy jedna metoda ma ref
parametr , ref readonly
, in
lub 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 readonly
i 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
out
out
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ć modyfikatoryin
lubref
, 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 typemstruct
ogólnym, który nie jest ograniczony do struktury. - Słowa
ref readonly
kluczowe iin
nie mogą być używane, chyba że pierwszym argumentemstruct
jest . - Słowa
ref readonly
kluczowe iin
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ącymin
parametrem. W przeciwnym razie, gdy dwie metody różnią się tylko w obecnościin
, przeciążenie wartości według jest lepszym dopasowaniem. in
Określając wartość , należy zadeklarować zamiar przekazania argumentu przy użyciu odwołania. Argument używany z elementemin
musi reprezentować lokalizację, do której można się bezpośrednio odwoływać. Mają zastosowanie te same ogólne reguły iout
ref
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ęciein
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 argumentamiin
:- 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.
- Zmienna tymczasowa umożliwia stałe czasu kompilacji jako
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:
- Jednowymiarowy typ
T[]
tablicy , w którym przypadku typ elementu toT
. - Typ zakresu:
System.Span<T>
System.ReadOnlySpan<T>
Tutaj typ elementu toT
.
- Typ z dostępną metodą create z odpowiednim typem elementu. Metoda create jest identyfikowana przy użyciu tego samego atrybutu używanego dla wyrażeń kolekcji.
- Struktura lub typ klasy, który implementujeSystem.Collections.Generic.IEnumerable<T>, gdzie:
- Typ ma konstruktor, który można wywołać bez argumentów, a konstruktor jest co najmniej tak dostępny, jak deklarujący element członkowski.
- Typ ma metodę
Add
wystąpienia (a nie rozszerzenia), w której:- Metodę można wywołać za pomocą jednego argumentu wartości.
- Jeśli metoda jest ogólna, argumenty typu można wywnioskować z argumentu.
- Metoda jest co najmniej tak dostępna, jak deklarujący element członkowski. Tutaj typ elementu to typ iteracji typu.
- Typ interfejsu:
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#.