Parameter Metode
Secara default, argumen dalam C# diteruskan ke fungsi menurut nilai. Itu berarti salinan variabel diteruskan ke metode . Untuk jenis nilai (struct
), salinan nilai diteruskan ke metode . Untuk jenis referensi (class
), salinan referensi diteruskan ke metode . Pengubah parameter memungkinkan Anda meneruskan argumen berdasarkan referensi. Konsep berikut membantu Anda memahami perbedaan ini dan cara menggunakan pengubah parameter:
- Meneruskan menurut nilai berarti meneruskan salinan variabel ke metode .
- Melewati referensi berarti meneruskan akses ke variabel ke metode .
- Variabel jenis referensi berisi referensi ke datanya.
- Variabel jenis nilai berisi datanya secara langsung.
Karena struct adalah jenis nilai, metode menerima dan beroperasi pada salinan argumen struct saat Anda meneruskan struct by value ke metode . Metode tidak memiliki akses ke struktur asli dalam metode pemanggilan dan oleh karena itu tidak dapat mengubahnya dengan cara apa pun. Metode hanya dapat mengubah salinan.
Instans kelas adalah jenis referensi bukan jenis nilai. Ketika jenis referensi diteruskan berdasarkan nilai ke metode, metode menerima salinan referensi ke instans kelas. Kedua variabel merujuk ke objek yang sama. Parameter adalah salinan referensi. Metode yang disebut tidak dapat menetapkan ulang instans dalam metode panggilan. Namun, metode yang disebut dapat menggunakan salinan referensi untuk mengakses anggota instans. Jika metode yang disebut mengubah anggota instans, metode panggilan juga melihat perubahan tersebut karena mereferensikan instans yang sama.
Output dari contoh berikut menggambarkan perbedaannya. Metode ClassTaker
mengubah nilai willIChange
bidang karena metode menggunakan alamat dalam parameter untuk menemukan bidang instans kelas yang ditentukan. Bidang willIChange
struct dalam metode pemanggilan tidak berubah dari panggilan StructTaker
karena nilai argumen adalah salinan struct itu sendiri, bukan salinan alamatnya. StructTaker
mengubah salinan, dan salinannya hilang saat panggilan ke StructTaker
selesai.
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
*/
Kombinasi jenis parameter dan mode argumen
Bagaimana argumen diteruskan, dan apakah itu jenis referensi atau jenis nilai mengontrol modifikasi apa yang dibuat pada argumen terlihat dari pemanggil:
- Saat Anda meneruskan jenis nilai menurut nilai:
- Jika metode menetapkan parameter untuk merujuk ke objek yang berbeda, perubahan tersebut tidak terlihat dari pemanggil.
- Jika metode memodifikasi status objek yang dirujuk oleh parameter, perubahan tersebut tidak terlihat dari pemanggil.
- Saat Anda meneruskan jenis referensi menurut nilai:
- Jika metode menetapkan parameter untuk merujuk ke objek yang berbeda, perubahan tersebut tidak terlihat dari pemanggil.
- Jika metode memodifikasi status objek yang dirujuk oleh parameter, perubahan tersebut terlihat dari pemanggil.
- Saat Anda meneruskan jenis nilai berdasarkan referensi:
- Jika metode menetapkan parameter untuk merujuk ke objek lain menggunakan
ref =
, perubahan tersebut tidak terlihat dari pemanggil. - Jika metode memodifikasi status objek yang dirujuk oleh parameter, perubahan tersebut terlihat dari pemanggil.
- Jika metode menetapkan parameter untuk merujuk ke objek lain menggunakan
- Saat Anda meneruskan jenis referensi menurut referensi:
- Jika metode menetapkan parameter untuk merujuk ke objek yang berbeda, perubahan tersebut terlihat dari pemanggil.
- Jika metode memodifikasi status objek yang dirujuk oleh parameter, perubahan tersebut terlihat dari pemanggil.
Melewati jenis referensi dengan referensi memungkinkan metode yang disebut untuk menggantikan objek yang dirujuk parameter referensi dalam pemanggil. Lokasi penyimpanan objek diteruskan ke metode sebagai nilai parameter referensi. Jika Anda mengubah nilai di lokasi penyimpanan parameter (untuk menunjuk ke objek baru), Anda juga mengubah lokasi penyimpanan tempat pemanggil merujuk. Contoh berikut ini meneruskan instans jenis referensi sebagai parameter ref
.
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
Konteks referensi dan nilai yang aman
Metode dapat menyimpan nilai parameter dalam bidang. Ketika parameter diteruskan oleh nilai, itu biasanya aman. Nilai disalin, dan jenis referensi dapat dijangkau saat disimpan dalam bidang. Meneruskan parameter dengan referensi dengan aman mengharuskan pengkompilasi untuk menentukan kapan aman untuk menetapkan referensi ke variabel baru. Untuk setiap ekspresi, pengkompilasi menentukan konteks aman yang mengikat akses ke ekspresi atau variabel. Pengkompilasi menggunakan dua cakupan: konteks aman dan konteks ref-safe.
- Konteks aman menentukan cakupan di mana ekspresi apa pun dapat diakses dengan aman.
- Konteks ref-safe mendefinisikan cakupan di mana referensi ke ekspresi apa pun dapat diakses atau dimodifikasi dengan aman.
Secara informal, Anda dapat menganggap cakupan ini sebagai mekanisme untuk memastikan kode Anda tidak pernah mengakses atau memodifikasi referensi yang tidak lagi valid. Referensi valid selama mengacu pada objek atau struktur yang valid. Konteks aman menentukan kapan variabel dapat ditetapkan atau ditetapkan ulang. Konteks ref-safe menentukan kapan variabel dapat ditetapkan ref atau ref ditetapkan kembali. Penugasan menetapkan variabel ke nilai baru; penetapan ref menetapkan variabel untuk merujuk ke lokasi penyimpanan yang berbeda.
Parameter referensi
Anda menerapkan salah satu pengubah berikut ke deklarasi parameter untuk meneruskan argumen berdasarkan referensi, bukan berdasarkan nilai:
ref
: Argumen harus diinisialisasi sebelum memanggil metode . Metode ini dapat menetapkan nilai baru ke parameter , tetapi tidak diperlukan untuk melakukannya.out
: Metode panggilan tidak diperlukan untuk menginisialisasi argumen sebelum memanggil metode . Metode harus menetapkan nilai ke parameter .ref readonly
: Argumen harus diinisialisasi sebelum memanggil metode . Metode ini tidak dapat menetapkan nilai baru ke parameter .in
: Argumen harus diinisialisasi sebelum memanggil metode . Metode ini tidak dapat menetapkan nilai baru ke parameter . Pengkompilasi mungkin membuat variabel sementara untuk menyimpan salinan argumen kein
parameter.
Anggota kelas tidak dapat memiliki tanda tangan yang hanya berbeda dengan ref
, , ref readonly
, in
atau out
. Kesalahan kompilator terjadi jika satu-satunya perbedaan antara dua anggota jenis adalah salah satunya memiliki ref
parameter dan yang lain memiliki out
parameter , , ref readonly
atau in
. Namun, metode dapat kelebihan beban ketika satu metode memiliki ref
parameter , , ref readonly
in
, atau out
dan yang lain memiliki parameter yang diteruskan oleh nilai, seperti yang ditunjukkan dalam contoh berikut. Dalam situasi lain yang memerlukan pencocokan tanda tangan, seperti menyembunyikan atau menimpa, in
, , ref
ref readonly
, dan out
merupakan bagian dari tanda tangan dan tidak cocok satu sama lain.
Ketika parameter memiliki salah satu pengubah sebelumnya, argumen yang sesuai dapat memiliki pengubah yang kompatibel:
- Argumen untuk
ref
parameter harus menyertakan pengubahref
. - Argumen untuk
out
parameter harus menyertakan pengubahout
. - Argumen untuk
in
parameter dapat secara opsional menyertakan pengubahin
. Jika pengubahref
digunakan pada argumen sebagai gantinya, pengkompilasi mengeluarkan peringatan. - Argumen untuk
ref readonly
parameter harus menyertakan pengubahin
atauref
, tetapi tidak keduanya. Jika tidak ada pengubah yang disertakan, pengkompilasi mengeluarkan peringatan.
Saat Anda menggunakan pengubah ini, mereka menjelaskan bagaimana argumen digunakan:
ref
berarti metode dapat membaca atau menulis nilai argumen.out
berarti metode menetapkan nilai argumen.ref readonly
berarti metode membaca, tetapi tidak dapat menulis nilai argumen. Argumen harus diteruskan oleh referensi.in
berarti metode membaca, tetapi tidak dapat menulis nilai argumen. Argumen akan diteruskan oleh referensi atau melalui variabel sementara.
Anda tidak dapat menggunakan pengubah parameter sebelumnya dalam jenis metode berikut:
- Metode asinkron, yang Anda tentukan menggunakan pengubah async.
- Metode iterator, yang mencakup yield return atau pernyataan
yield break
.
Metode ekstensi juga memiliki batasan penggunaan kata kunci argumen ini:
- Kata
out
kunci tidak dapat digunakan pada argumen pertama dari metode ekstensi. - Kata
ref
kunci tidak dapat digunakan pada argumen pertama metode ekstensi ketika argumen bukanstruct
, atau jenis generik yang tidak dibatasi untuk menjadi struct. - Kata
ref readonly
kunci danin
tidak dapat digunakan kecuali argumen pertama adalahstruct
. - Kata
ref readonly
kunci danin
tidak dapat digunakan pada jenis generik apa pun, bahkan ketika dibatasi menjadi struct.
Properti bukan variabel. Ini adalah metode. Properti tidak dapat menjadi argumen untuk ref
parameter.
ref
pengubah parameter
Untuk menggunakan parameter ref
, definisi metode dan metode panggilan harus menggunakan kata kunci ref
secara eksplisit. (Kecuali bahwa metode panggilan bisa menghilangkan ref
saat melakukan panggilan COM.)
void Method(ref int refArgument)
{
refArgument = refArgument + 44;
}
int number = 1;
Method(ref number);
Console.WriteLine(number);
// Output: 45
Argumen yang diteruskan ke ref
parameter harus diinisialisasi sebelum diteruskan.
out
pengubah parameter
Untuk menggunakan parameter out
, definisi metode dan metode panggilan harus secara eksplisit menggunakan kata kunci out
. Contohnya:
int initializeInMethod;
OutArgExample(out initializeInMethod);
Console.WriteLine(initializeInMethod); // value is now 44
void OutArgExample(out int number)
{
number = 44;
}
Variabel yang diteruskan sebagai out
argumen tidak harus diinisialisasi sebelum diteruskan dalam panggilan metode. Namun, metode yang dipanggil diperlukan untuk menetapkan nilai sebelum metode tersebut kembali.
Metode dekonstruksi mendeklarasikan parameternya dengan pengubah out
untuk mengembalikan beberapa nilai. Metode lain dapat mengembalikan tuple nilai untuk beberapa nilai pengembalian.
Anda dapat mendeklarasikan variabel dalam pernyataan terpisah sebelum meneruskannya sebagai out
argumen. Anda juga dapat mendeklarasikan out
variabel dalam daftar argumen panggilan metode, bukan dalam deklarasi variabel terpisah. out
deklarasi variabel menghasilkan kode yang lebih ringkas, dapat dibaca, dan juga mencegah Anda menetapkan nilai secara tidak sengaja ke variabel sebelum panggilan metode. Contoh berikut menentukan number
variabel dalam panggilan ke metode 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
Anda juga dapat mendeklarasikan variabel lokal yang diketik secara implisit.
ref readonly
Pengubah
Pengubah ref readonly
harus ada dalam deklarasi metode. Pengubah di situs panggilan bersifat opsional. Pengubah in
atau ref
dapat digunakan. Pengubah ref readonly
tidak valid di situs panggilan. Pengubah mana yang Anda gunakan di situs panggilan dapat membantu menjelaskan karakteristik argumen. Anda hanya dapat menggunakan ref
jika argumen adalah variabel, dan dapat ditulis. Anda hanya dapat menggunakan in
saat argumen adalah variabel. Ini mungkin dapat ditulis, atau baca saja. Anda tidak dapat menambahkan salah satu pengubah jika argumen bukan variabel, tetapi merupakan ekspresi. Contoh berikut menunjukkan kondisi ini. Metode berikut menggunakan pengubah ref readonly
untuk menunjukkan bahwa struktur besar harus diteruskan oleh referensi karena alasan performa:
public static void ForceByRef(ref readonly OptionStruct thing)
{
// elided
}
Anda dapat memanggil metode menggunakan pengubah ref
atau in
. Jika Anda menghilangkan pengubah, pengkompilasi mengeluarkan peringatan. Saat argumen adalah ekspresi, bukan variabel, Anda tidak dapat menambahkan in
pengubah atau ref
, jadi Anda harus menekan peringatan:
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
Jika variabel adalah readonly
variabel, Anda harus menggunakan pengubah in
. Pengkompilasi mengeluarkan kesalahan jika Anda menggunakan pengubah sebagai gantinya ref
.
Pengubah ref readonly
menunjukkan bahwa metode mengharapkan argumen menjadi variabel daripada ekspresi yang bukan variabel. Contoh ekspresi yang bukan variabel adalah konstanta, nilai pengembalian metode, dan properti. Jika argumen bukan variabel, pengkompilasi mengeluarkan peringatan.
in
pengubah parameter
Pengubah in
diperlukan dalam deklarasi metode tetapi tidak perlu di situs panggilan.
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;
}
Pengubah in
memungkinkan pengompilasi membuat variabel sementara untuk argumen dan meneruskan referensi baca-saja ke argumen tersebut. Pengkompilasi selalu membuat variabel sementara ketika argumen harus dikonversi, ketika ada konversi implisit dari jenis argumen, atau ketika argumen adalah nilai yang bukan variabel. Misalnya, ketika argumen adalah nilai harfiah, atau nilai yang dikembalikan dari pengakses properti. Saat API Anda mengharuskan argumen diteruskan oleh referensi, pilih ref readonly
pengubah alih-alih pengubah in
.
Metode yang didefinisikan menggunakan in
parameter berpotensi mendapatkan pengoptimalan performa. Beberapa struct
argumen jenis mungkin berukuran besar, dan ketika metode dipanggil dalam perulangan yang ketat atau jalur kode penting, biaya penyalinan struktur tersebut sangat besar. Metode mendeklarasikan in
parameter untuk menentukan bahwa argumen dapat diteruskan oleh referensi dengan aman karena metode yang disebut tidak mengubah status argumen tersebut. Meneruskan argumen tersebut dengan referensi menghindari salinan mahal (potensial). Anda secara eksplisit menambahkan pengubah in
di situs panggilan untuk memastikan argumen diteruskan berdasarkan referensi, bukan berdasarkan nilai. Secara eksplisit menggunakan in
memiliki dua efek berikut:
- Menentukan
in
di situs panggilan memaksa pengkompilasi untuk memilih metode yang ditentukan dengan parameter yangin
cocok. Jika tidak, ketika dua metode berbeda di hadapanin
saja, kelebihan beban berdasarkan nilai adalah kecocokan yang lebih baik. - Dengan menentukan
in
, Anda menyatakan niat Anda untuk meneruskan argumen berdasarkan referensi. Argumen yang digunakan denganin
harus mewakili lokasi yang dapat langsung dirujuk. Aturan umum yang sama untukout
argumen danref
berlaku: Anda tidak dapat menggunakan konstanta, properti biasa, atau ekspresi lain yang menghasilkan nilai. Jika tidak, menghilangkan di situs panggilan menginformasikan pengkompilasi bahwa tidak masalah untuk membuat variabel sementara untuk diteruskanin
dengan referensi baca-saja ke metode . Pengkompilasi membuat variabel sementara untuk mengatasi beberapa batasan denganin
argumen:- Variabel sementara memungkinkan konstanta waktu kompilasi sebagai parameter
in
. - Variabel sementara mengizinkan properti, atau ekspresi lain untuk parameter
in
. - Variabel sementara memungkinkan argumen di mana ada konversi implisit dari jenis argumen ke jenis parameter.
- Variabel sementara memungkinkan konstanta waktu kompilasi sebagai parameter
Dalam semua instans sebelumnya, kompilator membuat variabel sementara yang menyimpan nilai konstanta, properti, atau ekspresi lainnya.
Kode berikut menunjukkan berbagai aturan ini:
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`
Sekarang, misalkan metode lain yang menggunakan argumen berdasarkan nilai tersedia. Hasilnya berubah seperti yang ditunjukkan dalam kode berikut:
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`
Satu-satunya panggilan metode saat argumen diteruskan berdasarkan referensi adalah yang terakhir.
Catatan
Kode sebelumnya menggunakan int
sebagai jenis argumen untuk kesederhanaan. Karena int
tidak lebih besar dari referensi di sebagian besar mesin modern, tidak ada manfaatnya untuk meneruskan satu int
sebagai referensi baca-saja.
params
Pengubah
Tidak ada parameter lain yang diizinkan setelah params
kata kunci dalam deklarasi metode, dan hanya satu params
kata kunci yang diizinkan dalam deklarasi metode.
Jenis parameter yang params
dideklarasikan harus merupakan jenis koleksi. Jenis koleksi yang dikenali adalah:
- Jenis
T[]
array dimensi tunggal , dalam hal ini jenis elemen adalahT
. - Jenis rentang:
System.Span<T>
System.ReadOnlySpan<T>
Di sini, jenis elemennya adalahT
.
- Jenis dengan metode buat yang dapat diakses dengan jenis elemen yang sesuai. Metode buat diidentifikasi menggunakan atribut yang sama yang digunakan untuk ekspresi koleksi.
- Jenis struct atau class yang mengimplementasikan System.Collections.Generic.IEnumerable<T> di mana:
- Jenis memiliki konstruktor yang dapat dipanggil tanpa argumen, dan konstruktor setidaknya dapat diakses sebagai anggota yang mendeklarasikan.
- Jenis memiliki metode
Add
instans (bukan ekstensi) di mana:- Metode dapat dipanggil dengan argumen nilai tunggal.
- Jika metode umum, argumen jenis dapat disimpulkan dari argumen .
- Metode ini setidaknya dapat diakses sebagai anggota yang mendeklarasikan. Di sini, jenis elemen adalah jenis iterasi dari jenis .
- Jenis antarmuka:
Sebelum C# 13, parameter harus berupa array dimensi tunggal.
Saat Anda memanggil metode dengan parameter params
, Anda dapat meneruskan:
- Daftar argumen yang dipisahkan koma dari jenis elemen array.
- Kumpulan argumen dari jenis yang ditentukan.
- Tidak ada argumen. Jika Anda tidak mengirim argumen, panjang daftar
params
adalah nol.
Contoh berikut menunjukkan berbagai cara di mana argumen dapat dikirim ke parameter 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[]
*/
Resolusi kelebihan beban dapat menyebabkan ambiguitas ketika argumen untuk params
parameter adalah jenis koleksi. Jenis koleksi argumen harus dapat dikonversi ke jenis koleksi parameter. Ketika kelebihan beban yang berbeda memberikan konversi yang lebih baik untuk parameter tersebut, metode tersebut mungkin lebih baik. Namun, jika argumen ke parameter adalah elemen diskrit atau hilang, semua kelebihan beban dengan jenis parameter yang berbeda params
sama untuk parameter tersebutparams
.
Untuk detail selengkapnya, lihat bagian tentang Daftar argumen di Spesifikasi Bahasa C#. Spesifikasi bahasa adalah sumber definitif untuk sintaks dan penggunaan C#.