Saturday, December 24, 2016

Bab 13. Pemrograman C# Belajar Dari Contoh



13. Delegate, Event, dan Lambda








Bab ini akan mendiskusikan tiga fitur inovatif C#: delegate, event, dan lambda. Delegate memberikan cara untuk mengenkapsulasi sebuah metode. Event merupakan sebuah pemberitahuan bahwa suatu aksi telah terjadi. Delegate dan event berelasi karena sebuah event dibangun berdasarkan delegate. Ekspresi lambda merupakan fitur yang relatif baru di dalam C# yang menawarkan cara dalam mendefinisikan unit kode yang dapat dieksekusi. Ekspresi lambda sering dipakai ketika bekerja dengan delegate dan event.


Delegate
Dalam bahasa sederhana, delegate merupakan sebuah objek yang dapat menunjuk ke suatu metode. Oleh karena itu, ketika Anda menciptakan delegate, Anda sedang menciptakan sebuah objek yang menampung referensi yang menunjuk ke suatu metode. Selain itu, metode dapat dipanggil melalui referensi tersebut. Dengan kata lain, delagate dapat memanggil metode yang ia tunjuk. Ini merupakan konsep yang sangat berguna.

Adalah penting untuk memahami bahwa delegate yang sama dapat dipakai untuk memanggil berbagai metode selama program berjalan, hanya dengan mengubah metode yang ditunjuk. Jadi, metode yang akan dipanggil oleh delegate tidak ditentukan pada saat kompilasi, tetapi pada saat program sedang berjalan. Ini merupakan keuntungan utama dari sebuah delegate.

Tipe delegate dideklarasikan menggunakan katakunci delegate. Bentuk umum sebuah deklarasi delegate ditunjukkan di sini:

delegate tipe-nilaibalik nama(daftar-parameter);

Di sini, tipe-nilaibalik adalah tipe dari nilai yang dijadikan nilai balik oleh metode yang dipanggil oleh delegate. Nama delegate dispesifikasi oleh nama. Parameter yang dibutuhkan oleh metode yang dipanggil melalui delegate dispesifikasi di dalam daftar-parameter. Begitu diciptakan, sebuah instans delegate dapat menunjuk ke dan memanggil metode yang mempunyai nilai balik dan daftar parameter sesuai dengan yang dispesifikasi oleh deklarasi delegate.

Poin kunci untuk dipahami adalah bahwa delegate dapat dipakai untuk memanggil sembarang metode yang cocok dengan sidik dan tipe nilai baliknya. Selain itu, metode bisa berupa metode instans yang diasosiasikan dengan sebuah objek atau metode static yang diasosiasikan dengan sebuah kelas. Yang penting adalah bahwa tipe nilai balik dan sidik metode harus sesuai dengan delegate. Untuk melihat bagaimana delegate bekerja, akan disajikan sebuah contoh sederhana berikut:

// Sebuah contoh delegate sederhana.
using System;

// Mendeklarasikan sebuah tipe delegate.
delegate string StrMod(string str);

class UjiDelegate {
  // Mengganti spasi dengan tanda-hubung.
  static string GantiSpasi(string s) {
    Console.WriteLine("Mengganti spasi dengan tanda-hubung.");
    return s.Replace(' ', '-');
  }

  // Menghapus spasi.
  static string HapusSpasi(string s) {
    string temp = "";
    int i;

    Console.WriteLine("Menghaspus spasi.");
    for(i=0; i < s.Length; i++)
      if(s[i] != ' ') temp += s[i];
     
    return temp;
  }

  // Membalikkan sebuah string.
  static string Balik(string s) {
    string temp = "";
    int i, j;

    Console.WriteLine("Membalikkan string.");
    for(j=0, i=s.Length-1; i >= 0; i--, j++)
      temp += s[i];

    return temp;
  }

  static void Main() {
    // Menciptakan sebuah delegate.
    StrMod strOp = new StrMod(GantiSpasi);
    string str;

    // Memanggil beberapa metode melalui delegate.
    str = strOp("Ini adalah sebuah test.");
    Console.WriteLine("String yang dihasilkan: " + str);

    Console.WriteLine();

    strOp = new StrMod(HapusSpasi);
    str = strOp("Ini adalah sebuah test.");
    Console.WriteLine("String yang dihasilkan: " + str);

    Console.WriteLine();

    strOp = new StrMod(Balik);
    str = strOp("Ini adalah sebuah test.");
    Console.WriteLine("String yang dihasilkan: " + str);
  }
}

Keluaran program yang dihasilkan adalah:

Mengganti spasi dengan tanda-hubung.
String yang dihasilkan: Ini-adalah-sebuah-test.

Menghaspus spasi.
String yang dihasilkan: Iniadalahsebuahtest.

Membalikkan string.
String yang dihasilkan: .tset haubes halada inI

Akan didiskusikan program ini lebih detil. Program mendeklarasikan sebuah tipe delegate yang dinamakan StrMod, ditunjukkan di sini:

delegate string StrMod(string str);

Perhatikan bahwa StrMod mengambil satu parameter string dan menghasilkan nilai balik berupa sebuah string.

