Bagikan melalui


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.
  • 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 ke in parameter.

Anggota kelas tidak dapat memiliki tanda tangan yang hanya berbeda dengan ref, , ref readonly, inatau out. Kesalahan kompilator terjadi jika satu-satunya perbedaan antara dua anggota jenis adalah salah satunya memiliki ref parameter dan yang lain memiliki outparameter , , ref readonlyatau in . Namun, metode dapat kelebihan beban ketika satu metode memiliki refparameter , , ref readonlyin, 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, , refref 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 pengubah ref .
  • Argumen untuk out parameter harus menyertakan pengubah out .
  • Argumen untuk in parameter dapat secara opsional menyertakan pengubah in . Jika pengubah ref digunakan pada argumen sebagai gantinya, pengkompilasi mengeluarkan peringatan.
  • Argumen untuk ref readonly parameter harus menyertakan pengubah in atau ref , 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 bukan struct, atau jenis generik yang tidak dibatasi untuk menjadi struct.
  • Kata ref readonly kunci dan in tidak dapat digunakan kecuali argumen pertama adalah struct.
  • Kata ref readonly kunci dan in 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 yang in cocok. Jika tidak, ketika dua metode berbeda di hadapan in 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 dengan in harus mewakili lokasi yang dapat langsung dirujuk. Aturan umum yang sama untuk out argumen dan ref 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 diteruskan in dengan referensi baca-saja ke metode . Pengkompilasi membuat variabel sementara untuk mengatasi beberapa batasan dengan in 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.

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:

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