Bagikan melalui


21 Delegasi

21.1 Umum

Deklarasi delegasi mendefinisikan kelas yang berasal dari kelas System.Delegate. Instans delegasi merangkum daftar pemanggilan, yang merupakan daftar satu atau beberapa metode, yang masing-masing disebut sebagai entitas yang dapat dipanggil. Untuk metode instans, entitas yang dapat dipanggil terdiri dari instans dan metode pada instans tersebut. Untuk metode statis, entitas yang dapat dipanggil hanya terdiri dari metode. Memanggil instans delegasi dengan sekumpulan argumen yang sesuai menyebabkan setiap entitas delegasi yang dapat dipanggil dipanggil dengan sekumpulan argumen yang diberikan.

Catatan: Properti menarik dan berguna dari instans delegasi adalah bahwa ia tidak tahu atau peduli tentang kelas metode yang dienkapsulasinya; yang penting adalah bahwa metode tersebut kompatibel (§21.4) dengan jenis delegasi. Ini membuat delegasi sangat cocok untuk pemanggilan "anonim". catatan akhir

21.2 Deklarasi delegasi

delegate_declaration adalah type_declaration (§14,7) yang mendeklarasikan jenis delegasi baru.

delegate_declaration
    : attributes? delegate_modifier* 'delegate' return_type delegate_header
    | attributes? delegate_modifier* 'delegate' ref_kind ref_return_type
      delegate_header
    ;

delegate_header
    : identifier '(' parameter_list? ')' ';'
    | identifier variant_type_parameter_list '(' parameter_list? ')'
      type_parameter_constraints_clause* ';'
    ;
    
delegate_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | unsafe_modifier   // unsafe code support
    ;

unsafe_modifier didefinisikan dalam §24.2.

Ini adalah kesalahan waktu kompilasi agar pengubah yang sama muncul beberapa kali dalam deklarasi delegasi.

Deklarasi delegasi yang menyediakan variant_type_parameter_list adalah deklarasi delegasi generik. Selain itu, setiap delegasi yang bersarang di dalam deklarasi kelas generik atau deklarasi struct generik adalah deklarasi delegasi generik, karena argumen jenis untuk jenis yang berisi harus disediakan untuk membuat jenis yang dibangun (§8.4).

Pengubah new hanya diizinkan pada delegasi yang dideklarasikan dalam jenis lain, dalam hal ini menentukan bahwa delegasi tersebut menyembunyikan anggota yang diwariskan dengan nama yang sama, seperti yang dijelaskan dalam §15.3.5.

Pengubah public, protected, internal, dan private mengontrol aksesibilitas jenis delegasi. Tergantung pada konteks di mana deklarasi delegasi terjadi, beberapa pengubah ini mungkin tidak diizinkan (§7.5.2).

Nama jenis delegasi adalah pengidentifikasi.

Seperti metode (§15.6.1), jika ref ada, delegasi mengembalikan-demi-ref; jika tidak, jika return_type adalah void, delegasi mengembalikan-tanpa-nilai; jika tidak, delegasi mengembalikan-berdasarkan-nilai.

parameter_list opsional menentukan parameter delegasi.

Return_type deklarasi delegasi returns-by-value atau returns-no-value menentukan jenis hasil, jika ada, yang dikembalikan oleh delegasi.

Ref_return_type deklarasi delegasi returns-by-ref menentukan jenis variabel yang direferensikan oleh variable_reference (§9,5) yang dikembalikan oleh delegasi.

variant_type_parameter_list opsional (§19.2.3) menentukan parameter jenis untuk delegasi itu sendiri.

Jenis pengembalian jenis delegasi harus void, atau output-safe (§19.2.3.2).

Semua jenis parameter jenis delegasi harus aman input (§19.2.3.2). Selain itu, setiap jenis parameter output atau referensi juga akan aman output.

Catatan: Parameter output diperlukan agar aman input karena pembatasan implementasi umum. catatan akhir