Selanjutnya, di dalam UjiDelegate, tiga metode static dideklarasikan, masing-masing dengan satu parameter bertipe string dan menghasilkan nilai balik bertipe string. Jadi, ketiganya cocok dengan delegate StrMod. Ketiga metode tersebut melakukan tugas pemodifikasian string. Perhatikan bahwa GantiSpasi() menggunakan salah satu dari metode string, Replace(), untuk mengganti spasi dengan tanda-hubung.

Di dalam Main(), sebuah referensi StrMod(), strOp, diciptakan dan ditugasi sebuah referensi yang menunjuk ke GantiSpasi(). Perhatikan baris ini:

StrMod strOp = new StrMod(GantiSpasi);

Perhatikan bagaiman metode GantiSpasi() dilewatkan sebagai sebuah parameter. Hanya namanya yang dipakai; tidak ada parameter yang dispesifikasi. Hal ini dapat digeneralisir. Ketika menginstansiasi sebuah delegate, Anda hanya perlu menspesifikasi nama metode yang akan ditunjuk oleh delegate tersebut. Tentu saja, sidik metode harus cocok dengan yang ada pada deklarasi delegate. Jika tidak, error kompilasi akan dibangkitkan sistem.

Berikutnya, GantiSpasi() dipanggil melalui instans delegate, strOp, seperti ditunjukkan di sini:

str = strOp("Ini adalah sebuah test.");

Karena strOp menujuk ke GantiSpasi(), maka GantiSpasi() yang dipanggil.

Selanjutnya, strOp ditugasi sebuah referensi yang menunjuk ke HapusSpasi(), dan kemudian strOp dipanggil kembali. Kali ini, HapusSpasi() yang dipanggil.

Terakhir, strOp ditugasi sebuah referensi ke Balik() dan strOp dipanggil. Ini menyebabkan pemanggilan Balik().


Konversi Grup Metode
Sejak versi 2.0, C# mencantumkan sebuah opsi yang secara signifikan menyederhanakan sintaks dalam menugaskan sebuah metode kepada delegate. Fitur ini dikenal dengan konversi grup metode, yang menugaskan nama sebuah metode kepada delegate, tanpa menggunakan new atau tanpa secara eksplisit memanggil konstruktor delegate.

Sebagai contoh, berikut metode Main() pada program sebelumnya ditulis-ulang agar menggunakan konversi grup metode:

// Sebuah contoh delegate sederhana.
using System;

// Mendeklarasikan sebuah tipe delegate.
delegate string StrMod(string str);

class UjiDelegate {
  // Mengganti spasi dengan tanda-hubung.
  static string GantiSpasi(string s) {
    Console.WriteLine("Mengganti spasi dengan tanda-hubung.");
    return s.Replace(' ', '-');
  }

  // Menghapus spasi.
  static string HapusSpasi(string s) {
    string temp = "";
    int i;

    Console.WriteLine("Menghaspus spasi.");
    for(i=0; i < s.Length; i++)
      if(s[i] != ' ') temp += s[i];
     
    return temp;
  }

  // Membalikkan sebuah string.
  static string Balik(string s) {
    string temp = "";
    int i, j;

    Console.WriteLine("Membalikkan string.");
    for(j=0, i=s.Length-1; i >= 0; i--, j++)
      temp += s[i];

    return temp;
  }

  static void Main() {
      // Mengkonstruksi sebuah delegate menggunakan konversi grup metode.
      StrMod strOp = GantiSpasi; // menggunakan konversi grup metode
      string str;

      // Memanggil metode melalui delegate.
      str = strOp("Ini adalah sebuah test.");
      Console.WriteLine("String yang dihasilkan: " + str);
     
      Console.WriteLine();

      strOp = GantiSpasi; // menggunakan konversi grup metode
      str = strOp("Ini adalah sebuah test.");
      Console.WriteLine("String yang dihasilkan: " + str);
     
      Console.WriteLine();

      strOp = Balik; // menggunakan konversi grup metode
      str = strOp("Ini adalah sebuah test.");
      Console.WriteLine("String yang dihasilkan: " + str);
   
  }
}

Perhatikan khusus pada cara strOp diciptakan dan ditugasi metode GantiSpasi() pada baris ini:

StrMod strOp = GantiSpasi; // menggunakan konversi grup metode

Nama metode ditugaskan secara langsung kepada strOp. C# secara otomatis menyediakan sebuah konversi dari metode menjadi tipe delegate. Sintaks ini dapat digeneralisir untuk sembarang situasi dimana di dalamnya sebuah metode ditugaskan kepada (dikonversi menjadi) suatu tipe delegate.

Karena sintaks konversi grup metode lebih sederhana dari pendekatan semula, sintaks tersebut akan digunakan pada buku ini.


Menggunakan Metode Instans Sebagai Delegate
Meskipun pada contoh terdahulu digunakan metode static, delegate dapat juga menunjuk ke metode instans. Hal ini harus dilakukan melalui sebuah referensi objek. Sebagai contoh, berikut adalah versi yang ditulis-ulang dari contoh sebelumnya, yang mengenkapsulasi beberapa operasi string di dalam kelas StringOps. Perhatikan bahwa sintaks konversi grup metode dapat diterapkan pada situasi ini.

