Saturday, December 24, 2016

Bab 7. Pemrograman C# Belajar Dari Contoh



7. Sekali Lagi Metode Dan Kelas







Bab ini kembali akan membahas kelas dan metode, diawali dengan menjelaskan tentang bagaimana mengendalikan akses terhadap anggota suatu kelas. Bab ini kemudian mendiskusikan pelewatan objek dan pengembalian objek sebagai nilai balik, mengoverload metode, berbagai bentuk Main(), rekursi, dan penggunaan katakunci static.


Pengendalian Akses Terhadap Anggota Kelas
Meskipun pendekatan C# sedikit lebih rumit, namun pada intinya, ada dua tipe dasar dari anggota kelas: public dan private. Anggota publik dapat dengan bebas diakses oleh kode yang didefinisikan di luar kelasnya. Ini merupakan tipe anggota kelas yang telah digunakan sejauh ini. Anggota privat hanya dapat diakses oleh metode yang didefinisikan di dalam kelasnya. Melalui penggunaan anggota privat inilah akses dikendalikan.

Pembatasan akses terhadap anggota kelas merupakan bagian fundamental dalam pemrograman berorientasi objek karena hal ini membantu untuk menghindarkan objek dari penyalahgunaan. Dengan mengijinkan akses terhadap data privat hanya untuk metode-metode yang didefinisikan di dalam kelasnya, Anda dapat mencegah penugasan nilai yang tidak diinginkan pada data privat tersebut. Tidak dimungkinkan bagi kode di luar kelas untuk secara langsung menetapkan nilai dari anggota privat. Anda juga dapat mengendalikan secara tepat bagaimana dan kapan data di dalam sebuah objek digunakan.


Pemodifikasi Akses C#
Kendali akses anggota dicapai melalui penggunaan empat pemodifikasi akses: public, private, protected, dan internal. Pada bab ini, yang dibahas hanya public dan private. Pemodifikasi akses protected akan dibahas pada Bab 10 karena melibatkan pewarisan. Pemodifikasi internal hanya bisa diterapkan pada assembly.

Ketika anggota sebuah kelas dimodifikasi oleh penspesifikasi public, anggota itu dapat diakses oleh sembarang kode di dalam program Anda. Hal ini mencakup semua metode yang didefinisikan di dalam kelas lain.

Ketika anggota suatu kelas dispesifikasi sebagai private, maka anggota itu hanya dapat diakses oleh anggota lain di dalam kelas tersebut. Jadi, metode yang didefinisikan di dalam kelas lain tidak dapat mengaksesnya. Seperti dijelaskan pada Bab 5, jika tidak ada penspesifikasi akses yang digunakan, anggota kelas tersebut secara default dispesifikasi private.
Penspesifikasi akses ditempatkan di awal statemen deklarasi anggota. Berikut beberapa contohnya:

public string pesanError;
private double balon;
private bool apakahError(byte status) { // ...

Untuk memahami perbedaan antara public dan private, perhatikan program berikut:

// Akses public versus private.
using System;

class KelasKu {
  private int alfa; // secara eksplisit private
  int beta; // akses private access secara default
  public int gamma; // akses public

  // Beberapa metode untuk mengakses alfa dan beta. Boleh bagi anggota
  // dari suatu kelas untuk mengakses anggota private sesama kelas.
  public void SetAlfa(int a) {
    alfa = a;
  }

  public int GetAlfa() {
    return alfa;
  }

  public void SetBeta(int a) {
    beta = a;
  }

  public int GetBeta() {
    return beta;
  }
}

class DemoAkses {
  static void Main() {
      KelasKu ob = new KelasKu();

    // Akses terhadap alfa dan beta diijinkan melalui metode sesama kelas.
    ob.SetAlfa(-99);
    ob.SetBeta(19);

    Console.WriteLine("ob.alfa adalah " + ob.GetAlfa());
    Console.WriteLine("ob.beta adalah " + ob.GetBeta());

    // Anda tidak bisa mengakses alfa atau beta seperti ini:
    // ob.alfa = 10; // Salah! alfa adalah private!
    // ob.beta = 9; // Salah! beta adalah private!
   
    // Diijinkan untuk secara langsung mengakses gamma karena ia public.
    ob.gamma = 99;
  }
}

Keluaran program adalah:

ob.alfa adalah -99
ob.beta adalah 19

Seperti yang dapat Anda lihat, di dalam kelas KelasKu, alfa dispesifikasi sebagai private, beta secara default private, dan gamma dispesifikasi sebagai public. Karena alfa dan beta adalah private, maka keduanya tidak dapat diakses oleh kode di luar kelasnya. Oleh karena itu, di dalam kelas DemoAkses, keduanya tidak dapat diakses secara langsung. Keduanya, alfa dan beta, harus diakses melalui metode public, seperti SetAlfa() dan GetAlfa(). Sebagai contoh, jika Anda menghapus simbol komentar dar awal baris berikut

// ob.alfa = 10; // Salah! alfa adalah private!

Anda tidak akan bisa mengkompilasi program ini karena terjadi pelanggaran akses. Meskipun akses terhadap alfa olek kode di luar kelas KelasKu tidak diijinkan, beberapa metode di dalam KelasKu dengan bebas dapat mengaksesnya, seperti SetAlfa() dan GetAlfa(). Hal yang sama berlaku untuk beta.

Poin kunci di sini adalah: Sebuah anggota privat dapat digunakan secara bebas oleh anggota-anggota lain dari kelas yang sama, tetapi tidak bisa diakses oleh kode di luar kelas tersebut.


Menerapkan Akses Publik Dan Privat
Penggunaan yang tepat dari akses publik dan privat merupakan komponen kunci dalam keberhasilan pemrograman berorientasi objek. Meskipun tidak ada aturan tegas mengenai hal ini, berikut diberikan beberapa prinsip umum yang bisa dijadikan acuan:
·         Anggota suatu kelas yang hanya digunakan di dalam kelas itu sendiri harus dideklarasikan privat.
·         Data istans yang harus dalam rentang nilai tertentu harus dideklarasikan privat, sehingga metode publik bisa berperan sebagai pemeriksa rentang.
·         Jika pengubahan suatu anggota dapat menyebabkan pengaruh melebih anggota itu sendiri (yaitu, mempengaruhi aspek lain dari objek), maka anggota itu harus privat dan akses terhadapnya harus dikendalikan.
·         Anggota yang bisa membahayakan suatu objek ketika disalahgunakan harus dideklarasikan privat. Akses terhadap anggota ini harus melalui metode publik untuk mencegah penyalahgunaan.
·         Metode yang mendapatkan (get) dan menetapkan (set) nilai dari data privat harus dideklarasikan publik.


Mengendalikan Akses: Sebuah Studi Kasus
Untuk lebih memahami “bagaimana dan mengapa” akses kendali dilakukan, berikut disajikan sebuah studi kasus. Salah satu contoh penting dalam pemrograman beroerintasi objek adalah sebuah kelas yang mengimplementasikan sebuah tumpukan. Seperti yang mungkin Anda telah ketahui, tumpukan merupakan suatu struktur data yang mengimplementasikan LIFO (last-in, firts-out). Nama tumpukan berasal dari analogi tumpukan piring. Piring pertama pada meja merupakan piring terakhir yang akan digunakan.

Tumpukan merupakan contoh klasik dalam pemrograman berorientasi objek karena mengkombinasikan penyimpanan informasi dengan beberapa metode yang mengakses informasi tersebut. Jadi, tumpukan merupakan mesin data yang melaksanakan pendekatan LIFO. Kombinasi semacam itu merupakan pilihan tepat bagi sebuah kelas dimana di dalamnya beberapa metode untuk penyimpanan informasi dideklarasikan privat, dan beberapa metode untuk mengakses informasi dideklarasikan publik.

Tumpukan mendefinisikan dua operasi dasar: push dan pop. Push menempatkan sebuah nilai di atas tumpukan. Operasi pop mengambil atau menghapus sebuah nilai dari atas tumpukan. Jadi, begitu suatu nilai dihapus dari tumpukan, ia tidak bisa diakses lagi.

Contoh yang ditampilkan di sini menciptakan sebuah kelas bernama Tumpukan. Penyimpanan untuk tumpukan disediakan oleh sebuah array privat. Operasi push dan pop tersedia melaui beberapa metode publik pada kelas Tumpukan. Seperti yang ditunjukkan di sini, kelas Tumpukan menyimpan beberapa karakter, tetapi mekanisme yang sama dapat pula dibuat untuk menyimpan sembarang tipe data lain.

// Kelas Tumpukan untuk karakter.
using System;

class Tumpukan {
  // Anggota-anggota ini privat.
  char[] tumpukan; // menampung tumpukan
  int iat; // indeks dari atas tumpukan

  // Menciptakan sebuah Tumpukan kosong dengan ukuran tertentu.
  public Tumpukan(int ukuran) {
    tumpukan = new char[ukuran]; // mengalokasikan memori untuk tumpukan
    iat = 0;
  }

  // Mendorong karakter ke atas tumpukan.
  public void Push(char ch) {
    if(iat==tumpukan.Length) {
      Console.WriteLine(" -- Tumpukan penuh.");
      return;
    }
    tumpukan[iat] = ch;
    iat++;
  }

  // Menghapus sebuah karakter dari atas tumpukan.
  public char Pop()
  {
    if (iat == 0)
    {
        Console.WriteLine(" -- Tumpukan kosong.");
        return (char)0;
    }
    iat--;
    return tumpukan[iat];
  }

  // Menghasilkan true jika tumpukan penuh.
  public bool apaPenuh()
  {
    return iat == tumpukan.Length;
  }

  // Menghasilkan true jika tumpukan kosong.
  public bool apaKosong()
  {
    return iat == 0;
  }

  // Menghasilkan kapasitas total dari tumpukan.
  public int Kapasitas()
  {
    return tumpukan.Length;
  }

  // Menghasilkan jumlah objek yang saat ini pada tumpukan.
  public int GetJumlah()
  {
    return iat;
  }
}

Akan diperiksa program tersebut secara detil. Kelas Tumpukan diawali dengan pendeklarasian dua variabel instans:

// Anggota-anggota ini privat.
char[] tumpukan; // menampung tumpukan
int iat; // indeks dari atas tumpukan

Array tumpukan menyediakan tempat penyimpanan untuk tumpukan, dimana pada kasus ini adalah tumpukan karakter. Perhatikan bahwa tidak ada array yang dialokasikan. Pengalokasian array aktual ditangani oleh konstruktor Tumpukan. Anggota iat memuat indeks dari atas tumpukan.

Kedua anggota iat dan tumpukan secara default private. Hal ini untuk menegakkan mekanisme LIFO (last-in, first-out). Jika akses publik terhadap iat diijinkan, maka elemen-elemen pada tumpukan dapat diakses secara tidak berurutan. Di samping itu, iat merupakan indeks dari elemen teratas di dalam tumpukan, manipulasi terhadap iat oleh kode di luar kelas Tumpukan harus dicegah agar menghindari perusakan tumpukan. Akses terhadap tumpukan dan iat disediakan secara tidak langsung bagi pengguna kelas Tumpukan melalui beberapa metode publik.

Konstruktor Tumpukan ditampilkan berikut:

// Menciptakan sebuah Tumpukan kosong dengan ukuran tertentu.
public Tumpukan(int ukuran) {
  tumpukan = new char[ukuran]; // mengalokasikan memori untuk tumpukan
  iat = 0;
}

Ukuran tumpukan yang diinginkan dilewatkan kepada konstruktor. Konstruktor mengalokasikan array dan menetapkan iat menjadi nol. Jadi, nilai nol di dalam iat mengindikasikan bahwa tumpukan kosong.

Metode publik Push() menempatkan sebuah elemen ke atas tumpukan, ditunjukkan di sini:

// Mendorong karakter ke atas tumpukan.
public void Push(char ch) {
  if(iat==tumpukan.Length) {
    Console.WriteLine(" -- Tumpukan penuh.");
    return;
  }
  tumpukan[iat] = ch;
  iat++;
}

ELemen yang akan ditempatkan ke atas tumpukan dilewatkan di dalam ch. Sebelum elemen tersebut ditambahkan ke tumpukan, pemeriksaan dilakukan untuk memastikan apakah masih ada tempat di dalam array. Ini dilakukan dengan memastikan bahwa iat tidak melebihi panjang tumpukan. Jika masih ada tempat di dalam array, elemen tersebut disimpan di dalam tumpukan pada indeks yang dispesifikasi oleh iat, dan kemudian iat diinkremen. Jadi, iat selalu memuat indeks dari elemen berikutnya di dalam tumpukan.

Untuk menghapus sebuah elemen dari tumpukan, Anda bisa memanggil metode publik Pop(), yang ditunjukkan di sini:

// Menghapus sebuah karakter dari atas tumpukan.
public char Pop()
{
  if (iat == 0)
  {
      Console.WriteLine(" -- Tumpukan kosong.");
      return (char)0;
  }
  iat--;
  return tumpukan[iat];
}

Di sini, nilai dari iat diperiksa. Jika ia bernilai nol, maka tumpukan kosong. Sebaliknya, iat didekremen, dan elemen pada indeks tersebut dijadikan nilai balik.

Meskipun Push() dan Pop() adalah dua metode yang diperlukan untuk mengimplementasikan sebuah tumpukan, beberapa metode lain juga penting, dan kelas Tumpukan mendefinisikan empat metode lainnya. Semua metode tersebut adalah apaPenuh(), apaKosong(), Kapasitas(), dan GetJumlah(). Semua metode tersebut menyediakan informasi tentang keadaan tumpukan, yang ditunjukkan di sini:

// Menghasilkan true jika tumpukan penuh.
public bool apaPenuh()
{
  return iat == tumpukan.Length;
}

// Menghasilkan true jika tumpukan kosong.
public bool apaKosong()
{
  return iat == 0;
}

// Menghasilkan kapasitas total dari tumpukan.
public int Kapasitas()
{
  return tumpukan.Length;
}

// Menghasilkan jumlah objek yang saat ini pada tumpukan.
public int GetJumlah()
{
  return iat;
}

Metode apaPenuh() menghasilkan nilai balik true ketika tumpukan penuh dan false jika sebaliknya. Metode apaKosong() menghasilkan nilai balik true jika tumpukan kosong dan false jika sebaliknya. Untuk mendapatkan kapasitas total tumpukan (yaitu, total jumlah elemen yang bisa dimuat), Anda bisa memanggil metode Kapasitas(). Untuk mendapatkan jumlah elemen yang saat ini disimpan dalam tumpukan, Anda bisa memanggila GetJumlah(). Semua metode tersebut berguna karena informasi yang diberikan memerlukan akses terhadap iat, yang privat. Ini merupakan contoh bagaimana metode publik dapat mengakses anggota privat secara aman.

Program berikut mendemonstrasikan tumpukan:

// Demonstrasi kelas Tumpukan.
using System;

class DemoTumpukan {
  static void Main() {
      Tumpukan tumpukan1 = new Tumpukan(10);
      Tumpukan tumpukan2 = new Tumpukan(10);
      Tumpukan tumpukan3 = new Tumpukan(10);

      char ch;
      int i;

    // Menempatkan beberapa karakter pada tumpukan1.
    Console.WriteLine("Menempatkan A sampai J pada tumpukan1.");
    for(i=0; !tumpukan1.apaPenuh(); i++)
      tumpukan1.Push((char) ('A' + i));

    if(tumpukan1.apaPenuh()) Console.WriteLine("tumpukan1 penuh.");

    // Menampilkan isi dari tumpukan1.
    Console.Write("Isi dari tumpukan1: ");
    while( !tumpukan1.apaKosong() ) {
      ch = tumpukan1.Pop();
      Console.Write(ch);
    }
    Console.WriteLine();

    if(tumpukan1.apaKosong()) Console.WriteLine("tumpukan1 kosong.\n");

    // Menempatkan beberapa karakter lagi pada tumpukan1.
    Console.WriteLine("Lagi menempatkan A sampai J pada tumpukan1.");

    for(i=0; !tumpukan1.apaPenuh(); i++)
      tumpukan1.Push((char) ('A' + i));

    // Sekarang, menghapus dari tumpukan1 dan menempatkan elemen pada tumpukan2.
    // Ini menyebabkan tumpukan2 menampung elemen elemen dengan urutan terbalik.
    Console.WriteLine("Sekarang, mengambil karakter dari tumpukan1 dan menempatkannya " +
                      "pada tumpukan2.");

    while (!tumpukan1.apaKosong())
    {
      ch = tumpukan1.Pop();
      tumpukan2.Push(ch);
    }

    Console.Write("Isi dari tumpukan2: ");

    while (!tumpukan2.apaKosong())
    {
      ch = tumpukan2.Pop();
      Console.Write(ch);
    }

    Console.WriteLine("\n");

    // Menempatkan 5 karakter pada tumpukan.
    Console.WriteLine("Menempatkan 5 karakter pada tumpukan3.");

    for (i = 0; i < 5; i++)
      tumpukan3.Push((char)('A' + i));

    Console.WriteLine("Kapasitas dari tumpukan3: " + tumpukan3.Kapasitas());
    Console.WriteLine("Jumlah objek pada tumpukan3: " +
                       tumpukan3.GetJumlah());
  }
}

Keluaran program ditampilkan di sini:

Menempatkan A sampai J pada tumpukan1.
tumpukan1 penuh.
Isi dari tumpukan1: JIHGFEDCBA
tumpukan1 kosong.

Lagi menempatkan A sampai J pada tumpukan1.
Sekarang, mengambil karakter dari tumpukan1 dan menempatkannya pada tumpukan2.
Isi dari tumpukan2: ABCDEFGHIJ

Menempatkan 5 karakter pada tumpukan3.
Kapasitas dari tumpukan3: 10
Jumlah objek pada tumpukan3: 5


Melewatkan Referensi Kepada Metode
Sampai sejauh ini, contoh pada buku ini hanya menggunakan tipe nilai, seperti int atau double, sebagai parameter pada metode. Namun, sebenarnya tipe referensi juga umum dipakai sebagai parameter. Sebagai contoh, perhatikan contoh berikut:

// Referensi dapat dilewatkan kepada metode.
using System;

class KelasKu {
  int alfa, beta;

  public KelasKu(int i, int j)
  {
    alfa = i;
    beta = j;
  }

  // Menghasilkan true jika ob memuat nilai sama dengan objek pemanggil.
  public bool SamaDengan(KelasKu ob)
  {
      if ((ob.alfa == alfa) & (ob.beta == beta))
        return true;
    else return false;
  }

  // Membuat salinan dari ob.
  public void Salin(KelasKu ob)
  {
    alfa = ob.alfa;
    beta = ob.beta;
  }

  public void Tampil()
  {
    Console.WriteLine("alfa: {0}, beta: {1}",
                      alfa, beta);
  }
}

class PelewatanObjek {
    static void Main() {
        KelasKu ob1 = new KelasKu(4, 5);
        KelasKu ob2 = new KelasKu(6, 7);
      
        Console.Write("ob1: ");
        ob1.Tampil();
       
        Console.Write("ob2: ");
        ob2.Tampil();
       
        if (ob1.SamaDengan(ob2))
            Console.WriteLine("ob1 dan ob2 mempunyai nilai sama.");
        else
            Console.WriteLine("ob1 dan ob2 mempunyai nilai beda.");
       
        Console.WriteLine();
       
        // Sekarang, membuat ob1 salinan dari obe2.
        ob1.Salin(ob2);
       
        Console.Write("ob1 setelah penyalinan: ");
        ob1.Tampil();
       
        if (ob1.SamaDengan(ob2))
            Console.WriteLine("ob1 dan ob2 mempunyai nama sama.");
        else
            Console.WriteLine("ob1 dan ob2 mempunyai nilai beda.");
    }
}

Keluaran program ditampilkan di sini:

ob1: alfa: 4, beta: 5
ob2: alfa: 6, beta: 7
ob1 dan ob2 mempunyai nilai beda.

ob1 setelah penyalinan: alfa: 6, beta: 7
ob1 dan ob2 mempunyai nama sama.

Metode SamaDengan() dan Salin() masing-masing mengambil sebuah argumen bertipe KelasKu sebagai argumen. Metode SamaDengan() membandingkan nilai alfa dan beta di dalam objek pemanggil dengan nilai alfa dan beta di dalam objek yang dilewatkan melalui ob. Metode ini menghasilkan true hanya jika kedua objek memuat nilai sama. Metode Salin() menugaskan nilai alfa dan beta di dalam objek yang ditunjuk oleh ob kepada alfa dan beta di dalam objek pemanggil. Seperti yang ditunjukkan pada contoh ini, secara sintaks, cara tipe referensi dilewatkan kepada metode sama dengan pelewatan nilai.


Bagaimana Argumen Dilewatkan
Seperti didemonstrasikan pada contoh sebelumnya, pelewatan referensi objek kepada metode merupakan hal yang cukup sederhana. Namun, ada beberapa aspek yang tidak ditunjukkan pada contoh itu. Pada beberapa kasus, pengaruh pelewatan tipe referensi akan berbeda dari pelewatan nilai. Untuk melihat bagaimana, akan dibahas tentang dua cara pelewatan argumen.

Cara pertama adalah pemanggilan-dengan-nilai. Metode ini menyalin nilai dari sebuah argumen ke dalam parameter formal dari subrutin. Oleh karena itu, perubahan yang dilakukan terhadap parameter subrutin tidak akan berpengaruh pada argumen. Cara kedua adalah pemanggilan-dengan-referensi. Pada metode ini, sebuah referensi yang menunjuk ke argumen dilewatkan kepada parameter. Di dalam subrutin, referensi ini dipakai untuk mengakses argumen aktual yang dispesifikasi dalam pemanggilan. Ini berarti bahwa perubahan yang dilakukan terhadap parameter akan berpengaruh pada argumen yang digunakan untuk memanggil subrutin.

Secara default, C# menggunakan pemanggilan-dengan-nilai, yang berarti bahwa salinan dari argumen dibuat dan diberikan kepada parameter penerima. Jadi, ketika Anda melewatkan sebuah tipe nilai, seperti int atau double, apa yang terjadi pada parameter yang menerima argumen tidak berpengaruh di luar metode. Sebagai contoh, perhatikan program berikut:

// Tipe nilai dilewatkan dengan nilai.
using System;

class Test {
  /* Metode ini tidak menyebabkan perubahan terhadap argumen
     yang digunakan dalam pemanggilan. */
  public void TidakBerubah(int i, int j) {
    i = i + j;
    j = -j;
  }
}

class PemanggilanDenganNilai {
  static void Main() {
    Test ob = new Test();
    int a = 15, b = 20;

    Console.WriteLine("a dan b sebelum pemanggilan: " +
                       a + " " + b);

    ob.TidakBerubah(a, b);
   
    Console.WriteLine("a dan b setelah pemanggilan: " +
                      a + " " + b);
  }
}

Keluaran program ditunjukkan di sini:

a dan b sebelum pemanggilan: 15 20
a dan b setelah pemanggilan: 15 20

Seperti yang dapat Anda lihat, beberapa operasi yang terjadi di dalam TidakBerubah() tidak berpengaruh pada nilai-nilai dari a dan b yang digunakan dalam pemanggilan. Ini karena salinan dari a dan b diberikan kepada parameter i dan j, tetapi a dan b sama sekali independen dari i dan j. Jadi, penugasan nilai baru pada i tidak akan mempengaruhi a.

Ketika Anda melewatkan sebuah referensi kepada metode, situasi menjadi sedikit kompleks. Pada kasus ini, referensi, itu sendiri, dilewatkan dengan nilai. Jadi, salinan dari referensi dibuat dan perubahan terhadap parameter tidak akan berpengaruh pada argumen. (Sebagai contoh, membuat parameter sekarang menunjuk ke sebuah objek baru tidak akan mempengaruhi objek yang ditunjuk oleh argumen). Namun, Anda perlu hati-hati, perubahan yang dilakukan terhadap objek yang ditunjuk oleh parameter akan mempengaruhi objek yang ditunjuk oleh argumen. Akan Anda lihat mengapa.

Ingat bahwa ketika Anda menciptakan sebuah variabel bertipe kelas, Anda sama saja dengan menciptakan sebuah referensi yang menunjuk ke objek dari kelas tersebut. Jadi, ketika Anda melewatkan referensi kepada sebuah metode, parameter yang menerimanya akan menunjuk ke objek sama yang ditunjuk oleh argumen. Oleh karena itu, argumen dan parameter keduanya akan menunjuk ke objek yang sama. Jadi, perubahan yang dilakukan terhadap objek di dalam metode akan berpengaruh pada objek yang digunakan sebagai argumen. Sebagai contoh, perhatikan program berikut:

// Objek dilewatkan dengan referensi.
using System;

class Test
{
    public int a, b;
    public Test(int i, int j)
    {
        a = i;
        b = j;
    }
    /* Melewatkan sebuah objek. Sekarang, ob.a dan ob.b di dalam objek
       yang digunakan dalam pemanggilan akan berubah. */
    public void Berubah(Test ob)
    {
        ob.a = ob.a + ob.b;
        ob.b = -ob.b;
    }
}

class PemanggilanDenganRef {
    static void Main() {
        Test ob = new Test(15, 20);
       
        Console.WriteLine("ob.a dan ob.b sebelum pemanggilan: " +
                          ob.a + " " + ob.b);
       
        ob.Berubah(ob);
       
        Console.WriteLine("ob.a dan ob.b setelah pemanggilan: " +
                          ob.a + " " + ob.b);
    }
}

Keluaran program ditunjukkan di sini:

ob.a dan ob.b sebelum pemanggilan: 15 20
ob.a dan ob.b setelah pemanggilan: 35 -20

Seperti yang dapat Anda lihat, pada kasus ini, beberapa operasi di dalam Berubah() mempengaruhi objek yang digunakan sebagai argumen.


Menggunakan Parameter ref dan out
Seperti yang baru saja dijelaskan, tipe nilai, seperti int atau char, dilewatkan dengan nilai kepada metode. Ini berarti bahwa perubahan pada parameter penerima tipe nilai tidak mempengaruhi argumen aktual yang dipakai dalam pemanggilan. Anda dapat, bagaimanapun, mengubah watak ini. Melalui penggunaan katakunci ref dan out, Anda dimungkinkan untuk melewatkan sembara tipe nilai dengan referensi. Dengan melakukannya, metode dapat mengubah argumen yang dipakai di dalam pemanggilan.

Sebelum membahas mengenai penggunaan ref dan out, Anda perlu memahami mengapa Anda ingin melewatkan sebuah tipe nilai dengan referensi. Secara umum, ada dua alasan: untuk mengijinkan metode untuk mengubah isi argumennya atau untuk mengijinkan metode untuk menghasilkan lebih dari satu nilai balik. Akan dibahas setiap alasan lebih detil lagi.

Seringkali Anda menginginkan sebuah metode yang mampu beroperasi pada argumen aktual yang dilewatkan kepadanya. Contoh penting dari kasus ini adalah metode Tukar() yang menukar nilai dari kedua argumennya. Karena tipe nilai dilewatkan dengan nilai, adalah tidak mungkin untuk menulis sebuah metode yang menukar nilai dari dua int, misalnya, menggunakan mekanisme pelewatan dengan nilai default C#.

Seperti Anda ketahui, statemen return memampukan sebuah metode untuk menghasilkan nilai balik kepada pemanggilnya. Namun, sebuah metode hanya dapat menghasilkan satu nilai balik setiap kali ia dipanggil. Bagaimana jika Anda memerlukan dua atau lebih nilai balik? Sebagai contoh, bagaimana jika Anda ingin menciptakan sebuah metode yang mendekomposisi sebuah angka titik-mengambang menjadi komponen integer dan komponen fraksionalnya? Untuk melakukan ini memerlukan dua informasi yang dijadikan nilai balik: komponen integer dan komponen fraksional. Metode ini tidak dapat ditulis dengan satu nilai balik. Pemodifikasi out menyelesaikan masalah ini.


Menggunakan ref
Pemodifikasi parameter ref menyebabkan C# untuk menciptakan pemanggilan-dengan-referensi, bukan pemanggilan-dengan-nilai. Pemodifikasi ref dispesifikasi ketika metode dideklarasikan dan ketika ia dipanggil. Akan dibahas mulai dengan contoh sederhana. Program berikut menciptakan sebuah metode bernama Kuadrat() yang menghasilkan nilai balik berupa kuadrat dari argumennya. Perhatikan kegunaan dari ref.

// Penggunaan ref untuk melewatkan tipe nilai dengan referensi.
using System;

class TestRef {
    // Metode ini mengubah argumennya. Perhatikan penggunaan ref.
    public void Kuadrat(ref int i)
    {
        i = i * i;
    }
}

class DemoRef {
    static void Main() {
        TestRef ob = new TestRef();
        int a = 10;
       
        Console.WriteLine("a sebelum pemanggilan: " + a);
       
        ob.Kuadrat(ref a); // perhatikan penggunaan ref
       
        Console.WriteLine("a setelah pemanggilan: " + a);
    }
}

Perhatikan bahwa ref mengawali keseluruhan deklarasi parameter di dalam metode dan juga mengawali argumen ketika metode dipanggil. Keluaran dari program ini, seperti yang ditampilkan, menegaskan bahwa nilai dari argumen, a, termodifikasi oleh Kuadrat():

a sebelum pemanggilan: 10
a setelah pemanggilan: 100

Dengan menggunakan ref, sekarang dimungkinkan untuk menulis sebuah metode yang menukar nilai dari dua argumen bertipe nilai. Sebagai contoh, berikut adalah program yang memuat sebuah metode bernama Tukar() yang menukar nilai dari kedua argumen integernya:

// Menukar dua nilai.
using System;

class TukarNilai {
  // Metode ini sekarang mengubah argumennya.
  public void Tukar(ref int a, ref int b) {
    int t;
    t = a;
    a = b;
    b = t;
  }
}

class DemoTukarNilai {
    static void Main() {
        TukarNilai ob = new TukarNilai();
        int x = 10, y = 20;
       
        Console.WriteLine("x dan y sebelum pemanggilan: " + x + " " + y);
       
        ob.Tukar(ref x, ref y);
       
        Console.WriteLine("x dan y sesudah pemanggilan: " + x + " " + y);
    }
}

Keluaran dari program ini adalah

x dan y sebelum pemanggilan: 10 20
x dan y sesudah pemanggilan: 20 10

Hal penting untuk memahami ref: Argumen yang dilewatkan dengan ref harus ditugasi sebuah nilai sebelum pemanggilan. Alasannya adalah bahwa metode yang menerima argumen semacam itu berasumsi bahwa parameter menunjuk ke sebuah nilai valid. Jadi, dengan menggunakan ref, Anda tidak bisa menggunakan metode tersebut untuk memberikan nilai awal pada argumen.


Menggunakan out
Kadangkala Anda ingin menggunakan sebuah parameter referensi untuk menerima nilai dari suatu metode. Sebagai contoh, Anda mungkin memiliki sebuah metode yang melakukan beberapa fungsi, seperti membuka socket jaringan, yang menghasilkan nilai balik berupa status sukses/gagal di dalam parameter referensi. Masalah dengan skenario ini adalah bahwa parameter ref harus diinisialisasi sebelum pemanggilan. Jadi, untuk menggunakan sebuah parameter ref, Anda perlu memberikan nilai awal pada argumen. Solusi alternatif yang lebih baik: parameter out.

Parameter out sama dengan parameter ref dengan satu pengecualian: parameter out hanya bisa dipakai untuk melewatkan sebuah nilai. Anda tidak perlu memberikan nilai awal pada variabel yang digunakan sebagai parameter out sebelum pemanggilan metode. Di samping itu, di dalam metode, parameter out dipandang tidak memiliki nilai awal. Ini mengimplikasikan bahwa metode harus menugaskan sebuah nilai kepada parameter. Jadi, setelah pemanggilan metode, parameter out akan memuat sebuah nilai.

Berikut merupakan sebuah contoh yang menggunakan parameter out. Pada kelas Dekomposisi, metode GetKomponen() mendekomposisi angka titik-mengambang menjadi komponen integer dan komponen fraksional. Perhatikan bagaimana setiap komponen dijadikan nilai balik kepada pemanggil.

// Menggunakan out.
using System;

class Dekomposisi {
    /* Mendekomposisi sebuah angka titik-mengambang menjadi
       komponen integer dan fraksionalnya. */
    public int GetKomponen(double n, out double frak) {
        int seluruh;
        seluruh = (int)n;
        frak = n - seluruh; // melewatkan bagian fraksional kembali ke frak
        return seluruh; // menghasilkan nilai balik
    }
}

class GunakanOut
{
    static void Main()
    {
        Dekomposisi ob = new Dekomposisi();
        int i;
        double f;
       
        i = ob.GetKomponen(10.125, out f);

        Console.WriteLine("Komponen integer adalah " + i);
        Console.WriteLine("Komponen fraksional adalah " + f);
    }
}

Keluaran program ditampilkan di sini:

Komponen integer adalah 10
Komponen fraksional adalah 0.125

Metode GetKomponen() menghasilkan dua informasi. Pertama, bagian integer dari n dikembalikan sebagai nilai balik. Kedua, bagian fraksional dari n dilewatkan kembali ke pemanggil melalui parameter out, yaitu frak. Seperti yang ditunjukkan pada contoh ini, dengan menggunakan out, dimungkinkan bagi metode untuk menghasilkan dua nilai balik.

Tentu saja, Anda tidak dibatasi hanya boleh menggunakan satu parameter out. Sebuah metode dapat menghasilkan sebanyak mungkin informasi yang diperlukan melalui parameter out. Berikut diberikan sebuah contoh yang menggunakan dua parameter out. Metode MempFaktBersama() melakukan dua fungsi. Pertama, ia menentukan apakah kedua integer memiliki faktor bersama (selain 1). Metode ini menghasilkan true jika ya dan false jika sebaliknya. Kedua, jika keduanya mempunyai faktor bersama, MempFaktBersama() akan menghasilkan faktor bersama terkecil dan faktor bersama terbesar di dalam kedua parameter out.

// Menggunakan dua parameter out.
using System;

class Num {
  /* Menentukan jika x dan v mempunyai pembagi bersama.
     Jika ya, maka faktor bersama terbesar dan terkecil dikembalikan
     dalam kedua parameter out. */

  public bool MempFaktBersama(int x, int y,
                              out int terkecil, out int terbesar) {
    int i;
    int maks = x < y ? x : y;
    bool pertama = true;
     
    terkecil = 1;
    terbesar = 1;
      
    // Mencari faktor bersama terkecil dan terbesar.
    for (i = 2; i <= maks / 2 + 1; i++)
    {
      if (((y % i) == 0) & ((x % i) == 0))
      {
        if (pertama)
        {
          terkecil = i;
          pertama = false;
        }
        terbesar = i;
      }
    }
   
    if (terkecil != 1) return true;
    else return false;
  }
}

class DemoOut
{
    static void Main()
    {
        Num ob = new Num();
        int lcf, gcf;
       
        if (ob.MempFaktBersama(231, 105, out lcf, out gcf))
        {
            Console.WriteLine("Lcf dari 231 dan 105 adalah " + lcf);
            Console.WriteLine("Gcf dari 231 dan 105 adalah " + gcf);
        }
        
        else
            Console.WriteLine("Tidak ada faktor bersama dari 35 dan 49.");

        if (ob.MempFaktBersama(35, 51, out lcf, out gcf))
        {
            Console.WriteLine("Lcf dari 35 dan 51 adalah " + lcf);
            Console.WriteLine("Gcf dari 35 dan 51 adalah " + gcf);
        }
       
        else
            Console.WriteLine("Tidak ada faktor bersama dari 35 dan 51.");
    }
}

Dalam Main(), perhatikan bahwa lcf dan gcf tidak ditugasi nilai sebelum pemanggilan terhadap MempFaktBersama(). Akan menjadi error jika parameter ref digunakan menggantikan out. Metode ini menghasilkan true atau false, bergantung pada apakah kedua integer mempunyai faktor bersama atau tidak. Jika mempunyai, faktor bersama terkecil dan terbesar dijadikan nilai balik di dalam parameter out. Keluaran program ditunjukkan di sini:

Lcf dari 231 dan 105 adalah 3
Gcf dari 231 dan 105 adalah 21
Tidak ada faktor bersama dari 35 dan 51.


Menggunakan ref Dan out Pada Referensi
Penggunaan ref dan out tidak dibatasi hanya pada pelewatan tipe nilai. Keduanya juga bisa dipakai ketika sebuah referensi dilewatkan. Ketika ref atau out memodifikasi sebuah referensi, ini menyebabkan metode dapat mengubah objek yang ditunjuk oleh referensi, Perhatikan program berikut, yang menggunakan parameter referensi ref untuk menukar dua objek yang ditunjuk oleh dua referensi berikut:

// Menukar dua referensi.
using System;

class TukarRef {
  int a, b;

  public TukarRef (int i, int j)
  {
    a = i;
    b = j;
  }

  public void Tampil() {
    Console.WriteLine("a: {0}, b: {1}", a, b);
  }

  // Metode ini menukar argumennya.
  public void Tukar(ref TukarRef ob1, ref TukarRef ob2)
  {
    TukarRef t;
    t = ob1;
    ob1 = ob2;
    ob2 = t;
  }
}

class DemoTukarRef {
  static void Main() {
    TukarRef x = new TukarRef(1, 2);
    TukarRef y = new TukarRef(3, 4);

    Console.Write("x sebelum pemanggilan: ");
    x.Tampil();

    Console.Write("y sebelum pemanggilan: ");
    y.Tampil();

    Console.WriteLine();

    // Saling-tukar dua objek yang ditunjuk oleh x dan y.
    x.Tukar(ref x, ref y);
    x.Tampil();
   
    Console.Write("y setelah pemanggilan: ");
    y.Tampil();
  }
}

Keluaran dari program ini adalah:

x sebelum pemanggilan: a: 1, b: 2
y sebelum pemanggilan: a: 3, b: 4

a: 3, b: 4
y setelah pemanggilan: a: 1, b: 2

Pada contoh ini, metode Tukar() menukar objek yang ditunjuk oleh kedua argumennya. Sebelum pemanggilan Tukar(), x menunjuk ke sebuah objek yang memuat nilai 1 dan 2, dan y menunjuk ke sebuah objek yang memuat nilai 3 dan 4. Setelah pemanggilan Tukar(), x menunjuk ke sebuah objek yang memuat nilai 3 dan 4, dan y menunjuk ke sebuah objek yang memuat nilai 1 dan 2. Jika parameter ref tidak digunakan, maka penukaran di dalam Tukar() tidak akan berpengaruh di luar Tukar(). Anda bisa membuktikan hal ini dengan menghapus ref dari Tukar().


Menggunakan Jumlah Argumen Tak-Tentu
Ketika Anda menciptakan sebuah metode, biasanya Anda telah mengetahui jumlah argumen yang akan dilewatkan kepadanya, tetapi ada kalanya Anda tidak mengetahuinya. Kadangkala Anda ingin menciptakan sebuah metode yang dapat dilewatkan sejumlah argumen tak tentu banyaknya. Sebagai contoh, perhatikan sebuah metode yang mencari nilai terkecil dari sebuah himpunan nilai. Kepada metode semacam itu dapat dilewatkan dua nilai, atau tiga, atau empat, dan seterusnya. Metode semacam itu tidak bisa diciptakan menggunakan parameter biasa. Anda harus menggunakan sebuah parameter bertipe spesial, yaitu parameter params.

Pemodifikasi params dipakai untuk mendeklarasikan sebuah parameter array yang akan menerima sejumlah nol atau lebih argumen. Jumlah elemen di dalam array akan sama dengan jumlah argumen yang dilewatkan kepada metode. Program Anda kemudian mengakses array tersebut untuk mendapatkan argumen.

Berikut merupakan sebuah contoh yang menggunakan params dalam menciptakan sebuah metode bernama MinNil(), yang menghasilkan nilai minimum dari sehimpunan nilai:

// Demonstrasi params.
using System;

class Min {
  public int MinNilai(params int[] angka) {
    int m;

    if (angka.Length == 0)
    {
      Console.WriteLine("Error: tidak ada argumen.");
      return 0;
    }

    m = angka[0];

    for (int i = 1; i < angka.Length; i++)
      if (angka[i] < m) m = angka[i];
      return m;
    }
  }

  class DemoParams {
    static void Main() {
        Min ob = new Min();
        int min;
        int a = 10, b = 20;

        // Memanggil dengan 2 nilai.
        min = ob.MinNilai(a, b);
        Console.WriteLine("Minimum adalah " + min);
       
        // Memanggil dengan 3 nilai.
        min = ob.MinNilai(a, b, -1);
        Console.WriteLine("Minimum adalah " + min);
       
        // Memanggil dengan 5 nilai.
        min = ob.MinNilai(18, 23, 3, 14, 25);
        Console.WriteLine("Minimum adalah " + min);
       
        // Dapat memanggil dengan sebuah array int.
        int[] args = { 45, 67, 34, 9, 112, 8 };
        min = ob.MinNilai(args);
        Console.WriteLine("Minimum adalah " + min);
    }
}

Keluaran dari program ini adalah:

Minimum adalah 10
Minimum adalah -1
Minimum adalah 3
Minimum adalah 8

Setiap kali MinNilai() dipanggil, argumen-argumen dilewatkan kepadanya melalui array angka. Panjang dari array sama dengan jumlah elemen. Jadi, Anda dapat menggunakan MinNilai() untuk mencari nilai minimum dari sejumlah nilai.

Perhatikan pemanggilan terakhir terhadap MinNilai(), dimana sebuah array int dilewatkan kepada metode tersebut. Hal ini legal untuk dilakukan. Ketika sebuah parameter params diciptakan, ia menerima sejumlah argumen sebanyak tak-tentu atau sebuah array yang memuat beberapa argumen.

Meskipun Anda dapat melewatkan sebuah parameter params yang memuat sejumlah tak-tentu argumen, semua argumen harus bertipe kompatibel. Sebagai contoh, pemanggilan MinNilai() seperti ini:

min = ob.MinNil(1, 2.2); // Salah!

tidak diijinkan karena tidak ada konversi otomatis dari double(2.2) menjadi int, yang merupakan tipe dari array angka di dalam MinNilai().

Ketika menggunakan params, Anda perlu hati-hati mengenai kondisi batas karena sebuah parameter params dapat menerima sejumlah tak-tentu argumen, bahkan nol!. Sebagai contoh, secara sintaks adalah valid untuk memanggil MinNilai() seperti ini:

min = ob.MinNilai(); // tidak ada argumen
min = ob.MinNilai(3); // 1 argumen

Inilah mengapa ada pemeriksaan di dalam MinNilai() untuk memastikan bahwa sedikitnya satu elemen harus ada di dalam array angka sebelum mencoba untuk mengakses elemen tersebut. Jika tidak ada pemeriksaan, maka eksepsi runtime akan dihasilkan jika MinNilai() dipanggil tanpa argumen. (Eksepsi akan dipelajari pada Bab 12). Pada program berikut, metode TampilArg() mengambil satu parameter string dan sebuah array integer params:

// Menggunakan parameter reguler dengan parameter params.
using System;

class KelasKu
{
    public void TampilArg(string msg, params int[] angka)
    {
        Console.Write(msg + ": ");
       
        foreach (int i in angka)
            Console.Write(i + " ");
       
        Console.WriteLine();
    }
}

class DemoParams2 {
    static void Main() {
        KelasKu ob = new KelasKu();

        ob.TampilArg("Berikut adalah beberapa integer",
                     1, 2, 3, 4, 5);

        ob.TampilArg("Berikut dua lagi integer",
                     17, 20);
    }
}

Berikut adalah keluaran program yang dihasilkan:

Berikut adalah beberapa integer: 1 2 3 4 5
Berikut dua lagi integer: 17 20


Menghasilkan Nilai Balik Objek
Sebuah metode dapat menghasilkan nilai balik bertipe sembarang, termasuk tipe kelas. Sebagai contoh, versi berikut dari kelas PersegiPanjang menyertakan sebuah metode bernama Perbesar() yang menciptakan suatu persegipanjang yang secara proporsional sama dengan persegipanjang pemanggil, tetapi lebih besar dengan sebuah faktor tertentu:

// Menghasilkan sebuah objek.
using System;

class PersegiPanjang {
  int lebar;
  int tinggi;

  public PersegiPanjang(int w, int h)
  {
    lebar = w;
    tinggi = h;
 }

  public int Luas() {
    return lebar * tinggi;
  }

  public void Tampil() {
    Console.WriteLine(lebar + " " + tinggi);
  }

  /* Menghasilkan sebuah persegipanjang yang lebih besar dengan
     faktor tertentu daripada persegipanjang pemanggil. */
  public PersegiPanjang Perbesar(int faktor) {
      return new PersegiPanjang(lebar * faktor, tinggi * faktor);
  }
}

class ObjekPersegiPanjang {
  static void Main() {
    PersegiPanjang r1 = new PersegiPanjang(4, 5);

    Console.Write("Dimensi dari r1: ");
    r1.Tampil();
    Console.WriteLine("Luas dari r1: " + r1.Luas());
   
    Console.WriteLine();

    // Menciptakan sebuah persegipanjang dua kali lebih besar dari r1.
    PersegiPanjang r2 = r1.Perbesar(2);
    Console.Write("Dimensi dari r2: ");
    r2.Tampil();
    Console.WriteLine("Luas dari r2: " + r2.Luas());
  }
}

Keluaran program ditampilkan di sini:

Dimensi dari r1: 4 5
Luas dari r1: 20

Dimensi dari r2: 8 10
Luas dari r2: 80

Ketika sebuah objek dijadikan nilai balik oleh metode, objek tersebut tetap ada sampai tidak ada lagi referensi yang menunjuk kepadanya. Jadi, sebuah objek tidak akan dihancurkan hanya karena metode yang menciptakannya berhenti.

Salah satu terapan dari hal ini adalah kelas Factory. Kelas Factory dipakai unutk menciptakan objek dari kelasnya sendiri. Dalam beberapa situasi, Anda mungkin tidak ingin memberikan akses terhadap konstruktor kelas kepada pengguna karena alasan keamanan atau karena penciptaan objek bergantung pada beberapa faktor luar. Berikut diberikan contohnya:

// Menggunakan kelas Factory.
using System;

class KelasKu {
  int a, b; // private

  // Menciptakan kelas Factory untuk KelasKu.
  public KelasKu Factory(int i, int j)
  {
    KelasKu t = new KelasKu();
    t.a = i;
    t.b = j;
    return t; // menghasilkan objek
  }

  public void Tampil() {
    Console.WriteLine("a dan b: " + a + " " + b);
  }
}

class MembuatObjek {
  static void Main() {
    KelasKu ob = new KelasKu();
    int i, j;

    // Menghasilkan objek-objek menggunakan Factory.
    for(i=0, j=10; i < 10; i++, j--) {
        KelasKu obLain = ob.Factory(i, j); // membuat objek
        obLain.Tampil();
    }

    Console.WriteLine();
  }
}

Program tersebut menghasilkan keluaran:

a dan b: 0 10
a dan b: 1 9
a dan b: 2 8
a dan b: 3 7
a dan b: 4 6
a dan b: 5 5
a dan b: 6 4
a dan b: 7 3
a dan b: 8 2
a dan b: 9 1

KelasKu tidak mendefinisikan konstruktor, jadi hanya konstruktor default yang disediakan oleh kompiler. Oleh karena itu, tidak dimungkinkan untuk menetapkan nilai untuk a dan b menggunakan konstruktor. Tetapi, kelas Factory dapat menciptakan objek dimana di dalamnya a dan b diberikan nilai. Di samping itu, karena a dan b privat, penggunaan Factory() merupakan satu-satunya cara untuk menetapkan kedua nilai dari variabel tersebut.

Di dalam Main(), sebuah objek KelasKu diinstansiasi, dan metode Factory pada objek tersebut dipakai di dalam loop for untuk menciptakan sepuluh objek. Baris kode yang menciptakan sepuluh objek ditampilkan di sini:

for(i=0, j=10; i < 10; i++, j--) {
   KelasKu obLain = ob.Factory(i, j); // membuat objek
   obLain.Tampil();
}

Pada tiap iterasi, sebuah referensi objek bernama obLain diciptakan, dan ia ditugasi dengan referensi ke objek yang diciptakan oleh Factory. Di akhir tiap iterasi di dalam loop, obLain menjadi keluar skop, sehingga objek tersebut akan dihancurkan.


Menghasilkan Nilai Balik Array
Karena di dalam C# array diimplementasikan sebagai objek, sebuah metode juga bisa menjadikan array sebagai nilai balik. Sebagai contoh, pada program berikut, metode CariFaktor() menghasilkan nilai balik sebuah array yang memuat beberapa faktor dari argumen yang dilewatkan kepadanya:

// Menghasilkan sebuah array.
using System;

class Faktor {
  /* Menghasilkan sebuah array yang memuat faktor-faktor dari angka.
     jumfaktor akan memuat jumlah faktor yang ditemukan. */
    public int[] CariFaktor(int angka, out int jumfaktor) {
    int[] faktor = new int[80]; // berukuran 80
    int i, j;

    // Mencari faktor-faktor dan menempatkannya di dalam array faktor.
    for (i = 2, j = 0; i < angka / 2 + 1; i++)
      if ((angka % i) == 0)
      {
          faktor[j] = i;
        j++;
      }
   
    jumfaktor = j;
    return faktor;
  }
}

class CariFaktor {
    static void Main() {
        Faktor f = new Faktor();
        int jumfaktor;
        int[] faktor;

        faktor = f.CariFaktor(1000, out jumfaktor);

        Console.WriteLine("Faktor dari 1000 adalah: ");

        for (int i = 0; i < jumfaktor; i++)
            Console.Write(faktor[i] + " ");
        Console.WriteLine();
    }
}

Keluaran program ditampilkan di sini:

Faktor dari 1000 adalah:
2 4 5 8 10 20 25 40 50 100 125 200 250 500

Di dalam Faktor, CariFaktor() dideklarasikan seperti ini:

public int[] CariFaktor(int angka, out int jumfaktor) {

Perhatikan bagaimana tipe nilai balik array int dispesifikasi. Sintaks ini dapat digeneralisir. Sebagai contoh, berikut mendeklarasikan sebuah metode bernama suatuMetode() yang menghasilkan nilai balik array double dua dimensi:

public double[,] suatuMetode() { // ...


Mengoverload Metode
Dalam C#, dua atau lebih metode di dalam kelas yang sama dapat mempunyai nama sama, sepanjang deklarasi parameternya berbeda. Ketika hal ini terjadi, metode-metode tersebut dikatakan dioverload, dan proses ini dikenal dengan pengoverloadan metode. Pengoverloadan metode merupakan salah satu cara C# mengimplementasikan polimorfisme.

Secara umum, untuk mengoverload sebuah metode, cukup dengan mendeklarasikan beberapa versi metode yang berbeda. Kompiler akan menangani semuanya. Anda harus mengantisipasi satu hal penting ini: Tipe dan/atau jumlah parameter dari tiap metode teroverload harus berbeda. Tidak cukup bagi metode-metode teroverload untuk berbeda pada tipe nilai baliknya saja. Semua versi teroverload harus berbeda dalam tipe parameter atau/dan jumlah parameternya.

Ketika sebuah metode parameter teroverload dipanggil, versi dari metode yang dieksekusi adalah yang memiliki parameter cocok dengan argumen. Berikut adalah contoh sederhana yang mengilustrasikan pengoverloadan metode:

// Demonstrasi pengoverloadan metode.
using System;

class Overload
{
    public void DemoOvl()
    {
        Console.WriteLine("Tidak ada parameter");
    }

    // Mengoverload DemoOvl untuk satu parameter.
    public void DemoOvl(int a)
    {
        Console.WriteLine("Satu parameter: " + a);
    }

    // Mengoverload DemoOvl untuk dua parameter.
    public int DemoOvl(int a, int b)
    {
        Console.WriteLine("Dua parameter: " + a + " " + b);
        return a + b;
    }

    // Mengoverload DemoOvl untuk dua parameter double.
    public double DemoOvl(double a, double b)
    {
        Console.WriteLine("Dua parameter double: " +
                          a + " " + b);
        return a + b;
    }
}
class DemoOverload {
    static void Main() {
        Overload ob = new Overload();
        int resI;
        double resD;

        // Memanggil semua versi dari DemoOvl().
        ob.DemoOvl();
        Console.WriteLine();

        ob.DemoOvl(2);
        Console.WriteLine();

        resI = ob.DemoOvl(4, 6);
        Console.WriteLine("Hasil dari ob.DemoOvl(4, 6): " + resI);
        Console.WriteLine();

        resD = ob.DemoOvl(1.1, 2.32);
        Console.WriteLine("Hasil dari ob.DemoOvl(1.1, 2.32): " + resD);
    }
}

Program menghasilkan keluaran berikut:

Tidak ada parameter

Satu parameter: 2

Dua parameter: 4 6
Hasil dari ob.DemoOvl(4, 6): 10

Dua parameter double: 1.1 2.32
Hasil dari ob.DemoOvl(1.1, 2.32): 3.42

Seperti yang dapat Anda lihat, DemoOvl() dioverload empat kali. Versi pertama tidak mengambil parameter apapun; kedua mengambil satu parameter integer; ketiga mengambil dua parameter integer; dan keempat mengambil dua parameter double. Perhatikan bahwa dua versi pertama dari DemoOvl() menghasilkan void dan dua versi kedua menghasilkan nilai balik. Jadi, mencoba untuk menggunakan dua versi DemoOvl() berikut akan menyebabkan error:

// DemoOvl(int) OK!.
public void DemoOvl(int a) {
  Console.WriteLine("Satu parameter: " + a);
}

/* Error! Dua DemoOvl(int) tidak OK, meskipun
   berbeda tipe nilai balik. */
public int DemoOvl(int a) {
  Console.WriteLine("Satu parameter: " + a);
  return a * a;
}

Seperti Anda ingat dari Bab 2, C# menyediakan konversi tipe implisit otomatis. Konversi ini juga diterapkan pada parameter dari metode teroverload. Sebagai contoh, perhatikan berikut:

// Konversi tipe implisit dapat mempengaruhi metode teroverload.
using System;

class Overload2 {
  public void MetodeKu(int x) {
    Console.WriteLine("Di dalam MetodeKu(int): " + x);
  }

  public void MetodeKu(double x)
  {
    Console.WriteLine("Di dalam MetodeKu(double): " + x);
  }
}

class KonvTipe {
static void Main() {
    Overload2 ob = new Overload2();

    int i = 10;
    double d = 10.1;

    byte b = 99;
    short s = 10;
    float f = 11.5F;

    ob.MetodeKu(i); // memanggil ob.MetodeKu(int)
    ob.MetodeKu(d); // memanggil ob.MetodeKu(double)

    ob.MetodeKu(b); // memanggil ob.MetodeKu(int) -- konversi tipe
    ob.MetodeKu(s); // memanggil ob.MetodeKu(int) -- konversi tipe
    ob.MetodeKu(f); // memanggil ob.MetodeKu(double) -- konversi tipe
  }
}

Program tersebut menghasilkan keluaran:

Di dalam MetodeKu(int): 10
Di dalam MetodeKu(double): 10.1
Di dalam MetodeKu(int): 99
Di dalam MetodeKu(int): 10
Di dalam MetodeKu(double): 11.5

Pada contoh ini, hanya dua versi MetodeKu() yang didefinisikan: satu yang memiliki sebuah parameter int dan lainnya yang mempunyai sebuah parameter double. Namun, dimungkinkan untuk melewatkan sebuah nilai byte, short, atau float kepada MetodeKu(). Pada kasus byte dan short,  karena C# secara otomatis akan mengkonversinya menjadi int. Jadi, MetodeKu(int) yang dipanggil pada kasus tersebut. Pada kasus float, nilai dikonversi menjadi double dan MetodeKu(double) yang dipanggil.

Adalah hal penting untuk memahami bahwa konversi implisit hanya bisa diterapkan jika ada kesesuaian tipe antara parameter dan argumen. Sebagai contoh, berikut adalah program sebelumnya dengan penambahan sebuah versi MetodeKu() yang menspesifikasi parameter byte:

// Konversi tipe implisit dapat mempengaruhi metode teroverload.
using System;

class Overload2 {
  public void MetodeKu(byte x) {
    Console.WriteLine("Di dalam MetodeKu(byte): " + x);
  }

  public void MetodeKu(int x) {
    Console.WriteLine("Di dalam MetodeKu(int): " + x);
  }

  public void MetodeKu(double x)
  {
    Console.WriteLine("Di dalam MetodeKu(double): " + x);
  }
}

class KonvTipe {
static void Main() {
    Overload2 ob = new Overload2();

    int i = 10;
    double d = 10.1;

    byte b = 99;
    short s = 10;
    float f = 11.5F;

    ob.MetodeKu(i); // memanggil ob.MetodeKu(int)
    ob.MetodeKu(d); // memanggil ob.MetodeKu(double)

    ob.MetodeKu(b); // memanggil ob.MetodeKu(int) -- konversi tipe
    ob.MetodeKu(s); // memanggil ob.MetodeKu(int) -- konversi tipe
    ob.MetodeKu(f); // memanggil ob.MetodeKu(double) -- konversi tipe
  }
}

Keluaran program ditampilkan di sini:

Di dalam MetodeKu(int): 10
Di dalam MetodeKu(double): 10.1
Di dalam MetodeKu(byte): 99
Di dalam MetodeKu(int): 10
Di dalam MetodeKu(double): 11.5

Pada versi ini, karena terdapat sebuah versi dari MetodeKu() yang mengambil sebuah argumen byte, ketika MetodeKu() dipanggil dengan sebuah argmen byte, MetodeKu(byte) dipanggil dan konversi otomatis menjadi int tidak terjadi.

Kedua ref dan out dapat dilibatkan dalam overload. Sebagai contoh, berikut didefinisikan dua metode berbeda:

public void MetodeKu(int x) {
  Console.WriteLine("Di dalam MetodeKu(int): " + x);
}

public void MetodeKu(ref int x) {
  Console.WriteLine("Di dalam MetodeKu(ref int): " + x);
}

Jadi,

ob.MetodeKu(i)

memanggil MetodeKu(int x), tetapi

ob.MetodeKu(ref i)

memanggil MetodeKu(ref int x).

Meskipun ref dan out dapat dipakai dalam overload, perbedaan keduanya tidak cukup. Sebagai contoh, dua versi MetodeKu() berikut adalah tak valid:

// Salah!
public void MetodeKu(out int x) { // ...
public void MetodeKu (ref int x) { // ...

Pada kasus ini, kompiler tidak bisa membedakan antara kedua versi MetodeKu() hanya karena yang satu menggunakan parameter out int dan yang lain menggunakan parameter ref int.


Mengoverload Konstruktor
Seperti metode, konstruktor juga dapat dioverload. Dengan melakukannya, Anda dapat menciptakan objek dalam berbagai cara. Sebagai contoh, perhatikan program berikut:

// Demonstrasi konstruktor teroverload.
using System;

class KelasKu
{
    public int x;

    public KelasKu()
    {
        Console.WriteLine("Di dalam KelasKu().");
        x = 0;
    }

    public KelasKu(int i)
    {
        Console.WriteLine("Di dalam KelasKu(int).");
        x = i;
    }

    public KelasKu(double d)
    {
        Console.WriteLine("Di dalam KelasKu(double).");
        x = (int)d;
    }
   
    public KelasKu(int i, int j)
    {
        Console.WriteLine("Di dalam KelasKu(int, int).");
        x = i * j;
    }
}

class DemonOverKonst
{
    static void Main()
    {
        KelasKu t1 = new KelasKu();
        KelasKu t2 = new KelasKu(88);
        KelasKu t3 = new KelasKu(17.23);
        KelasKu t4 = new KelasKu(2, 4);

        Console.WriteLine("t1.x: " + t1.x);
        Console.WriteLine("t2.x: " + t2.x);
        Console.WriteLine("t3.x: " + t3.x);
        Console.WriteLine("t4.x: " + t4.x);
    }
}

Keluaran program ditampilkan di sini:

Di dalam KelasKu().
Di dalam KelasKu(int).
Di dalam KelasKu(double).
Di dalam KelasKu(int, int).
t1.x: 0
t2.x: 88
t3.x: 17
t4.x: 8

KelasKu() dioverload dengan empat cara, masing-masing menciptakan objek secara berbeda. Konstruktor yang sesuai dipanggil berdasarkan argumen yang dispesifikasi ketika new dieksekusi. Dengan mengoverload konstruktor suatu kelas, Anda memberikan fleksibilitas pada pengguna kelas dalam hal penciptaan objek.

Salah satu alasan yang paling umum dijumpai mengapa konstruktor perlu dioverload adalah untuk membolehkan satu objek menginisialisasi objek lainnya. Sebagai contoh, berikut merupakan versi mutakhir dari kelas Tumpukan yang sebelumnya telah ditulis. Versi ini mengijinkan satu tumpukan dikonstruksi dari tumpukan lain:

// Kelas Tumpukan untuk karakter.
using System;

class Tumpukan
{
    // Anggota-anggota ini privat.
    char[] tumpukan; // menampung tumpukan
    int iat; // indeks dari atas tumpukan

    // Menciptakan sebuah Tumpukan kosong dengan ukuran tertentu.
    public Tumpukan(int ukuran)
    {
        tumpukan = new char[ukuran]; // mengalokasikan memori untuk tumpukan
        iat = 0;
    }

    // Mengkonstruksi sebuah Tumpukan dari sebuah tumpukan.
    public Tumpukan(Tumpukan ob) {
    // Mengalokasikan memori untuk tumpukan.
        tumpukan = new char[ob.tumpukan.Length];

    // Menyalin elemen-elemen ke tumpukan baru.
    for(int i=0; i < ob.iat; i++)
        tumpukan[i] = ob.tumpukan[i];

    // Menetapkan iat untuk tumpukan baru.
    iat = ob.iat;
    }

    // Mendorong karakter ke atas tumpukan.
    public void Push(char ch)
    {
        if (iat == tumpukan.Length)
        {
            Console.WriteLine(" -- Tumpukan penuh.");
            return;
        }
        tumpukan[iat] = ch;
        iat++;
    }

    // Menghapus sebuah karakter dari atas tumpukan.
    public char Pop()
    {
        if (iat == 0)
        {
            Console.WriteLine(" -- Tumpukan kosong.");
            return (char)0;
        }
        iat--;
        return tumpukan[iat];
    }

    // Menghasilkan true jika tumpukan penuh.
    public bool apaPenuh()
    {
        return iat == tumpukan.Length;
    }

    // Menghasilkan true jika tumpukan kosong.
    public bool apaKosong()
    {
        return iat == 0;
    }

    // Menghasilkan kapasitas total dari tumpukan.
    public int Kapasitas()
    {
        return tumpukan.Length;
    }

    // Menghasilkan jumlah objek yang saat ini pada tumpukan.
    public int GetJumlah()
    {
        return iat;
    }
}

// Demonstrasi kelas Tumpukan.
class DemoTumpukan {
  static void Main() {
    Tumpukan tumpukan1 = new Tumpukan(10);

    char ch;
    int i;

    // Menempatkan beberapa karakter pada tumpukan1.
    Console.WriteLine("Menempatkan A sampai J pada tumpukan1.");
    for (i = 0; !tumpukan1.apaPenuh(); i++)
        tumpukan1.Push((char)('A' + i));

    // Menciptakan sebuah salinan dari tumpukan1.
    Tumpukan tumpukan2 = new Tumpukan(tumpukan1);

    // Menampilkan isi dari tumpukan1.
    Console.Write("Isi dari tumpukan1: ");
    while (!tumpukan1.apaKosong()) {
      ch = tumpukan1.Pop();
      Console.Write(ch);
    }
 
    Console.WriteLine();

    Console.Write("Isi dari tumpukan2: ");
    while (!tumpukan2.apaKosong())
    {
      ch = tumpukan2.Pop();
      Console.Write(ch);
    }
 
    Console.WriteLine("\n");
  }
}

Keluaran program ditampilkan di sini:

Menempatkan A sampai J pada tumpukan1.
Isi dari tumpukan1: JIHGFEDCBA
Isi dari tumpukan2: JIHGFEDCBA

Dalam DemoTumpukan, tumpukan pertama, tumpukan1, dikonstruksi dan diisi dengan beberapa karakter. Tumpukan ini kemudian dipakai untuk mengkonstruksi tumpukan kedua, tumpukan2. Ini menyebabkan konstruktor Tumpukan berikut dieksekusi:

// Mengkonstruksi sebuah Tumpukan dari sebuah tumpukan.
public Tumpukan(Tumpukan ob) {
  // Mengalokasikan memori untuk tumpukan.
  tumpukan = new char[ob.tumpukan.Length];

  // Menyalin elemen-elemen ke tumpukan baru.
  for(int i=0; i < ob.iat; i++)
    tumpukan[i] = ob.tumpukan[i];

  // Menetapkan iat untuk tumpukan baru.
  iat = ob.iat;
}

Di dalam konstruktor ini, sebuah array dialokasikan, cukup panjang untuk menampung semua elemen yang dimuat tumpukan yang dilewatkan di dalam ob. Kemudian, isi array di dalam ob disalin ke array baru. Setelah konstruktor selesai dieksekusi, tumpukan baru dan tumpukan asli tetap terpisah, tetapi dengan isi sama.


Memanggil Konstruktor Teroverload Melalui this
Ketika bekerja dengan konstruktor teroverload, dimungkinkan dalam menggunakan satu konstruktor memanggil konstruktor lain. Dalam C#, ini dilakukan dengan menggunakan katakunci this. Bentuk umumnya ditunjukkan di sini:

nama-konstruktor(daftar-parameter1) : this(daftar-parameter2) {
  // ... tubuh konstruktor, boleh kosong
}

Ketika konstruktor dieksekusi, konstruktor teroverload yang cocok dengan daftar parameter yang dispesifikasi oleh daftar-parameter2 lebih dahulu dieksekusi. Kemudian, jika terdapat sembarang statemen di dalam konstruktor asli, semuanya akan dieksekusi. Berikut disajikan sebuah contoh:

// Demonstrasi pemanggilan sebuah konstruktor melalui this.
using System;

class XYCoord {
    public int x, y;
   
    public XYCoord() : this(0, 0) {
        Console.WriteLine("Di dalam XYCoord()");
    }
   
    public XYCoord(XYCoord obj) : this(obj.x, obj.y) {
        Console.WriteLine("Di dalam XYCoord(obj)");
    }
   
    public XYCoord(int i, int j) {
        Console.WriteLine("Di dalam XYCoord(int, int)");
        x = i;
        y = j;
    }
}

class DemonOverloadKonst {
    static void Main() {
        XYCoord t1 = new XYCoord();
        XYCoord t2 = new XYCoord(8, 9);
        XYCoord t3 = new XYCoord(t2);

        Console.WriteLine("t1.x, t1.y: " + t1.x + ", " + t1.y);
        Console.WriteLine("t2.x, t2.y: " + t2.x + ", " + t2.y);
        Console.WriteLine("t3.x, t3.y: " + t3.x + ", " + t3.y);
    }
}

Program menghasilkan keluaran berikut:

Di dalam XYCoord(int, int)
Di dalam XYCoord()
Di dalam XYCoord(int, int)
Di dalam XYCoord(int, int)
Di dalam XYCoord(obj)
t1.x, t1.y: 0, 0
t2.x, t2.y: 8, 9
t3.x, t3.y: 8, 9

Berikut adalah bagaimana program bekerja. Di dalam kelas XYCoord, satu-satunya konstruktor yang secara aktual menginisialisasi bidang x dan y adalah XYCoord(int, int). Dua konstruktor yang lain hanya memanggil XYCoord(int, int) melalui this. Sebagai contoh, ketika objek t1 diciptakan, konstruktorya, XYCoord(int, int), dipanggil. ini mengakibatkan this(0, 0) dieksekusi, dimana pada kasus ini diterjemahkan menjadi pemanggilan terhadap XYCoord(0, 0). Penciptaan objek t2 sama seperti penciptaan t1.

Keuntungan lain adalah bahwa Anda dapat menciptakan konstruktor dengan argumen default terimplikasi yang digunakan ketika argumen tidak dispesifikasi secara eksplisit. Sebagai contoh, Anda dapat menciptakan konstruktor XYCoord lain seperti ini:

public XYCoord(int x) : this(x, x) { }

Konstruktor ini secara otomatis menetapkan koordinat y bernilai sama dengan koordinat x.


Penginisialisasi Objek
Penginisialisasi objek menyediakan cara lain dalam menciptakan sebuah objek dan menginisialisasi bidang dan propertinya. (Lihat Bab 9 untuk diskusi tentang properti). Dengan menggunakan penginisialisasi objek, Anda tidak memanggil konstruktor kelas dengan cara yang normal. Tetapi, Anda perlu menspesifikasi nama bidang dan/atau properti yang akan diinisialisasi, memberikannya masing-masing dengan nilai awal. Jadi, sintaks penginisialisasi objek memberikan cara alternatif untuk secara eksplisit memanggil konstruktor kelas. Akan diberikan sebuah contoh sederhana:

// Contoh demonstrasi sederhana yang menggunakan penginisialisasi objek.
using System;

class KelasKu {
    public int Hitung;
    public string Str;
}

class DemoInitObj {
    static void Main() {
        // Menciptakan objek KelasKu menggunakan penginisialisasi objek.
        KelasKu obj = new KelasKu { Hitung = 100, Str = "Pengujian" };
       
        Console.WriteLine(obj.Hitung + " " + obj.Str);
    }
}

Berikut adalah keluaran program:

100 Pengujian

Seperti ditampilkan pada keluaran, nilai dari obj.Hitung diinisialisasi dengan 100 dan nilai dari obj.Str diinisialisasi dengan “Pengujian”. Perhatikan bahwa KelasKu tidak mendefinisikan sembarang konstruktor eksplisit, dan bahwa sintaks konstruktor normal tidak digunakan. Objek obj diciptakan menggunakan baris berikut:

KelasKu obj = new KelasKu { Hitung = 100, Str = "Pengujian" };

Di sini, nama bidang secara eksplisit dispesifikasi bersamaan dengan nilai awalnya.

Penting untuk memahami bahwa urutan penginisialisasi adalah hal yang tidak penting. Sebagai contoh, obj dapat diinisialisasi seperti ini:

KelasKu obj = new KelasKu { Str = "Pengujian", Hitung = 100 };

Dalam statemen ini, inisialisasi atas Str mendahului inisialisasi atas Hitung. Di dalam program, hal itu kebalikannya. Namun, tetapi akan memberikan hasil sama.

Berikut merupakan format umum atas sintaks inisialisasi objek:

new nama-kelas { nama = ekspr, nama = ekspr, nama = ekspr, ... }

Di sini, nama menspesifikasi nama bidang atau properti yang diakses melalui nama-kelas. Tipe ekpresi penginisialisasi yang ditentukan oleh ekspr harus kompatibel dengan tipe bidang atau properti.


Argumen Opsional
C# mempunyai fitur baru yang menambah fleksibilitas dalam hal penspesifikasian argumen ketika sebuah metode dipanggil. Fitur ini dinamakan argumen opsional, yang membolehkan Anda mendefinisikan sebuah nilai default untuk suatu parameter metode. Nilai default ini dipakai jika argumen yang berkaitan dengan parameter tersebut tidak dispesifikasi ketika metode dipanggil. Argumen opsional dapat menyederhanakan pemanggilan metode, dimana di dalamnya argumen default dapat diterapkan pada beberapa parameter.

Argumen opsional diciptakan dengan membuat parameter opsional. Untuk melakukannya, Anda hanya perlu menspesifikasi nilai default untuk parameter, menggunakan sintaks yang sama dengan inisialisasi variabel. Nilai default harus berupa sebuah ekspresi konstanta. Sebagai contoh, perhatikan deklarasi metode ini:

static void MetOpsArg(int alfa, int beta=10, int gamma = 20) {

Di sini terdapat dua parameter opsional yang dideklarasikan, beta dan gamma. Pada kasus ini, beta mempunyai nilai default 10, dan gamma mempunyai nilai default 20. Kedua nilai default ini digunakan jika tidak ada argumen yang dispesifikasi untuk parameter tersebut ketika metode dipanggil. Perhatikan bahwa alfa bukan parameter opsional, tetapi parameter normal, jadi argumen untuknya selalu diperlukan. Metode MetOpsArg() dapat dipanggil dengan beberapa cara ini:

// Melewatkan semua argumen secara eksplisit.
MetOpsArg (1, 2, 3);

// Membiarkan gamma menjadi default.
MetOpsArg (1, 2);

// Membiarkan kedua beta dan gamma menjadi default.
MetOpsArg (1);

Pemanggilan pertama melewatkan nilai 1 kepada alfa, 2 kepada beta, dan 3 kepada gamma. Jadi, ketiga argumen dispesifikasi secara eksplisit, dan tidak ada nilai default yang digunakan. Pemanggilan kedua melewatkan nilai 1 kepada alfa dan 2 kepada beta, tetapi gamma menggunakan nilai default 20. Pemanggilan ketiga melewatkan 1 kepada alfa, dan membiarkan beta dan gamma menggunakan nilai default. Adalah hal penting untuk memahami bahwa tidak bisa membuat beta menjadi nilai default tanpa juga menetapkan gamma menjadi default. Begitu argumen pertama menjadi default, maka argumen lainnya harus juga default.

Program berikut menunjukkan keseluruhan proses yang baru saja dijelaskan:

// Demonstrasi argumen opsional.
using System;

class DemoOpsiArg {
  static void MetOpsArg(int alfa, int beta = 10, int gamma = 20) {
    Console.WriteLine("Berikut adalah alfa, beta, dan gamma: " +
                      alfa + " " + beta + " " + gamma);
  }

  static void Main() {
    // Melewatkan semua argumen secara eksplisit.
    MetOpsArg(1, 2, 3);

    // Membuat gamma default.
    MetOpsArg(1, 2);

    // Membuat kedua beta dan gamma default.
    MetOpsArg(1);
  }
}

Keluaran program di sini menegaskan penggunaan argumen default:

Berikut adalah alfa, beta, dan gamma: 1 2 3
Berikut adalah alfa, beta, dan gamma: 1 2 20
Berikut adalah alfa, beta, dan gamma: 1 10 20

Penting untuk memahami bahwa semua parameter opsional harus berada di sisi kanan dari parameter normal. Sebagai contoh, deklarasi berikut adalah salah:

int Contoh(string nama = "user", int userId) { // Error!

Untuk membenarkan deklarasi ini, Anda harus mendeklarasikan userId sebelum nama. Begitu Anda mulai mendeklarasikan parameter opsional, Anda tidak bisa lagi menspesifikasi parameter normal. Sebagai contoh, deklarasi ini juga tidak benar:

int Contoh(int akunId, string nama = "user", int userId) { // Error!

Karena nama adalah opsional, userId juga  harus ditempatkan sebelum nama (atau userId juga dibuat menjadi opsional).

Argumen Opsional Versus Overloading
Dalam beberapa kasus, argumen opsional bisa menjadi alternatif bagi pengoverloadan metode. Untuk memahami mengapa, sekali lagi perhatikan MetOpsArg() yang tadi. Sebelum penggunaan argumen opsional dalam C#, Anda akan memerlukan tiga versi berbeda dari MetOpsArg() untuk memiliki fungsionalitas yang sama. Ketiga versi tersebut mempunyai deklarasi berikut:

static void MetOpsArg (int alfa)
static void MetOpsArg (int alfa, int beta)
static void MetOpsArg (int alfa, int beta, int gamma)

Ketiga metode teroverload tersebut dapat dipanggil dengan satu, dua, atau tiga argumen. Tubuh ketiga metode tersebut juga harus menyediakan nilai-nilai dari beta dan gamma ketika tidak dilewatkan. Pada kasus ini, argumen opsional menjadi pendekatan yang lebih baik.


Argumen Opsional Versus Ambiguitas
Satu masalah yang bisa terjadi ketika menggunakan argumen opsional adalah ambiguitas. Ini terjadi ketika sebuah metode yang mempunyai parameter opsional dioverload. Pada beberapa kasus, kompiler menjadi tidak bisa menentukan versi mana yang akan dipanggil ketika argumen opsional tidak dispesifikasi. Sebagai contoh, perhatikan kedua versi dari MetOpsArg():

static void MetOpsArg(int alfa, int beta=10, int gamma = 20) {
  Console.WriteLine("Berikut adalah alfa, beta, dan gamma: " +
                    alfa + " " + beta + " " + gamma);
}

static void MetOpsArg (int alfa, double beta=10.0, double gamma = 20.0) {
  Console.WriteLine("Berikut adalah alfa, beta, dan gamma: " +
                    alfa + " " + beta + " " + gamma);
}

Perhatikan bahwa satu-satunya perbedaan antara kedua versi adalah tipe beta dan gamma, yang merupakan parameter opsional. Dalam versi pertama, tipenya adalah int. Dalam versi kedua, tipenya adalah double. Pemanggilan terhadap MetOpsArg() berikut menjadi ambigu:

MetOpsArg(1); // Error! Ambigu

Pemanggilan ini ambigu karena kompiler tidak mengetahui versi mana yang akan dipanggil. Pada kasus ini, Andalah yang bertanggung jawab untuk menghindarinya.

Contoh Praktis Dari Argumen Opsional
Program berikut mengilustrasikan bagaimana argumen opsional dapat menyederhanakan pemanggilan metode. Program ini mendeklarasikan sebuah metode bernama Tampil(), yang menampilkan sebuah string. String dapat ditampilkan secara keseluruhan atau hanya sepotong saja.

// Menggunakan argumen opsional untuk menyederhanakan pemanggilan metode.
using System;

class UseOptArgs {
  // Menampilkan sepotong string.
  static void Tampil(string str, int mulai = 0, int berhenti = -1) {

  if(berhenti < 0)
    berhenti = str.Length;

  // Memeriksa kondisi di-luar-batas.
  if(berhenti > str.Length | mulai > berhenti | mulai < 0)
    return;

  for(int i=mulai; i < berhenti; i++)
    Console.Write(str[i]);

  Console.WriteLine();
  }

  static void Main() {
    Tampil("ini adalah sebuah test");
    Tampil("ini adalah sebuah test", 11);
    Tampil("ini adalah sebuah test", 5, 12);
  }
}

Keluaran program ditampilkan di sini:

ini adalah sebuah test
sebuah test
dalah s

Akan diperiksa metode Tampil() dengan lebih detil. String yang akan ditampilkan dilewatkan di dalam argumen pertama. Argumen ini diperlukan. Argumen kedua dan ketiga adalah opsional. Argumen opsional menspesifikasi indeks awal dan indeks akhir dari sepenggal string untuk ditampilkan. Jika sebuah nilai tidak dilewatkan kepada berhenti, maka nilai defaultnya adalah -1, yang mengindikasikan titik berhenti adalah akhir string. Jika sebuah nilai tidak dilewatkan kepada mulai, maka nilai defaultnya adalah 0. Oleh karena itu, jika tidak ada argumen opsional yang diberikan, string akan ditampilkan secara menyeluruh. Ini berarti bahwa jika Anda memanggil Tampil() dengan satu argumen (string yang akan ditampilkan), maka string ditampilkan secara keseluruhan. Jika Anda memanggil Tampil() dengan dua argumen, maka karakter yang dimulai dari mulai sampai akhir karakter akan ditampilkan. Jika ketiga argumen dilewatkan, maka sepenggal string dari mulai sampai berhenti akan ditampilkan.


Argumen Bernama
Fitur lain yang berkaitan dengan pelewatan argumen kepada sebuah metode adalah argumen bernama. Argumen bernama ditambahkan oleh C# 4.0. Seperti Anda ketahui, ketika Anda melewatkan argumen kepada sebuah metode, urutan argumen harus sesusi dengan urutan parameter yang didefinisikan oleh metode. Dengan kata lain, posisi argumen di dalam daftar argumen menentukan ke parameter mana ia ditugaskan. Argumen bernama menghapus pembatasan ini. Dengan argumen bernama, Anda dapat menspesifikasi nama parameter yang berkaitan dengan argumen tertentu. Dengan menggunakan argumen bernama, urutan argumen menjadi tidak penting. Jadi, argumen bernama sedikit seperti penginisialisasi objek yang dideskripsikan sebelumnya, meskipun dengan sintaks yang berbeda.

Untuk menspesifikasi sebuah argumen bernama, digunakan sintaks ini:

nama-param : nilai

Di sini, nama-param menspesifikasi nama parameter yang akan menerima nilai yang dilewatkan. Berikut adalah contoh sederhana yang mendemonstrasikan argumen bernama. Program menciptakan sebuah metode bernama ApaFaktor() yang menghasilkan true jika parameter pertama dapat dibagi oleh parameter kedua (tanpa sisa).

// Menggunakan argumen bernama.
using System;

class DemoArgBernama {
 
  // Menentukan jika satu nilai bisa dibagi oleh nilai lain (tanpa sisa).
  static bool ApaFaktor(int nil, int pembagi) {
    if((nil % pembagi) == 0) return true;
    return false;
}

static void Main() {
  // Berikut menunjukkan berbagai cara dalam memanggil ApaFaktor().

  // Memanggil dengan menggunakan argumen posisional.
  if(ApaFaktor(10, 2))
    Console.WriteLine("2 adalah faktor dari 10.");

  // Memanggil dengan menggunakan argumen bernama.
  if(ApaFaktor(nil: 10, pembagi: 2))
    Console.WriteLine("2 adalah faktor dari 10.");

  // Urutan menjadi tidak penting dengan argumen bernama.
  if(ApaFaktor(pembagi: 2, nil: 10))
    Console.WriteLine("2 adalah faktor dari 10.");

  // Menggunakan argumen posisional dan argumen bernama.
  if(ApaFaktor(10, pembagi: 2))
    Console.WriteLine("2 adalah faktor dari 10.");
  }
}

Keluaran program ditampilkan di sini:

2 adalah faktor dari 10.
2 adalah faktor dari 10.
2 adalah faktor dari 10.
2 adalah faktor dari 10.

Seperti ditampilkan pada keluaran, setiap pemanggilan ApaFaktor() menghasilkan hasil yang sama. Program mendemonstrasikan dua aspek penting dari argumen bernama. Pertama, urutan penspesifikasian argumen tidak menjadi masalah lagi. Sebagai contoh, kedua pemanggilan ini adalah ekivalen:

ApaFaktor(nil :10, pembagi: 2)
ApaFaktor(pembagi: 2, nil: 10)

Menjadi independen merupakan keuntungan utama dari argumen bernama. Ini berarti bahwa Anda tidak perlu mengingat (atau bahkan mengetahui) urutan parameter di dalam metode yang dipanggil. Kedua, perhatikan bahwa Anda dapat menspesifikasi sebuah argumen posisional dan sebuah argumen bernama di dalam pemanggilan yang sama, seperti:

ApaFaktor(10, pembagi: 2)

Akan tetapi, perlu hati-hati, ketika menggabungkan argumen posisional dan argumen bernama, semua argumen posisional harus ditempatkan sebelum argumen bernama.

Argumen bernama juga dapat dipakai bersamaan dengan argumen opsional. Sebagai contoh, diasumsikan bahwa metode Tampil() yang digunakan, berikut adalah beberapa cara pemanggilan metode tersebut dengan argumen bernama:

// Menspesifikasi semua argumen dengan nama.
Tampil(berhenti: 10, str: "ini adalah pengujian", mulai: 0);

// Menjadikan mulai sebagai default.
Tampil (berhenti: 10, str: " ini adalah pengujian");

// Menspesifikasi string dengan posisi, berhenti dengan nama, dan
// menjadikan awal sebagai default.
Tampil ("ini adalah pengujian", berhenti: 10);


Metode Main( )
Sampai sejauh ini, Anda hanya menggunakan satu format dari Main(). Namun, metode ini mempunyai beberapa format teroverload. Beberapa di antaranya dapat dipakai untuk menghasilkan nilai balik, dan beberapa lagi dapat meneriman argumen. Masing-masing versi teroverload dari Main() akan dibahas di sini.


Nilai Balik Dari Main( )
Ketika sebuah program berakhir, Anda dapat menghasilkan nilai balik kepada proses pemanggil (seringkali proses pemanggil adalah sistem operasi) dari Main(). Untuk melakukannya, Anda dapat menggunakan formati Main() ini:

static int Main( )

Perhatikan bahwa metode di atas tidak dideklarasikan sebagai void, tetapi Main() versi ini mempunyai nilai balik bertipe int. Biasanya, nilai balik dari Main() mengindikasikan bahwa program berakhir secara normal atau berakhir secara mendadak karena kondisi abnormal. Secara konvensional, nilai balik 0 biasanya mengindikasikan terminasi normal. Nilai balik selain 0 mengindikasikan beberapa jenis error terjadi.


Melewatkan Argumen Kepada Main( )
Banyak program dapat menerima apa yang dinamakan argumen command-line. Argumen command-line merupakan informasi yang secara langsung ditempatkan setelah nama program pada command-line ketika program dieksekusi. Pada program C#, argumen ini kemudian dilewatkan kepada metode Main(). Untuk menerima argumen tersebut, Anda harus menggunakan salah satu format Main() berikut:

static void Main(string[ ] args)
static int Main(string[ ] args)

Format pertama menghasilkan void; format kedua dapat dipakai untuk menghasilkan sebuah nilai balik bertipe int. Pada kedua format, argumen command-line disimpan sebagai string di dalam array string yang dilewatkan kepada Main(). Panjang dari array args sama dengan jumlah argumen command-line.

Sebagai contoh, program berikut menampilkan semua argumen command-line:

// Menampilkan semua informasi command-line.
using System;

class DemoCL {
    static void Main(string[] args) {
        Console.WriteLine("Ada sebanyak " + args.Length +
                          " argumen command-line.");
       
        Console.WriteLine("Semua argumen tersebut adalah: ");
       
        for (int i = 0; i < args.Length; i++)
            Console.WriteLine(args[i]);
    }
}

Jika DemoCL dieksekusi seperti ini:

DemoCL satu dua tiga

maka Anda akan melihat keluaran:

Ada sebanyak 3 argumen command-line.
Semua argumen tersebut adalah:
satu
dua
tiga


Untuk memahami bagaimana argumen command-line digunakan, perhatikan program berikut. Program ini menggunakan cipher substitusi sederhana untuk mengkode atau mendekondekan pesan. Pesan yang akan dikodekan dispesifikasi pada command line. Cipher ini sangat sederhana: Untuk mengkodekan sebuah kata, setiap huruf diinkremen sebesar 1. Jadi, A menjadi B, dan seterusnya. Untuk mendekode, setiap huruf didekremen sebesar 1.

// Mengkodekan atau mendekodekan pesan menggunakan cipher substitusi sederhana.
using System;

class Cipher {
  static int Main(string[] args) {
    // Melihat apakah argumen ada.
    if(args.Length < 2) {
      Console.WriteLine("Kegunaan: enkode/dekode kata1 [kata2...kataN]");
      return 1; // nilai balik jika kode gagal
    }

    // Jika args ada, arg pertama harus dienkodekan atau didekodekan.
    if(args[0] != "enkode" & args[0] != "dekode") {
      Console.WriteLine("Arg pertama harus dienkodekan atau didekodekan.");
      return 1; // nilai balik jika kode gagal
    }

    // Enkode atau dekode pesan.
    for(int n=1; n < args.Length; n++) {
      for(int i=0; i < args[n].Length; i++) {
        if (args[0] == "enkode")
          Console.Write((char)(args[n][i] + 1));
        else
          Console.Write((char)(args[n][i] - 1));
      }

      Console.Write(" ");
    }

    Console.WriteLine();
    return 0;
  }
}

Untuk menggunakan program, Anda diminta untuk menspesifikasi perintah “enkode” atau “dekode” diikuti dengan frase yang ingin Anda enkript atau dekript. Berikut adalah dua contoh keluaran program:

C:>Cipher enkode satu dua
tbuv  evb

C:>Cipher dekode tbuv evb
satu  dua


Rekursi
Dalam C#, sebuah metode dapat memanggil dirinya sendiri. Proses ini dikenal dengan rekursi, dan metode yang memanggil dirinya sendiri tersebut dikenal dengan rekursif. Secara umum, rekursi merupakan proses pendefinisian secara sirkular. Komponen kunci dari suatu metode rekursif adalah bahwa ia memuat sebuah statemen yang mengeksekusi pemanggilan terhadap dirinya. Rekursi merupakan mekanisme kendali yang tangguh.

Contoh klasik dari rekursi adalah komputasi atas faktorial dari sebuah angka. Faktorial dari sebuah angka N adalah hasil dari 1 x 2 x...x N-1 x N. Program berikut menunjukkan cara rekursif untuk menghitung faktorial dari sebuah angka. Sebagai perbandingan, cara tak-rekursif juga disertakan di dalam program.

// Sebuah contoh rekursi sederhana.
using System;

class Faktorial {
    // Ini adalah sebuah metode rekursif.
    public int FaktR(int n)
    {
        int hasil;
        if (n == 1) return 1;
        hasil = FaktR(n - 1) * n;
        return hasil;
    }
   
    // Ini adalah sebuah metode iteratif ekivalen.
    public int FaktI(int n)
    {
        int t, hasil;
        hasil = 1;

        for (t = 1; t <= n; t++) hasil *= t;
        return hasil;
    }
}

class Rekursi {
    static void Main() {
        Faktorial f = new Faktorial();

        Console.WriteLine("Faktoral menggunakan metode rekursif.");
        Console.WriteLine("Faktorial dari 3 adalah " + f.FaktR(3));
        Console.WriteLine("Faktorial dari 4 adalah " + f.FaktR(4));
        Console.WriteLine("Faktorial dari 5 adalah " + f.FaktR(5));
        Console.WriteLine();

        Console.WriteLine("Faktorial menggunakan metode iteratif.");
        Console.WriteLine("Faktorial dari 3 adalah " + f.FaktI(3));
        Console.WriteLine("Faktorial dari 4 adalah " + f.FaktI(4));
        Console.WriteLine("Faktorial dari 5 adalah " + f.FaktI(5));
    }
}

Keluaran program ditampilkan di sini:

Faktoral menggunakan metode rekursif.
Faktorial dari 3 adalah 6
Faktorial dari 4 adalah 24
Faktorial dari 5 adalah 120

Faktorial menggunakan metode iteratif.
Faktorial dari 3 adalah 6
Faktorial dari 4 adalah 24
Faktorial dari 5 adalah 120

Operasi dari metode tak-rekursif FaktI() harusnya sudah bisa dimengerti dengan jelas. Metode tersebut menggunakan sebuah loop mulai dari 1 dan secara progresif mengalikan setiap angka dengan hasil perkalian bergerak.

Operasi dari metode rekursif FaktR() sedikit lebih kompleks. Ketika FaktR() dipanggil dengan argumen 1, metode menghasilkan 1; sebaliknya, ia menghasilkan FaktR(n-1)*n. Untuk mengevaluasi ekspresi ini, FaktR() dipanggil dengan n-1. Proses ini berulang sampai n sama dengan 1. Sebagai contoh, ketika faktorial dari 2 dihitung, pemanggilan pertama terhadap FaktR() akan menyebabkan pemanggilan kedua dilakukan dengan argumen 1. Pemanggilan kedua tersebut menghasilkan 1, yang kemudian dikalikan dengan 2 (nilai awal dari 2). Hasil yang diperoleh adalah 2. Anda bisa menyisipkan statemen WriteLine() ke dalam FaktR() untuk membuktikan level rekursi atas setiap pemanggilan.

Ketika sebuah metode memanggil dirinya sendiri, variabel dan parameter lokal baru dialokasikan pada tumpukan, dan kode metode dieksekusi dengan variabel tersebut. Pemanggilan rekursif tidak membuat salinan baru dari metode. Hanya argumen yang baru.

Berikut merupakan contoh lain dari rekursi. Metode TampilBalik() menggunakan rekursi untuk menampilkan argumen stringnya dengan gaya terbalik:

// Menampilkan sebuah string secara terbalik menggunakan rekursi.
using System;

class StrBalik {
    // Menampilkan string secara muncur.
    public void TampilBalik(string str)
    {
        if (str.Length > 0)
            TampilBalik(str.Substring(1, str.Length - 1));
        else
            return;
        Console.Write(str[0]);
    }
}

class DemoStrBalik {
    static void Main() {
        string s = "danau toba terbesar se asia tenggara";

        StrBalik rsOb = new StrBalik();

        Console.WriteLine("String asli: " + s);
        Console.Write("String terbalik: ");
       
        rsOb.TampilBalik(s);
        Console.WriteLine();
    }
}

Berikut adalah keluaran program:

String asli: danau toba terbesar se asia tenggara
String terbalik: araggnet aisa es rasebret abot uanad


Memahami static
Ada waktu dimana Anda ingin mendefinisikan sebuah anggota yang yang akan digunakan secara independen oleh sembarang objek dari kelas tersebut. Normalnya, suatu anggota kelas bisa diakses melalui sebuah objek dari kelasnya, tetapi dimungkin pula untuk menciptakan suatu anggota kelas yang bisa dipakai tanpa memerlukan instans kelas atau referensi. Untuk menciptakan anggota kelas semacam itu, pendeklarasiannya diawali dengan katakunci static. Ketika sebuah anggota kelas dideklarasikan static, maka ia dapat diakses sebelum sembarang objek dari kelasnya diciptakan dan tanpa memerlukan referensi objek. Anda bisa mendeklarasikan metode maupun variabel sebagai static. Contoh yang umum dijumpai dari anggota static adalah Main(), yang dideklarasikan static karena ia harus dipanggil oleh sistem operasi ketika program Anda mulai dikompilasi.

Di luar kelas, untuk menggunakan anggota static, Anda perlu menspesifikasi nama dari kelasnya diikuti dengan operator dot. Tidak ada objek yang perlu diciptakan. Anggota static tidak dapat diakses melalui sebuah referensi objek. Ia harus diakses melalui nama kelasnya. Sebagai contoh, jika Anda ingin menugaskan nilai 10 kepada sebuah variabel static, hitung, yang merupakan bagian dari kelas Pewaktu, maka Anda bisa menggunakan baris kode ini:

Pewaktu.hitung = 10;

Format ini sama dengan yang digunakan untuk mengakses variabel instans melalui sebuah objek, tetapi yang tidak sama adalah nama kelas yang digunakan. Sebuah metode static dapat juga dipanggil dengan cara yang sama, menggunakan operator dot dan nama kelas.

Variabel yang dideklarasikan static, secara esensial, adalah variabel global. Ketika objek dari kelasnya dideklarasikan, tidak ada salinan dari variabel static yang dibuat. Semua instans dari kelasnya menggunakan bersama variabel static. Variabel static diinisialisasi sebelum kelasnya digunakan. Jika tidak ada penginisialisasi eksplisit dispesifikasi, maka variabel static diinisialisasi dengan nol untuk tipe numerik, dengan null untuk tipe referensi, atau dengan false untuk tipe bool. Jadi, variabel static selalu memiliki nilai.

Perbedaan antara metode static dan metode normal adalah bahwa metode static dapat dipanggil melalui nama kelasnya, tanpa melalui sembarang instans dari kelas yang sedang diciptakan. Anda telah melihat contohnya: metode Sqrt(), yang merupakan sebuah metode static di dalam kelas System.Math.

Berikut adalah sebuah contoh yang mendeklarasikan variabel static dan metode static:

// Menggunakan static.
using System;

class DemoStatic{
    // Sebuah variabel static.
    public static int Nil = 100;
   
    // Sebuah metode static.
    public static int NilBagi2()
    {
        return Nil / 2;
    }
}

class SDemo {
    static void Main() {
        Console.WriteLine("Nilai awal dari DemoStatic.Nil adalah "
                          + DemoStatic.Nil);

        DemoStatic.Nil = 8;

        Console.WriteLine("DemoStatic.Nil sekarang adalah " + DemoStatic.Nil);

        Console.WriteLine("DemoStatic.NilBagi2(): " +
                           DemoStatic.NilBagi2());
    }
}

Keluaran program ditampilkan di sini:

Nilai awal dari DemoStatic.Nil adalah 100
DemoStatic.Nil sekarang adalah 8
DemoStatic.NilBagi2(): 4

Seperti yang ditampilkan keluaran, variabel static diinisialisasi sebelum sembarang objek dari kelasnya diciptakan. Ada beberapa batasan pada metode static:
·         Metode static tidak memiliki referensi this. Ini karena metode static tidak dieksekusi relatif terhadap sembarang objek.
·         Metode static dapat memanggil secara langsung metode static lain dari kelasnya. Ia tidak bisa memanggil secara langsung metode instans dari kelasnya. Alasannya adalah karena metode instans beroperasi pada objek spesifik, sedangkan metode static tidak dipanggil pada suatu objek spesifik.
·         Batasan yang sama berlaku pada data static. Sebuah data static hanya dapat secara langsung mengakses data static lain yang didefinisikan oleh kelasnya. 

Sebagai contoh, dalam kelas berikut, metode static, NilBagiDenom() adalah ilegal:

class StaticError {
  public int Denom = 3; // sebuah variabel instans normal
  public static int Nil = 1024; // sebuah variabel static

  /* Error! Tidak bisa secara langsung mengakses variabel tak-static
     dari dalam metode static. */
  static int NilBagiDenom() {
    return Nil/Denom; // Tidak akan bisa dikompilasi!
  }
}

Di sini, Denom merupakan sebuah variabel instans biasa yang tidak bisa diakses dari dalam sebuah metode static. Namun, penggunaan Nil di dalam metode static diijinkan karena ia adalah variabel static.

Adalah penting memahami bahwa metode static dapat memanggil metode instans dan mengakses variabel instans dari kelasnya jika dilakukan melalui objek dari kelas itu. Metode static hanya tidak bisa mengakses variabel instans atau metode instans dapat kualifikasi objek. Sebagai contoh, fragmen kode berikut dapat diterima:

class KelasKu {
  // Sebuah metode tak-static.
  void MetTakStatik() {
    Console.WriteLine("Di dalam MetTakStatik ().");
  }

  /* Dapat memanggil metode tak-static melalui sebuah
     referensi objek dari dalam metode static. */
  public static void MetStatik(KelasKu ob) {
    ob. MetTakStatik(); // hal ini OK
  }
}

Di sini, MetTakStatik() dipanggil oleh MetStatik() melalui ob, yang merupakan sebuah objek bertipe KelasKu.

Karena bidang static independen dari sembarang objek spesifik, bidang tersebut berguna ketika Anda perlu mendapatkan informasi yang diterapkan pada keseluruhan kelas. Berikut merupakan contoh dari situasi semacam itu. Program menggunakan sebuah bidang static untuk mendapatkan jumlah objek yang eksis:

// Menggunakan sebuah bidang static untuk menghitung instans.
using System;

class HitungInst {
    static int hitung = 0;

    // Menginkremen hitung ketikan objek diciptakan.
    public HitungInst()
    {
        hitung++;
    }
   
    // Mendekremen hitung ketika objek dihancurkan.
    ~HitungInst()
    {
        hitung--;
    }
   
    public static int GetHitung()
    {
        return hitung;
    }
}

class DemoHitung {
    static void Main() {
        HitungInst ob;

        for (int i = 0; i < 10; i++)
        {
            ob = new HitungInst();
            Console.WriteLine("Jumlah objek saat ini: " + HitungInst.GetHitung());
        }
    }
}

Keluaran program adalah sebagai berikut:

Jumlah objek saat ini: 1
Jumlah objek saat ini: 2
Jumlah objek saat ini: 3
Jumlah objek saat ini: 4
Jumlah objek saat ini: 5
Jumlah objek saat ini: 6
Jumlah objek saat ini: 7
Jumlah objek saat ini: 8
Jumlah objek saat ini: 9
Jumlah objek saat ini: 10

Setiap kali sebuah objek bertipe HitungInst diciptakan, bidang static, hitung, diinkremen. Setiap kali sebuah objek dihancurkan, hitung, didekremen. Jadi, hitung selalu memuat jumlah objek yang sedang eksis. Hal ini dimungkinak hanya melalui penggunaan bidang static.

Berikut adalah satu contoh lebih kompleks yang menggunakan static. Pada awal bab ini, Anda telah melihat bagaimana sebuah metode Factory digunakan untuk menciptakan objek. Pada contoh tersebut, metode Factory merupakan sebuah metode tak-static, yang berarti bahwa ia hanya bisa dipanggil melalui referensi objek. Ini juga berarti bahwa sebuah objek default dari kelas tersebut perlu diciptakan sehingga Factory dapat dipanggil.  Namun, ada cara yang lebih baik dalam mengimplementasikan hal ini. Caranya adalah dengan menjadikan Factory sebagai static, sehingga dapat dipanggil tanpa perlu menciptakan objek. Berikut adalah program untuk merealisasikan gagasan ini:

// Menggunakan static Factory.
using System;

class KelasKu {
    int a, b;
   
    // Menciptakan sebuah kelas Factory untuk KelasKu.
    static public KelasKu Factory(int i, int j)
    {
        KelasKu t = new KelasKu();
        t.a = i;
        t.b = j;
        return t; // menghasilkan sebuah objek

    }
   
public void Tampil()
    {
        Console.WriteLine("a dan b: " + a + " " + b);
    }
}

class MembuatObjek {
    static void Main() {
        int i, j;

        // Menghasilkan objek menggunakan Factory.
        for (i = 0, j = 10; i < 10; i++, j--)
        {
            KelasKu ob = KelasKu.Factory(i, j); // mendapatkan sebuah object
            ob.Tampil();
        }

        Console.WriteLine();
    }
}

Berikut adalah keluaran program:

a dan b: 0 10
a dan b: 1 9
a dan b: 2 8
a dan b: 3 7
a dan b: 4 6
a dan b: 5 5
a dan b: 6 4
a dan b: 7 3
a dan b: 8 2
a dan b: 9 1

Pada versi ini, Factory() dipanggil melalui nama kelasnya pada baris kode ini:

KelasKu ob = KelasKu.Factory(i, j);

Tidak diperlukan untuk menciptakan sebuah objek KelasKu sebelum menggunakan Factory().


Konstruktor static
Sebuah konstruktor dapat dispesifikasi sebagai static. Konstruktor static secara umum dipakai untuk menginisialisasi fitur yang dapat diterapkan pada sebuah kelas, bukan pada instansnya. Jadi, konstruktor static dipakai untuk menginisialisasi aspek-aspek sebuah kelas sebelum sembarang objek dari kelas tersebut diciptakan. Berikut adalah contoh sederhananya:

// Menggunakan sebuah konstruktor static.
using System;

class Konst
{
    public static int alfa;
    public int beta;

    // Sebuah konstruktor static.
    static Konst()
    {
        alfa = 99;
        Console.WriteLine("Di dalam konstruktor static.");
    }

    // Sebuah konstruktor instans.
    public Konst()
    {
        beta = 100;
        Console.WriteLine("Di dalam konstruktor instans.");
    }
}

class DemoKonst
{
    static void Main()
    {
        Konst ob = new Konst();

        Console.WriteLine("Konst.alfa: " + Konst.alfa);
        Console.WriteLine("ob.beta: " + ob.beta);
    }
}

Keluaran program adalah sebagai berikut:

Di dalam konstruktor static.
Di dalam konstruktor instans.
Konst.alfa: 99
ob.beta: 100

Perhatikan bahwa konstruktor static dipanggil secara otomatis sebelum konstruktor instans. Hal ini dapat digeneralisir. Pada semua kasus, konstruktor static akan dieksekusi sebelum sembarang konstruktor instans. Di samping itu, konstruktor static tidak memiliki pemodifikasi akses (jadi, menggunakan pemodifikasi akses default) dan tidak dapat dipanggil oleh program Anda.


Kelas static
Sebuah kelas dapat dideklarasikan static. Ada fitur katakunci dari kelas static. Pertama, tidak ada objek kelas static yang dapat diciptakan. Kedua, kelas static haru hanya memuat anggota-anggota static. Kelas static diciptakan dengan memodifikasi deklarasi kelas dengan katakunci static, ditunjukkan di sini:

static class nama-kelas { // ...

Di dalam kelas, semua anggota harus dispesifikasi secara eksplisit sebagai static. Membuat kelas menjadi static tidak secara otomatis membuat anggota-anggotanya menjadi static pula.

Kelas static mempunyai dua kegunaan utama. Pertama, kelas static diperlukan ketika menggunakan metode ekstensi. Metode ekstensi berelasi dengan LINQ. Kedua, kelas static digunakan untuk memuat koleksi yang memuat metode-metode static yang berelasi. Kegunaan kedua ini akan didemonstrasikan di sini.

Contoh berikut menggunakan kelas static, yang bernama NumerikFn untuk memuat beberapa metode static yang beroperasi pada sebuah nilai numerik. Karena semua anggota dari kelas NumerikFn dideklarasikan static, kelas juga harus dideklarasikan static, untuk menghindarinya dari instansiasi. Jadi, NumerikFn hanya berperan organisasional, yang menyediakan cara untuk mengelompokkan beberapa metode yang berelasi.
// Demonstrasi sebuah kelas static.
using System;

static class NumerikFn {
  // Menghasilkan kebalikan dari sebuah nilai.
  static public double Kebalikan(double angka) {
      return 1 / angka;
  }

  // Menghasilkan bagian fraksional dari sebuah angka.
  static public double BagianFrak(double angka) {
    return angka - (int)angka;
  }

  // Menghasilkan true jika angka genap.
  static public bool ApaGenap(double angka) {
      return (angka % 2) == 0 ? true : false;
  }

  // Menghasilkan true jika angka ganjil.
  static public bool ApaGanjil(double angka) {
    return !ApaGenap(angka);
  }
}

class DemoKelasStatic {
  static void Main() {
    Console.WriteLine("Kebalikan dari 5 adalah " +
                      NumerikFn.Kebalikan(5.0));

    Console.WriteLine("Bagian fraksional dari 4.234 adalah " +
                      NumerikFn.BagianFrak(4.234));

    if (NumerikFn.ApaGenap(10))
      Console.WriteLine("10 adalah genap.");
   
    if (NumerikFn.ApaGanjil(5))
      Console.WriteLine("5 adalah ganjil.");

    // Pencobaan berikut adalah untuk menciptakan sebuah instans dari
    // NumerikFn yang akan menyebabkan error.
    // NumerikFn ob = new NumerikFn(); // Salah!
  }
}

Keluaran program ditampilkan di sini:

Kebalikan dari 5 adalah 0.2
Bagian fraksional dari 4.234 adalah 0.234
10 adalah genap.
5 adalah ganjil.

Satu hal penting terakhir: Meskipun kelas static tidak bisa memiliki konstruktor instans, ia bisa mempunyai konstruktor static.




No comments:

Post a Comment