Selain itu, setiap batasan jenis kelas, batasan jenis antarmuka, dan batasan parameter jenis pada parameter jenis apa pun dari delegasi harus aman input.

Jenis delegasi dalam C# setara dengan nama, tidak setara secara struktural.

Contoh:

delegate int D1(int i, double d);
delegate int D2(int c, double d);

Jenis D1 delegasi dan D2 merupakan dua jenis yang berbeda, sehingga tidak dapat dipertukarkan, meskipun tanda tangannya identik.

contoh akhir

Seperti deklarasi jenis generik lainnya, argumen jenis harus diberikan untuk membuat jenis delegasi yang dibangun. Jenis parameter dan jenis pengembalian jenis delegasi yang dibangun dibuat dengan mengganti, untuk setiap parameter jenis dalam deklarasi delegasi, argumen jenis yang sesuai dari jenis delegasi yang dibangun.

Satu-satunya cara untuk mendeklarasikan jenis delegasi adalah melalui delegate_declaration. Setiap jenis delegasi adalah jenis referensi yang berasal dari System.Delegate. Anggota yang diperlukan untuk setiap jenis delegasi dirinci dalam §21.3. Jenis delegasi secara sealedimplisit , sehingga tidak diizinkan untuk mendapatkan jenis apa pun dari jenis delegasi. Juga tidak diizinkan untuk menyatakan jenis kelas non-delegasi yang berasal dari System.Delegate. System.Delegate bukan tipe delegasi; ini adalah jenis kelas tempat semua jenis delegasi diturunkan.

21.3 Mendelegasikan anggota

Setiap jenis delegasi mewarisi anggota dari Delegate kelas seperti yang dijelaskan dalam §15.3.4. Selain itu, setiap jenis delegasi harus menyediakan metode non-generik Invoke yang daftar parameternya cocok dengan parameter_list dalam deklarasi delegasi, yang jenis pengembaliannya cocok dengan return_type atau ref_return_type dalam deklarasi delegasi, dan untuk delegasi returns-by-ref yang ref_kind nya cocok dengan yang dalam deklarasi delegasi. Metode Invoke harus setidaknya dapat diakses sebagai jenis delegasi yang berisi. Memanggil Invoke metode pada jenis delegasi secara semantik setara dengan menggunakan sintaks pemanggilan delegasi (§21.6) .

Implementasi dapat menentukan anggota tambahan dalam jenis delegasi.

Kecuali untuk instans, setiap operasi yang dapat diterapkan ke instans kelas atau kelas juga dapat diterapkan ke kelas atau instans delegasi. Secara khusus, dimungkinkan untuk mengakses anggota jenis System.Delegate melalui sintaks akses anggota yang biasa.

21.4 Mendelegasikan kompatibilitas