// Delegate dapat menunjuk ke metode instans juga.
using System;

// Mendeklarasikan sebuah tipe delegate.
delegate string StrMod(string str);

class StringOps {
  // Mengganti spasi dengan tanda-hubung.
  public string GantiSpasi(string s) {
    Console.WriteLine("Mengganti spasi dengan tanda-hubung.");
    return s.Replace(' ', '-');
  }

  // Menghapus spasi.
  public string HapusSpasi(string s) {
    string temp = "";
    int i;

    Console.WriteLine("Menghaspus spasi.");
    for(i=0; i < s.Length; i++)
      if(s[i] != ' ') temp += s[i];
     
    return temp;
  }

  // Membalikkan sebuah string.
  public string Balik(string s) {
    string temp = "";
    int i, j;

    Console.WriteLine("Membalikkan string.");
    for(j=0, i=s.Length-1; i >= 0; i--, j++)
      temp += s[i];

    return temp;
  }
}

class UjiDelegate {
  static void Main() {
      StringOps so = new StringOps(); // menciptakan instans dari StringOps

      // Menginisialisasi delegate.
      StrMod strOp = so.GantiSpasi;
      string str;
     
      // Memanggil metode melalui delegate.
      str = strOp("Ini adalah sebuah test.");
      Console.WriteLine("String yang dihasilkan: " + str);
     
      Console.WriteLine();

      strOp = so.GantiSpasi; // menggunakan konversi grup metode
      str = strOp("Ini adalah sebuah test.");
      Console.WriteLine("String yang dihasilkan: " + str);
     
      Console.WriteLine();

      strOp = so.Balik; // menggunakan konversi grup metode
      str = strOp("Ini adalah sebuah test.");
      Console.WriteLine("String yang dihasilkan: " + str);
   
  }
}


Multicasting
Salah satu fitur paling menarik dari sebuah delegate adalah multicasting. Dalam bahasa sederhana, multicasting merupakan kemampuan untuk menciptakan daftar atau rantai pemanggilan metode yang akan secara otomatis dipanggil ketika sebuah delegate dipanggil. Rantai semacam itu sangat mudah diciptakan. Anda hanya perlu menginstansiasi sebuah delegate dan kemudian menggunakan operator + atau += untuk menambahkan metode ke dalam rantai. Untuk menghapus sebuah metode dari rantai, Anda bisa menggunakan operator atau -=. Jika delegate menghasilkan nilai balik, maka nilai balik yang dikembalikan oleh metode terakhir di dalam rantai menjadi nilai balik dari keseluruhan pemanggilan delegate. Jadi, delegate yang memanfaatkan multicasting biasanya mempunyai nilai balik bertipe void.

Berikut adalah sebuah contoh multicasting. Perhatikan bahwa program sebelumnya dikerjakan-ulang dengan mengubah tipe nilai balik pada metode manipulasi string menjadi void dan menggunakan sebuah parameter ref untuk menghasilkan nilai balik berupa string yang telah diubah.

// Demonstrasi multicasting.
using System;

// Mendeklarasikan sebuah tipe delegate.
delegate void StrMod(ref string str);

class DemoMulticast {
  // Mengganti spasi dengan tanda-hubung.
  static void GantiSpasi(ref string s) {
    Console.WriteLine("Mengganti spasi dengan tanda-hubung.");
    s = s.Replace(' ', '-');
  }

  // Menghapus spasi.
  static void HapusSpasi(ref string s) {
    string temp = "";
    int i;

    Console.WriteLine("Menghaspus spasi.");
    for(i=0; i < s.Length; i++)
      if(s[i] != ' ') temp += s[i];
     
    s = temp;
  }

  // Membalikkan sebuah string.
  static void Balik(ref string s) {
    string temp = "";
    int i, j;

    Console.WriteLine("Membalikkan string.");
    for(j=0, i=s.Length-1; i >= 0; i--, j++)
      temp += s[i];

    s = temp;
  }

  static void Main() {
      // mengkonstruksi beberapa delegate
      StrMod strOp;
      StrMod gantiSp = GantiSpasi;
      StrMod hapusSp = HapusSpasi;
      StrMod balikStr = Balik;
      string str = "Ini adalah sebuah test";

      // Membuat multicast.
      strOp = gantiSp;
      strOp += balikStr;

      // Memanggil multicast.
      strOp(ref str);
      Console.WriteLine("String yang dihasilkan: " + str);
      Console.WriteLine();

      // Menghapus ganti dan menambahkan hapus.
      strOp -= GantiSpasi;
      strOp += HapusSpasi;
      str = "Ini adalah sebuah test.";

      // Memanggil multicast.
      strOp(ref str);
      Console.WriteLine("String yang dihasilkan: " + str);
      Console.WriteLine();
  }
}

Program menghasilkan keluaran berikut:

Mengganti spasi dengan tanda-hubung.
Membalikkan string.
String yang dihasilkan: tset-haubes-halada-inI

Membalikkan string.
Menghaspus spasi.
String yang dihasilkan: .tsethaubeshaladainI

Di dalam Main(), empat instans delegate diciptakan. Satu, strOp, bernilai null. Ketiga lain menunjuk ke metode modifikasi string tertentu. Selanjutnya, multicast diciptakan untuk memanggil GantiSpasi() dan Balik(). Ini dilakukan melalui dua baris kode:

strOp = gantiSp;
strOp += balikStr;

Pertama, strOp ditugasi gantiSp. Selanjutnya, menggunakan operator +=, balikStr ditambahkan. Ketika strOp dipanggil, kedua metode dipanggil, menggantikan spasi dengan tanda-hubung dan membalikkan string, seperti yang ditunjukkan pada keluaran program.

Selanjutnya, gantiSp dihapus dari rantai, menggunakan baris ini:

strOp -= gantiSp;

dan hapusSp ditambahkan menggunakan baris ini:

strOp += hapusSp;

Kemudian, strOp kembali dipanggil. Kali ini, spasi dihapus dan string dibalikkan. Rantai delegate merupakan mekanisme yang tangguh karena membolehkan Anda untuk mendefinisikan sehimpunan metode yang dieksekusi dalam satu unit.


Kovariansi Dan Kontravariansi
Ada dua fitur tambahan bagi delegate: kovariansi dan kontravariansi. Biasanya, metode yang Anda lewatkan kepada delegate harus mempunyai tipe nilai balik dan sidik yang sama dengan delegate. Namun, kovariansi dan kontravariansi mengendorkan sedikit aturan ini. Kovariansi memampukan sebuah metode untuk ditugaskan kepada sebuah delegate manakala tipe nilai balik metode adalah sebuah kelas yang diderivasi dari kelas yang dispesifikasi oleh tipe nilai balik delegate. Kontravariansi memampukan sebuah metode untuk ditugaskan kepada delegate ketika tipe parameter metode merupakan kelas basis bagi kelas yang dispesifikasi oleh deklarasi delegate.

Berikut disajikan sebuah contoh yang mengilustrasikan kovariansi dan kontravariansi.

// Demonstrasi kovariansi dan kontravariansi.
using System;

class X {
  public int Nil;
}

//Y diwarisi dari X.
class Y : X { }

// Delegate ini menghasilkan tipe X dan mengambil argumen tipe Y.
delegate X UbahSaja(Y obj);

class KoDanKontraVariansi {
    // Metode ini menghasilkan tipe X dan mempunyai tipe parameter X.
    static X InkrA(X obj)
    {
        X temp = new X();
        temp.Nil = obj.Nil + 1;
        return temp;
    }

    // Metode ini menghasilkan tipe Y dan mempunyai tipe parameter Y.
    static Y InkrB(Y obj)
    {
        Y temp = new Y();
        temp.Nil = obj.Nil + 1;
        return temp;
    }
   
    static void Main()
    {
        Y Yob = new Y();
       
        // Pada kasus ini, parameter untuk InkrA
        // adalah tipe X dan parameter untuk UbahSaja adalah tipe Y.
        // Karena kontravariansi, baris berikut OK
        // untuk dilakukan.
        UbahSaja ubah = InkrA;

        X Xob = ubah(Yob);

        Console.WriteLine("Xob: " + Xob.Nil);
       
        // Pada kasus berikutnya, tipe nilai balik dari
        // InkrB adalah Y dan tipe nilai balik dari
        // UbahSaja adalah X. Karena kovariansi,
        // baris berikut OK dilakukan.
        ubah = InkrB;

        Yob = (Y)ubah(Yob);

        Console.WriteLine("Yob: " + Yob.Nil);
    }
}

Keluaran program yang dihasilkan adalah:

Xob: 1
Yob: 1

Di dalam program, perhatikan bahwa kelas Y diderivasi dari kelas X. Selanjutnya, perhatikan bahwa delegate UbahSaja dideklarasikan seperti ini:

delegate X UbahSaja(Y obj);

UbahSaja menghasilkan nilai balik tipe X dan mempunyai parameter bertipe Y. Berikutnya, perhatikan bahwa metode InkrA() dan InkrB() dideklarasikan seperti ini:

static X InkrA(X obj)
static Y InkrB(Y obj)

Metode InkrA() mempunyai parameter bertipe X dan menghasilkan nilai balik bertipe X. Metode InkrB() mempunyai parameter bertipe Y dan menghasilkan nilai balik bertipe Y. Karena kovariansi dan kontravariansi, masing-masing metode tersebut dapat dilewatkan kepada UbahSaja, seperti diilustrasikan pada program. Oleh karena itu, baris ini

UbahSaja ubah = InkrA;

menggunakan kontravariansi untuk memampukan InkrA() untuk dilewatkan kepada delegate karena InkrA() mempunyai parameter bertipe X, padahal delegate mempunyai parameter bertipe Y. Hal ini diijinkan karena, dengan kontravariansi, jika tipe parameter dari metode yang dilewatkan kepada delegate adalah kelas basis bagi tipe parameter yang digunakan oleh delegate, maka metode dan delegate menjadi kompatibel.