Metode atau jenis Mdelegasi kompatibel dengan jenis D delegasi jika semua hal berikut ini benar:

  • D dan M memiliki jumlah parameter yang sama, dan setiap parameter di D memiliki pengubah parameter by-reference yang sama dengan parameter yang sesuai di M.
  • Untuk setiap parameter nilai, konversi identitas (§10.2.2
  • Untuk setiap parameter referensi, jenis parameter di D sama dengan jenis parameter di M.
  • Salah satu hal berikut ini benar:
    • D dan M keduanya mengembalikantanpa nilai .
    • Ddan M adalah returns-by-value (§15.6.1, §21.2), dan konversi referensi identitas atau implisit ada dari jenis M pengembalian ke jenis pengembalian .D
    • Ddan M keduanya adalah returns-by-ref, konversi identitas ada antara jenis M pengembalian dan jenis Dpengembalian , dan keduanya memiliki ref_kind yang sama.

Definisi kompatibilitas ini memungkinkan kovariansi dalam jenis pengembalian dan kontravariansi dalam jenis parameter.

Contoh:

delegate int D1(int i, double d);
delegate int D2(int c, double d);
delegate object D3(string s);

class A
{
    public static int M1(int a, double b) {...}
}

class B
{
    public static int M1(int f, double g) {...}
    public static void M2(int k, double l) {...}
    public static int M3(int g) {...}
    public static void M4(int g) {...}
    public static object M5(string s) {...}
    public static int[] M6(object o) {...}
}

Metode A.M1 dan B.M1 kompatibel dengan jenis D1 delegasi dan D2, karena mereka memiliki jenis pengembalian dan daftar parameter yang sama. Metode B.M2, , dan B.M3 tidak kompatibel dengan jenis B.M4 delegasi dan D1, karena mereka memiliki jenis pengembalian atau daftar D2parameter yang berbeda. Metode B.M5 dan B.M6 keduanya kompatibel dengan jenis D3delegasi .

contoh akhir

Contoh:

delegate bool Predicate<T>(T value);

class X
{
    static bool F(int i) {...}
    static bool G(string s) {...}
}

Metode X.F ini kompatibel dengan jenis Predicate<int> delegasi dan metode X.G ini kompatibel dengan jenis Predicate<string>delegasi .

contoh akhir

Catatan: Arti intuitif dari kompatibilitas delegasi adalah bahwa metode kompatibel dengan jenis delegasi jika setiap pemanggilan delegasi dapat diganti dengan pemanggilan metode tanpa melanggar keamanan jenis, memperlakukan parameter opsional dan array parameter sebagai parameter eksplisit. Misalnya, dalam kode berikut:

delegate void Action<T>(T arg);

class Test
{
    static void Print(object value) => Console.WriteLine(value);

    static void Main()
    {
        Action<string> log = Print;
        log("text");
    }
}

Metode Print ini kompatibel dengan Action<string> jenis delegasi karena setiap pemanggilan Action<string> delegasi juga akan menjadi pemanggilan metode yang Print valid.

Jika penandatanganan metode Print di atas diubah menjadi Print(object value, bool prependTimestamp = false) misalnya, metode Print tidak akan lagi kompatibel dengan Action<string> menurut aturan subklausa ini.

catatan akhir

21.5 Mendelegasikan instansiasi

Instans dari delegasi dibuat oleh delegate_creation_expression (§12.8.17.5), konversi ke tipe delegasi, kombinasi delegasi, atau penghapusan delegasi. Instans delegasi yang baru dibuat kemudian mengacu pada satu atau beberapa:

  • Metode statis yang dirujuk dalam delegate_creation_expression, atau
  • Objek target (yang tidak dapat null) dan metode instans yang dirujuk dalam delegate_creation_expression, atau
  • Delegasi lain (§12.8.17.5).

Contoh:

delegate void D(int x);

class C
{
    public static void M1(int i) {...}
    public void M2(int i) {...}
}

class Test
{
    static void Main()
    {
        D cd1 = new D(C.M1); // Static method
        C t = new C();
        D cd2 = new D(t.M2); // Instance method
        D cd3 = new D(cd2);  // Another delegate
    }
}

contoh akhir

Sekumpulan metode yang dienkapsulasi oleh instans delegasi disebut daftar pemanggilan. Ketika instans delegasi dibuat dari satu metode, instans tersebut merangkum metode tersebut, dan daftar pemanggilannya hanya berisi satu entri. Namun, ketika dua instans non-delegasinull digabungkan, daftar pemanggilannya digabungkan—dalam urutan operand kiri lalu operand kanan—untuk membentuk daftar pemanggilan baru, yang berisi dua entri atau lebih.

Saat delegasi baru dibuat dari satu delegasi, daftar pemanggilan yang dihasilkan hanya memiliki satu entri, yang merupakan delegasi sumber (§12.8.17.5).

Delegasi digabungkan menggunakan biner + (§12.12.5) dan += operator (§12.23.4). Delegasi dapat dihapus dari kombinasi delegasi, menggunakan biner - (§12.12.6) dan -= operator (§12.23.4). Delegasi dapat dibandingkan dengan kesetaraan (§12.14.9).

Contoh: Contoh berikut menunjukkan instansiasi sejumlah delegasi, dan daftar pemanggilan yang sesuai:

delegate void D(int x);

class C
{
    public static void M1(int i) {...}
    public static void M2(int i) {...}
}

class Test
{
    static void Main() 
    {
        D cd1 = new D(C.M1); // M1 - one entry in invocation list
        D cd2 = new D(C.M2); // M2 - one entry
        D cd3 = cd1 + cd2;   // M1 + M2 - two entries
        D cd4 = cd3 + cd1;   // M1 + M2 + M1 - three entries
        D cd5 = cd4 + cd3;   // M1 + M2 + M1 + M1 + M2 - five entries
        D td3 = new D(cd3);  // [M1 + M2] - ONE entry in invocation
                             // list, which is itself a list of two methods.
        D td4 = td3 + cd1;   // [M1 + M2] + M1 - two entries
        D cd6 = cd4 - cd2;   // M1 + M1 - two entries in invocation list
        D td6 = td4 - cd2;   // [M1 + M2] + M1 - two entries in invocation list,
                             // but still three methods called, M2 not removed.
   }
}

Ketika cd1 dan cd2 dibuat, mereka masing-masing merangkum satu metode. Ketika cd3 diinstansiasi, ia memiliki daftar pemanggilan dua metode, dan M1, M2 dalam urutan tersebut. cd4Daftar pemanggilan berisi M1, , M2dan M1, dalam urutan tersebut. Untuk cd5, daftar pemanggilan berisi M1, , M2, M1M1, dan M2, dalam urutan tersebut.

Saat membuat delegasi dari delegasi lain dengan delegate_creation_expression hasilnya memiliki daftar pemanggilan dengan struktur yang berbeda dari aslinya, tetapi yang mengakibatkan metode yang sama dipanggil dalam urutan yang sama. Ketika td3 dibuat dari cd3 daftar pemanggilannya hanya memiliki satu anggota, tetapi anggota tersebut adalah daftar metode M1 dan M2 dan metode tersebut dipanggil oleh td3 dalam urutan yang sama seperti yang dipanggil oleh cd3. Demikian pula ketika td4 dibuat daftar pemanggilannya hanya memiliki dua entri tetapi memanggil tiga metode M1, , M2dan M1, dalam urutan itu seperti cd4 halnya.

Struktur daftar pemanggilan memengaruhi pengurangan delegasi. Delegasikan cd6, dibuat dengan mengurangi cd2 (yang memanggil M2) dari cd4 (yang memanggil M1, , M2dan M1) memanggil M1 dan M1. Namun mendelegasikan td6, dibuat dengan mengurangi cd2 (yang memanggil M2) dari td4 (yang memanggil M1, , M2dan M1) masih memanggil M1, M2 dan M1, dalam urutan tersebut, seperti M2 bukan entri tunggal dalam daftar tetapi anggota daftar berlapis. Untuk contoh selengkapnya tentang menggabungkan delegasi (serta menghapus), lihat §21,6.

contoh akhir

Setelah dibuat, instans delegasi selalu merujuk ke daftar pemanggilan yang sama.

Catatan: Ingat, ketika dua delegasi digabungkan, atau satu dihapus dari yang lain, hasil delegasi baru dengan daftar pemanggilannya sendiri; daftar pemanggilan delegasi digabungkan atau dihapus tetap tidak berubah. catatan akhir

21.6 Mendelegasikan pemanggilan

C# menyediakan sintaks khusus untuk memanggil delegasi. Ketika instans delegasi non-null yang daftar pemanggilannya berisi satu entri dipanggil, ia memanggil satu metode dengan argumen yang sama dengan yang diberikan dan mengembalikan nilai yang sama dengan metode yang dirujuk. (Lihat §12.8.10.4 untuk informasi terperinci tentang pemanggilan delegasi.) Jika pengecualian terjadi selama pemanggilan delegasi seperti itu, dan pengecualian itu tidak tertangkap dalam metode yang dipanggil, pencarian klausul tangkapan pengecualian berlanjut dalam metode yang memanggil delegasi, seolah-olah metode tersebut secara langsung memanggil metode yang dirujuk oleh delegasi.

Pemanggilan instans delegasi yang daftar pemanggilannya berisi beberapa entri berlanjut dengan memanggil setiap metode dalam daftar pemanggilan secara sinkron, secara berurutan. Setiap metode yang disebut diteruskan sekumpulan argumen yang sama seperti yang diberikan kepada instans delegasi. Jika pemanggilan delegasi seperti itu mencakup parameter referensi (§15.6.2.3.3), setiap pemanggilan metode akan terjadi dengan referensi ke variabel yang sama; perubahan ke variabel tersebut dengan satu metode dalam daftar pemanggilan akan terlihat oleh metode lebih jauh ke bawah daftar pemanggilan. Jika pemanggilan delegasi menyertakan parameter output atau nilai pengembalian, nilai akhir mereka akan berasal dari pemanggilan delegasi terakhir dalam daftar. Jika pengecualian terjadi selama pemrosesan pemanggilan delegasi seperti itu, dan pengecualian itu tidak tertangkap dalam metode yang dipanggil, pencarian klausul tangkapan pengecualian berlanjut dalam metode yang memanggil delegasi, dan metode apa pun di bawah daftar pemanggilan tidak dipanggil.

Mencoba memanggil instans delegasi yang nilainya null menghasilkan pengecualian jenis System.NullReferenceException.

Contoh: Contoh berikut menunjukkan cara membuat instans, menggabungkan, menghapus, dan memanggil delegasi:

delegate void D(int x);

class C
{
    public static void M1(int i) => Console.WriteLine("C.M1: " + i);

    public static void M2(int i) => Console.WriteLine("C.M2: " + i);

    public void M3(int i) => Console.WriteLine("C.M3: " + i);
}

class Test
{
    static void Main()
    {
        D cd1 = new D(C.M1);
        cd1(-1);             // call M1
        D cd2 = new D(C.M2);
        cd2(-2);             // call M2
        D cd3 = cd1 + cd2;
        cd3(10);             // call M1 then M2
        cd3 += cd1;
        cd3(20);             // call M1, M2, then M1
        C c = new C();
        D cd4 = new D(c.M3);
        cd3 += cd4;
        cd3(30);             // call M1, M2, M1, then M3
        cd3 -= cd1;          // remove last M1
        cd3(40);             // call M1, M2, then M3
        cd3 -= cd4;
        cd3(50);             // call M1 then M2
        cd3 -= cd2;
        cd3(60);             // call M1
        cd3 -= cd2;          // impossible removal is benign
        cd3(60);             // call M1
        cd3 -= cd1;          // invocation list is empty so cd3 is null
        // cd3(70);          // System.NullReferenceException thrown
        cd3 -= cd1;          // impossible removal is benign
    }
}

Seperti yang ditunjukkan dalam pernyataan cd3 += cd1;, delegasi dapat hadir dalam daftar pemanggilan beberapa kali. Dalam hal ini, itu hanya dipanggil sekali per kemunculan. Dalam daftar pemanggilan seperti ini, ketika delegasi tersebut dihapus, kemunculan terakhir dalam daftar pemanggilan adalah yang benar-benar dihapus.

Segera sebelum eksekusi pernyataan akhir, cd3 -= cd1;, delegasi cd3 mengacu pada daftar pemanggilan kosong. Mencoba menghapus delegasi dari daftar kosong (atau untuk menghapus delegasi yang tidak ada dari daftar yang tidak kosong) bukanlah kesalahan.

Output yang dihasilkan adalah:

C.M1: -1
C.M2: -2
C.M1: 10
C.M2: 10
C.M1: 20
C.M2: 20
C.M1: 20
C.M1: 30
C.M2: 30
C.M1: 30
C.M3: 30
C.M1: 40
C.M2: 40
C.M3: 40
C.M1: 50
C.M2: 50
C.M1: 60
C.M1: 60

contoh akhir