Baris berikutnya juga legal, tetapi kali ini karena kovariansi:

ubah = InkrB;

Pada kasus ini, tipe nilai balik dari InkrB() adalah Y, padahal tipe nilai balik dari UbahSaja adalah X. Namun, karena tipe nilai balik dari metode adalah kelas yang diderivasi dari tipe nilai balik delegate, maka keduanya menjadi kompatibel.


Metode Anonim
Sebuah metode anonim adalah suatu cara untuk menciptakan blok kode tak-bernama yang diasosiasikan dengan instans delegate tertentu. Metode anonim diciptakan menggunakan katakunci delegate dengan blok kode. Untuk melihat bagaimana ini dilakukan, akan disajikan sebuah contoh sederhana. Program berikut menggunakan sebuah metode anonim yang menampilkan angka 0 sampai 5.

// Demonstrasi sebuah metode anonim.
using System;

// Deklarasi sebuah tipe delegate.
delegate void Hitung();

class DemoMetAnonim
{
    static void Main()
    {
        // Di sini, kode untuk penghitungan dilewatkan
        // sebagai sebuah metode anonim.
        Hitung hitung = delegate
        {
            // Ini adalah blok kode yang dilewatkan kepada delegate.
            for (int i = 0; i <= 5; i++)
                Console.WriteLine(i);
        }; // perhatikan titik-koma
       
        hitung();
    }
}

Program ini pertama-tama mendeklarasikan sebuah tipe delegate, Hitung, yang tidak memiliki parameter dan menghasilkan void. Di dalam Main(), sebuah instans Hitung, dinamakan hitung, diciptakan. Kepada hitung dilewakan blok kode yang ditempatkan setelah katakunci delegate. Blok kode ini merupakan metode anonim yang akan dieksekusi ketika hitung dipanggil.  Perhatikan bahwa blok kode tersebut diakhiri dengan sebuah titik-koma, yang menghentikan deklarasi statemen. Keluaran program ditampilkan di sini:

0
1
2
3
4
5


Melewatkan Argumen Kepada Metode Anonim
Anda dimungkinkan untuk melewatkan satu atau lebih argumen kepada metode anonim. Untuk melakukannya, tempatkan daftar parameter (yang diapit sepasang kurung) setelah katakunci delegate. Kemudian, lewatkan argumen tersebut kepada instans delegate ketika ia dipanggil. Sebagai contoh, berikut merupakan program sebelumnya yang ditulis-ulang sehingga nilai akhir nilai untuk hitung dilewatkan:

// Demonstrasi sebuah metode anonim yang mengambil sebuah argumen.
using System;

// Perhatikan sekarang delegate mempunyai parameter.
delegate void Hitung(int akhir);

class DemoMetAnonim2
{
    static void Main()
    {
        // Di sini, kode untuk penghitungan dilewatkan
        // sebagai sebuah metode anonim.
        Hitung hitung = delegate (int akhir)
        {
            // Ini adalah blok kode yang dilewatkan kepada delegate.
            for (int i = 0; i <= akhir; i++)
                Console.WriteLine(i);
        }; // perhatikan titik-koma
       
        hitung(3);
        Console.WriteLine();
        hitung(5);
    }
}

Pada versi ini, Hitung sekarang mengambil sebuah argumen integer. Perhatikan bahwa daftar parameter dispesifikasi setelah katakunci delegate ketika metode anonim diciptakan. Kode di dalam metode anonim memiliki akses terhadap parameter akhir, sama seperti bila digunakan metode yang memiliki nama. Keluaran dari program ini ditampilkan berikut:

0
1
2
3

0
1
2
3
4
5


Nilai Balik Dari Metode Anonim
Sebuah metode anonim dapat mempunyai nilai balik. Nilai tersebut dihasilkan menggunakan statemen return, yang bekerja seperti pada metode bernama. Seperti yang Anda duga, tipe nilai balik harus kompatibel dengan tipe nilai balik yang dispesifikasi oleh delegate. Sebagai contoh, berikut adalah kode yang menghitung penjumlahan atas hitung dan memberikan nilai balik:

// Demonstrasi sebuah metode anonim yang menghasilkan nilai balik.
using System;

// Delegate ini menghasilkan nilai balik.
delegate int Hitung(int akhir);

class DemoMetAnonim3
{
    static void Main()
    {
        int hasil;

        // Di sini, nilai akhir untuk hitung
        // dilewatkan kepada metode anonim.
        // Penjumlahan atas hitung dijadikan nilai balik.
        Hitung hitung = delegate (int akhir)
        {
            int jum = 0;

            for (int i = 0; i <= akhir; i++)
            {
                Console.WriteLine(i);
                jum += i;
            }

            return jum; // nilai balik dari metode anonim
        };

        hasil = hitung(3);
        Console.WriteLine("Penjumlahan atas 3 adalah " + hasil);
        Console.WriteLine();

        hasil = hitung(5);
        Console.WriteLine("Penjumlahan atas 5 adalah " + hasil);
    }
}

Pada versi ini, nilai dari jum dijadikan nilai balik oleh blok kode yang diasosiasikan dengan instans delegate hitung. Perhatikan bahwa statemen return dipakai di dalam metode anonim, sama seperti ketika digunakan di dalam metode bernama. Keluaran program ditampilkan di sini:

0
1
2
3
Penjumlahan atas 3 adalah 6

0
1
2
3
4
5
Penjumlahan atas 5 adalah 15


Menggunakan Variabel Outer Pada Metode Anonim
Variabel lokal atau parameter yang mempunyai skop mencakup sebuah metode anonim dikenal dengan variabel outer. Metode anonim mempunyai akses terhadap dan dapat menggunakan variabel outer. Ketika variabel outer digunakan oleh metode anonim, variabel tersebut dikatakan sebagai ditangkap. Variabel yang tertangkap tetap eksis selama delegate yang menangkapnya belum dihancurkan. Jadi, meskipun variabel lokal biasanya dihancurkan ketika kendali program keluar dari blok variabel tersebut, jika variabel lokal tersebut sedang digunakan oleh sebuah metode anonim, maka variabel itu akan tetap eksis selama delegate yang menunjuk metode tersebut belum dihancurkan.

Penangkapan variabel lokal dapat menyebabkan hasil yang tidak diinginkan. Sebagai contoh, perhatkan versi program ini. Pada versi sebelumnya, penjumlahan atas hitung dilakukan. Namun, pada versi ini, objek Hitung diciptakan dan dijadikan nilai balik oleh sebuah metode static, Penghitung(). Objek ini menggunakan variabel jum, yang dideklarasikan di dalam skop tertutup yang disediakan oleh Penghitung(). Oleh karena itu, jum ditangkap oleh metode anonim. Di dalam Main(), Penghitung() dipanggil untuk mendapatkan sebuah objek Hitung. jum akan dihancurkan ketika program berhenti.

// Demonstrasi variabel yang ditangkap.
using System;

// Delegate ini menghasilkan nilai balik dan menerima argumen int.
delegate int Hitung(int akhir);

class VarTangkap
{
    static Hitung Penghitung()
    {
        int jum = 0;

        // Di sini, penjumlahan atas hitung disimpan
        // di dalam variabel jum yang ditangkap.
        Hitung hitungObj = delegate (int akhir) {
            for(int i=0; i <= akhir; i++) {
                Console.WriteLine(i);
                jum += i;
            }

            return jum;
        };

        return hitungObj;
    }

    static void Main() {
        // Mendapatkan penghitung.
        Hitung hitung = Penghitung();

        int hasil;

        hasil = hitung(3);
        Console.WriteLine("Penjumlahan atas 3 adalah " + hasil);
        Console.WriteLine();

        hasil = hitung(5);
        Console.WriteLine("Penjumlahan atas 5 adalah " + hasil);
    }
}

Keluaran program ditampilkan sebagai berikut:

0
1
2
3
Penjumlahan atas 3 adalah 6

0
1
2
3
4
5
Penjumlahan atas 5 adalah 21

Seperti yang dapat Anda lihat, penghitungan tetap berjalan normal. Namun, perhatikan nilai penjumlahan untuk 5, yang menghasilkan 21 bukan 15!. Alasannya adalah bahwa jum ditangkap oleh hitungObj ketika ia diciptakan oleh metode Penghitung(). Ini berarti variabel jum tetap eksis sampai hitung dihancurkan di akhir program. Jadi, nilainya tidak dihancurkan ketika Penghitung() selesai dieksekusi atau ketika setiap kali metode anonim dipanggil manakala hitung dipanggil di dalam Main().


Ekspresi Lambda
Di dalam sebuah ekspresi lambda, ekspresi di sisi kanan operator lambda => memakai parameter yang dispesifikasi di sisi kiri. Hasil ekspresi menjadi hasil dari operator lambda dan dijadikan nilai balik.

Berikut adalah bentuk umum dari ekspresi lambda yang hanya mengambil satu parameter:

param => ekspr

Ketika lebih dari satu parameter diperlukan, maka bentuk berikut digunakan:

(daftar-param) => ekspr

Oleh karena itu, ketika dua atau lebih parameter diperlukan, semuanya harus diapit oleh sepasang kurung. Jika tidak ada parameter yang dibutuhkan, maka kurung kosong tetap diperlukan.

Berikut adalah ekspresi lambda sederhana:

hitung => hitung + 2

Di sini, hitung adalah parameter yang dipakai pada ekspresi hitung + 2. Jadi, hasil adalah nilai dari hitung ditambah dengan dua. Ini adalah contoh lain:

n => n % 2 == 0

Pada kasus ini, ekspresi ini akan menghasilkan true jika n genap dan false jika n ganjil.

Penggunaan ekspresi lambda melibatkan dua tahap. Pertama, mendeklarasikan sebuah tipe delegate yang kompatibel dengan ekspresi lambda. Kedua, mendeklarasikan sebuah instans delegate dan menugaskan ekspresi lambda kepadanya. Ketika hal ini telah dilakukan, ekspresi lambda dapat dieksekusi dengan memanggil instans delegate. Hasil dari ekspresi lambda menjadi nilai balik.

Program berikut menunjukkan bagaimana melihat dua ekspresi lambda digunakan. Program mendeklarasikan dua tipe delegate. Pertama, dinamakan Inkr, mengambil sebuah argumen int dan menghasilkan nilai balik bertipe int. Kedua, dinamakan ApaGenap, mengambil sebuah argumen int dan menghasilkan nilai balik bertipe bool. Program kemudian menugaskan ekspresi lambda kepada instans dari kedua delegate. Terakhir, program mengeksekusi ekspresi lambda melalui instans delegate.

// Menggunakan dua ekspresi lambda.
using System;

// Mendeklarasikan sebuah delegate yang mengambil argumen int
// dan nilai balik bertipe int.
delegate int Inkr(int v);

// Mendeklarasikan sebuah delegate yang mengambil argumen int
// dan nilai balik bertipe bool.
delegate bool ApaGenap(int v);

class DemoLambdaSederhana {
   
    static void Main() {

        // Menciptakan instans delegate Inkr yang menunjuk ke
        // sebuah ekspresi lambda yang menambah parameternya sebesar 2.
        Inkr inkr = hitung => hitung + 2;

        // Sekarang, menggunakan ekspresi lambda inkr.
        Console.WriteLine("Menggunakan ekspresi lambda inkr: ");
        int x = -10;

        while(x <= 0) {
            Console.Write(x + " ");
            x = inkr(x); // menambah x dengan 2
        }

        Console.WriteLine("\n");

        // Menciptakan instans delegate ApaGenap yang menunjuk ke
        // sebuah ekspresi lambda yang menghasilkan true jika parameternya
        // genap dan false jika ganjil.
        ApaGenap apaGenap = n => n % 2 == 0;

        // Sekarang, menggunakan ekspresi lambda apaGenap.
        Console.WriteLine("Menggunakan ekspresi lambda apaGenap: ");
        for(int i=1; i <= 10; i++)
            if(apaGenap(i)) Console.WriteLine(i + " adalah genap.");
    }
}

Keluaran program ditampilkan di sini:

Menggunakan ekspresi lambda inkr:
-10 -8 -6 -4 -2 0

Menggunakan ekspresi lambda apaGenap:
2 adalah genap.
4 adalah genap.
6 adalah genap.
8 adalah genap.
10 adalah genap.

Pada program, perhatikan khusus pada kedua deklarasi ini:

Inkr inkr = hitung => hitung + 2;
ApaGenap apaGenap = n => n % 2 == 0;

Event
Fitur C# yang penting lainnya adalah event. Event merupakan pemberitahuan otomatis bahwa suatu aksi telah terjadi. Event bekerja seperti ini: Ketika event terjadi, semua handler yang teregister dipanggil. Handler event direperesentasikan oleh delegate.

Event adalah anggota sebuah kelas dan dideklarasikan menggunakan katakunci event. Bentuk umumnya adalah

event delegate-event nama-event;

Di sini, delegate-event adalah nama delegate yang digunakan untuk mendukung event, dan nama-event adalah nama objek event tertentu yang sedang dideklarasikan. Berikut akan disajikan sebuah contoh event sederhana:

// Sebuah demonstrasi event sederhana.
using System;

// Deklarasi sebuah tipe delegate untuk event.
delegate void EventHandlerKu();

// Deklarasik kelas yang memuat event.
class EventKu
{
    public event EventHandlerKu SuatuEvent;
   
    // Ini dipanggil untuk menyebabkan event.
    public void PadaSuatuEvent()
    {
        if (SuatuEvent != null)
            SuatuEvent();
    }
}

class DemoEvent {
    // Sebuah handler event.
    static void Handler() {
        Console.WriteLine("Event terjadi");
    }

    static void Main()
    {
        EventKu evt = new EventKu();
   
        // Menambahkan Handler() ke daftar event.
        evt.SuatuEvent += Handler;
   
        // Membangkitkan event.
        evt.PadaSuatuEvent();
    }
}

Program menghasilkan keluaran:

Event terjadi

Meskipun sederhana, program ini memuat semua elemen esensial untuk penanganan event. Periksa hati-hati. Program dimulai dengan mendeklarasikan sebuah tipe delegate untuk handler event, seperti ini:

delegate void EventHandlerKu();

Semua event diaktivasi melalui sebuah delegate. Jadi, tipe delegate event mendefinisikan tipe nilai balik dan sidik untuk event. Pada kasus ini, tidak ada parameter yang diperlukan, meski parameter event diijinkan.

Berikutnya, kelas event, dinamakan EventKu, diciptakan. Di dalam kelas tersebut, sebuah event, SuatuEvent, dideklarasikan, menggunakan baris ini:

public event EventHandlerKu SuatuEvent;

Perhatikan sintaksnya. Katakunci event memberitahu kompiler bahwa sebuah event sedang dideklarasikan.

Yang juga dideklarasikan di dalam EventKu adalah metode PadaSuatuEvent(), yang merupakan metode yang dipanggil program untuk memicu atau menyebabkan sebuah event (yaitu, inilah metode yang dipanggil ketika event terjadi). Metode ini memanggil sebuah handler event melalui delegate SuatuEvent, seperti ini:

if(SuatuEvent != null)
    SuatuEvent();

Perhatikan bahwa sebuah handler dipanggil jika dan hanya jika SuatuEvent tidak null. Untuk mencegah pemanggilan referensi null, delegate event harus diuji untuk memastikan bahwa ia tidak null.

Di dalam DemoEvent, sebuah handler event, Handler(), diciptakan. Pada contoh sederhana ini, handler event hanya menampilkan sebuah pesan. Di dalam Main(), sebuah objek EventKu diciptakan, dan Handler() diregister sebagai sebuah handler untuk event ini, dengan mencantumkan:

EventKu evt = new EventKu();

// Menambahkan Handler() pada daftar event.
evt.SuatuEvent += Handler;

Perhatikan bahwa handler ditambahkan menggunakan operator +=. Event hanya mendukung operator += dan -=. Pada kasus ini, Handler() merupakan sebuah metode static, tetapi sebenarnya handler event bisa saja berupa metode instans.

Terakhir, event dipicu dengan:

// Memicu event.
evt.PadaSuatuEvent();

Pemanggilan PadaSuatuEvent() menyebabkan semua handler event yang teregistrasi dipanggil. Pada kasus ini, hanya satu handler yang teregistrasi.

Contoh Event Multicast
Seperti delegate, event juga bisa multicast. Ini memampukan banyak objek untuk merespon sebuah pemberitahuan event. Berikut adalah contoh event multicast:

// Demonstrasi multicast event.
using System;

// Deklarasi sebuah tipe delegate untuk event.
delegate void EventHandlerKu();

// Deklarasi sebuah kelas yang memuat event.
class EventKu
{
    public event EventHandlerKu SuatuEvent;

    // Ini dipanggil untuk memicu.
    public void PadaSuatuEvent()
    {
        if (SuatuEvent != null)
            SuatuEvent();
    }
}

class X
{
    public void Xhandler()
    {
        Console.WriteLine("Event diterima oleh objek X");
    }
}

class Y
{
    public void Yhandler()
    {
        Console.WriteLine("Event diterima oleh objek Y");
    }
}

class DemoEvent2 {
    static void Handler() {
        Console.WriteLine("Event diterima oleh DemoEvent");
    }

    static void Main() {
        EventKu evt = new EventKu();
        X xOb = new X();
        Y yOb = new Y();

        // Menambahkan handler untuk daftar event.
        evt.SuatuEvent += Handler;
        evt.SuatuEvent += xOb.Xhandler;
        evt.SuatuEvent += yOb.Yhandler;

        // Memicu event.
        evt.PadaSuatuEvent();
        Console.WriteLine();

        // Menghapus sebuah handler.
        evt.SuatuEvent -= xOb.Xhandler;
        evt.PadaSuatuEvent();
    }
}

Keluaran program ditampilkan di sini:

Event diterima oleh DemoEvent
Event diterima oleh objek X
Event diterima oleh objek Y

Event diterima oleh DemoEvent
Event diterima oleh objek Y


Metode Instans Sebagai Handler Event
Meskipun metode instans dan metode static dapat dipakai sebagai handler event, tetapi keduanya berbeda dalam cara. Ketika metode static dipakai sebagai handler, pemberitahuan event berlaku pada kelas. Ketika metode instans digunakan sebagai handler, event dikirim ke instans objek tertentu. Jadi, setiap objek suatu kelas yang ingin menerima notifikasi event harus diregister secara individual. Pada kenyataannya, kebanyakan handler event adalah metode instans.

Program berikut menciptakan sebuah kelas X yang mendefinisikan suatu metode instans sebagai handler event. Ini berarti bahwa setiap objek X harus mendaftarkan secara individual untuk menerima event. Untuk mendemonstasikannya, program melakukan multicast event pada tiga objek bertipe X.

// Metode instans sebagai handler event
using System;

// Mendeklarasikan tipe delegate untuk sebuah event.
delegate void EventHandlerKu();

// Mendeklarasikan kelas yang memuat sebuah event.
class EventKu {

    public event EventHandlerKu SuatuEvent;

    // Ini dipanggil untuk memicu event.
    public void PadaSuatuEvent()
    {
        if (SuatuEvent != null)
            SuatuEvent();
    }
}

class X
{
    int id;

    public X(int x) { id = x; }
   
    // Ini adalah metode instans yang akan dipakai sebagai handler event.
    public void Xhandler()
    {
        Console.WriteLine("Event diterima oleh objek " + id);
    }
}

class DemoEvent3
{
    static void Main()
    {
        EventKu evt = new EventKu();
        X o1 = new X(1);
        X o2 = new X(2);
        X o3 = new X(3);

        evt.SuatuEvent += o1.Xhandler;
        evt.SuatuEvent += o2.Xhandler;
        evt.SuatuEvent += o3.Xhandler;

        // Memicu event.
        evt.PadaSuatuEvent();
    }
}

Keluaran program ditampikan di sini:

Event diterima oleh objek 1
Event diterima oleh objek 2
Event diterima oleh objek 3










No comments:

Post a Comment