Bagikan melalui


15 Kelas

15.1 Umum

Kelas adalah struktur data yang mungkin berisi anggota data (konstanta dan bidang), anggota fungsi (metode, properti, peristiwa, pengindeks, operator, konstruktor instans, finalizer, dan konstruktor statis), dan jenis berlapis. Tipe kelas mendukung pewarisan, mekanisme di mana kelas turunan dapat memperpanjang dan mengkhususkan kelas dasar.

15.2 Deklarasi kelas

15.2.1 Umum

class_declaration adalah type_declaration (§14,7) yang mendeklarasikan kelas baru.

class_declaration
    : attributes? class_modifier* 'partial'? 'class' identifier
        type_parameter_list? class_base? type_parameter_constraints_clause*
        class_body ';'?
    ;

class_declaration terdiri dari sekumpulan atribut opsional (§22), diikuti dengan sekumpulan class_modifier opsional (§15.2.2), diikuti oleh pengubah opsional partial (§15.2.7), diikuti oleh kata kunci class dan pengidentifikasi yang menamai kelas, diikuti oleh type_parameter_list opsional (§15.2.3), diikuti dengan spesifikasi class_base opsional (§15.2.4), diikuti dengan sekumpulan type_parameter_constraints_clause opsional (§15.2.5), diikuti dengan class_body (§15.2.6), secara opsional diikuti dengan titik koma.

Deklarasi kelas tidak boleh memberikan type_parameter_constraints_clausekecuali jika juga memberikan type_parameter_list.

Deklarasi kelas yang memasok type_parameter_list adalah deklarasi kelas generik. Selain itu, setiap kelas yang ditumpuk di dalam deklarasi kelas generik atau deklarasi struktur generik adalah deklarasi kelas generik, karena argumen jenis untuk jenis yang berisi harus disediakan untuk membuat jenis yang dibangun (§8.4).

15.2.2 Pengubah kelas

15.2.2.1 Umum

Class_declaration dapat secara opsional menyertakan urutan pengubah kelas:

class_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | 'abstract'
    | 'sealed'
    | 'static'
    | unsafe_modifier   // unsafe code support
    ;

unsafe_modifier (§23.2) hanya tersedia dalam kode tidak aman (§23).

Ini adalah kesalahan waktu kompilasi jika pengubah yang sama muncul beberapa kali dalam deklarasi kelas.

Pengubah new diizinkan pada kelas berlapis. Ini menentukan bahwa kelas menyembunyikan anggota yang diwariskan dengan nama yang sama, seperti yang dijelaskan dalam §15.3.5. Ini adalah kesalahan pada saat kompilasi jika pengubah new muncul pada deklarasi kelas yang bukan deklarasi kelas bersarang.

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

Ketika deklarasi jenis parsial (§15.2.7) menyertakan spesifikasi aksesibilitas (melalui pengubah public, protected, internal, dan private), spesifikasi tersebut harus konsisten dengan semua bagian lain yang menyertakan spesifikasi aksesibilitas. Jika tidak ada bagian dari jenis parsial yang menyertakan spesifikasi aksesibilitas, jenis tersebut diberikan aksesibilitas default yang sesuai (§7.5.2).

Pengubah abstract, sealed, dan static dibahas dalam subklaus berikut.

15.2.2.2 Kelas abstrak

Pengubah abstract digunakan untuk menunjukkan bahwa kelas tidak lengkap dan dimaksudkan untuk digunakan hanya sebagai kelas dasar. Kelas abstrak berbeda dari kelas non-abstrak dengan cara berikut:

  • Kelas abstrak tidak dapat dibuat secara langsung, dan merupakan kesalahan waktu kompilasi untuk menggunakan new operator pada kelas abstrak. Meskipun dimungkinkan untuk memiliki variabel dan nilai yang jenis waktu kompilasinya abstrak, variabel dan nilai tersebut akan selalu berupa null atau berisi referensi ke instans kelas non-abstrak yang berasal dari jenis abstrak.
  • Kelas abstrak diizinkan (tetapi tidak diperlukan) untuk berisi anggota abstrak.
  • Kelas abstrak tidak dapat disegel.

Ketika kelas non-abstrak diturunkan dari kelas abstrak, kelas non-abstrak harus mencakup implementasi aktual dari semua anggota abstrak yang diturunkan, sehingga menggantikan anggota abstrak tersebut.

Contoh: Dalam kode berikut

abstract class A
{
    public abstract void F();
}

abstract class B : A
{
    public void G() {}
}

class C : B
{
    public override void F()
    {
        // Actual implementation of F
    }
}

kelas A abstrak memperkenalkan metode Fabstrak . Kelas B memperkenalkan metode Gtambahan , tetapi karena tidak memberikan implementasi , FB juga harus dinyatakan abstrak. Kelas C mengambil alih F dan memberikan implementasi aktual. Karena tidak ada anggota abstrak di C, C diizinkan (tetapi tidak diharuskan) untuk tidak abstrak.

contoh akhir

Jika satu atau beberapa bagian dari deklarasi jenis parsial (§15.2.7) dari kelas menyertakan abstract pengubah, kelas tersebut abstrak. Jika tidak, kelas tersebut tidak abstrak.

15.2.2.3 Kelas yang disegel

Pengubah sealed digunakan untuk mencegah derivasi dari kelas. Kesalahan waktu kompilasi terjadi jika kelas yang disegel ditentukan sebagai kelas dasar kelas lain.

Kelas yang disegel juga tidak dapat menjadi kelas abstrak.

Catatan: Pengubah sealed terutama digunakan untuk mencegah derivasi yang tidak diinginkan, tetapi juga memungkinkan pengoptimalan run-time tertentu. Secara khusus, karena kelas yang disegel diketahui tidak pernah memiliki kelas turunan, dimungkinkan untuk mengubah pemanggilan anggota fungsi virtual pada instans kelas yang disegel menjadi pemanggilan non-virtual. catatan akhir

Jika satu atau beberapa bagian dari deklarasi jenis parsial (§15.2.7) dari kelas menyertakan sealed pengubah, kelas akan disegel. Selain itu, kelas tersebut tidak disegel.

15.2.2.4 Kelas statis

15.2.2.4.1 Umum

Pengubah static digunakan untuk menandai kelas yang dideklarasikan sebagai kelas statis. Kelas statis tidak boleh dibuat instans, tidak boleh digunakan sebagai jenis dan hanya boleh berisi anggota statis. Hanya kelas statis yang dapat berisi deklarasi metode ekstensi (§15.6.10).

Deklarasi kelas statis tunduk pada batasan berikut:

  • Kelas statis tidak boleh menyertakan sealed atau abstract pengubah. (Namun, karena kelas statis tidak dapat dibuat instansinya atau diturunkan, itu berperilaku seolah-olah kelas tersebut disegel dan abstrak.)
  • Kelas statis tidak boleh menyertakan spesifikasi class_base (§15.2.4) dan tidak dapat secara eksplisit menentukan kelas dasar atau daftar antarmuka yang diimplementasikan. Kelas statis secara implisit mewarisi dari jenis object.
  • Kelas statis hanya boleh berisi anggota statis (§15.3.8).

    Catatan: Semua konstanta dan jenis berlapis diklasifikasikan sebagai anggota statis. catatan akhir

  • Kelas statis tidak boleh memiliki anggota dengan protected, , private protectedatau protected internal aksesibilitas yang dinyatakan.

Terdapat kesalahan waktu kompilasi jika melanggar salah satu pembatasan ini.

Kelas statis tidak memiliki konstruktor instans. Tidak dimungkinkan untuk mendeklarasikan konstruktor instans di kelas statis, dan tidak ada konstruktor instans default (§15.11.5) disediakan untuk kelas statis.

Anggota kelas statis tidak secara otomatis statis, dan deklarasi anggota harus secara eksplisit menyertakan static pengubah (kecuali untuk konstanta dan jenis berlapis). Saat sebuah kelas bersarang dalam kelas induk statis, kelas bersarang tersebut bukan merupakan kelas statis kecuali secara eksplisit menyertakan static pengubah.

Jika satu atau beberapa bagian dari deklarasi jenis parsial (§15.2.7) dari kelas menyertakan static pengubah, kelas bersifat statis. Jika tidak, kelas tidak statis.

15.2.2.4.2 Mereferensikan jenis kelas statis

namespace_or_type_name (§7,8) diizinkan untuk mereferensikan kelas statis jika

  • namespace_or_type_name adalah T dalam sebuah namespace_or_type_name dengan bentuk T.I, atau
  • namespace_or_type-name adalah T dalam typeof_expression (§12.8.18) dengan bentuk typeof(T).

primary_expression (§12,8) diizinkan untuk mereferensikan kelas statis jika

  • primary_expression adalah E dalam member_access (§12.8.7) dengan E.I.

Dalam konteks lain, ini adalah kesalahan waktu kompilasi untuk merujuk kelas statis.

Catatan: Misalnya, adalah kesalahan jika kelas statis digunakan sebagai kelas induk, jenis penyusun (§15.3.7) dari anggota, argumen tipe generik, atau batasan parameter tipe. Demikian juga, kelas statis tidak dapat digunakan dalam jenis array, ekspresi baru, ekspresi cast, ekspresi 'is', ekspresi 'as', sizeof ekspresi, atau ekspresi nilai default. catatan akhir

15.2.3 Parameter jenis

Parameter tipe adalah pengidentifikasi sederhana yang menunjukkan pengganti untuk argumen tipe yang disediakan untuk membuat tipe terstruktur. Sebaliknya, argumen jenis (§8.4.2) adalah jenis yang menggantikan parameter jenis ketika jenis dibangun.

type_parameter_list
    : '<' decorated_type_parameter (',' decorated_type_parameter)* '>'
    ;

decorated_type_parameter
    : attributes? type_parameter
    ;

type_parameter didefinisikan dalam §8,5.

Setiap parameter jenis dalam deklarasi kelas menentukan nama di ruang deklarasi (§7,3) dari kelas tersebut. Dengan demikian, tidak boleh memiliki nama yang sama dengan parameter jenis lain dari kelas tersebut atau anggota yang dideklarasikan di kelas tersebut. Parameter jenis tidak boleh memiliki nama yang sama dengan jenis itu sendiri.

Dua deklarasi jenis generik parsial (dalam program yang sama) berkontribusi pada jenis generik yang tidak terikat yang sama jika mereka memiliki nama yang sepenuhnya memenuhi syarat yang sama (yang mencakup generic_dimension_specifier (§12.8.18) untuk jumlah parameter jenis) (§7.8.3). Dua deklarasi jenis parsial tersebut harus menentukan nama yang sama untuk setiap parameter jenis, secara berurutan.

15.2.4 Spesifikasi dasar kelas

15.2.4.1 Umum

Deklarasi kelas dapat mencakup spesifikasi class_base , yang menentukan kelas dasar langsung kelas dan antarmuka (§18) yang langsung diimplementasikan oleh kelas.

class_base
    : ':' class_type
    | ':' interface_type_list
    | ':' class_type ',' interface_type_list
    ;

interface_type_list
    : interface_type (',' interface_type)*
    ;

15.2.4.2 Kelas dasar

Ketika class_type disertakan dalam class_base, class_base menentukan kelas dasar langsung kelas yang dideklarasikan. Jika deklarasi kelas non-parsial tidak memiliki class_base, atau jika class_base hanya mencantumkan jenis antarmuka, kelas dasar langsung diasumsikan sebagai object. Ketika deklarasi kelas parsial mencakup spesifikasi kelas dasar, spesifikasi kelas dasar tersebut akan mereferensikan jenis yang sama dengan semua bagian lain dari jenis parsial yang mencakup spesifikasi kelas dasar. Jika tidak ada bagian dari kelas parsial yang menyertakan spesifikasi kelas dasar, kelas dasarnya adalah object. Kelas mewarisi anggota dari kelas dasar langsungnya, seperti yang dijelaskan dalam §15.3.4.

Contoh: Dalam kode berikut

class A {}
class B : A {}

Kelas A dikatakan sebagai kelas dasar langsung dari B, dan B dikatakan berasal dari A. Karena A tidak secara eksplisit menentukan kelas dasar langsung, kelas dasar langsungnya adalah object secara implisit.

contoh akhir

Untuk jenis kelas yang dibangun, termasuk jenis berlapis yang dideklarasikan dalam deklarasi jenis generik (§15.3.9.7), jika kelas dasar ditentukan dalam deklarasi kelas generik, kelas dasar dari jenis yang dibangun diperoleh dengan menggantikan, untuk setiap type_parameter dalam deklarasi kelas dasar, type_argument terkait dari jenis yang dibangun.

Contoh: Diberikan deklarasi kelas generik

class B<U,V> {...}
class G<T> : B<string,T[]> {...}

kelas dasar dari jenis G<int> yang dibangun adalah B<string,int[]>.

contoh akhir

Kelas dasar yang ditentukan dalam deklarasi kelas dapat menjadi jenis kelas yang dibangun (§8.4). Kelas dasar tidak dapat menjadi parameter jenis sendiri (§8,5), meskipun dapat melibatkan parameter jenis yang berada dalam cakupan.

Contoh:

class Base<T> {}

// Valid, non-constructed class with constructed base class
class Extend1 : Base<int> {}

// Error, type parameter used as base class
class Extend2<V> : V {}

// Valid, type parameter used as type argument for base class
class Extend3<V> : Base<V> {}

contoh akhir

Kelas dasar langsung dari suatu jenis kelas harus memiliki tingkat aksesibilitas yang setidaknya sama dengan jenis kelas itu sendiri (§7.5.5). Misalnya, adalah kesalahan waktu kompilasi jika sebuah kelas publik diturunkan dari kelas privat atau internal.

Kelas dasar langsung dari jenis kelas tidak boleh menjadi salah satu jenis berikut: System.Array, , System.DelegateSystem.Enum, System.ValueType atau jenisnyadynamic. Selain itu, deklarasi kelas generik tidak boleh digunakan System.Attribute sebagai kelas dasar langsung atau tidak langsung (§22.2.1).

Dalam menentukan arti spesifikasi A kelas dasar langsung dari kelas B, kelas B dasar langsung diasumsikan untuk sementara menjadi object, yang memastikan bahwa arti spesifikasi kelas dasar tidak dapat secara rekursif bergantung pada dirinya sendiri.

Contoh: Berikut ini

class X<T>
{
    public class Y{}
}

class Z : X<Z.Y> {}

dalam kesalahan karena dalam spesifikasi kelas dasar X<Z.Y>, kelas induk langsung dari Z dianggap sebagai object, dan karena itu (sesuai aturan §7.8) Z tidak dianggap memiliki anggota Y.

contoh akhir

Kelas dasar dari suatu kelas adalah kelas induk langsung dan kelas induk dari kelas tersebut. Dengan kata lain, sekumpulan kelas dasar merupakan penutupan transitif dari hubungan kelas dasar langsung.

Contoh: Dalam hal berikut:

class A {...}
class B<T> : A {...}
class C<T> : B<IComparable<T>> {...}
class D<T> : C<T[]> {...}

kelas D<int> dasar adalah C<int[]>, , B<IComparable<int[]>>, Adan object.

contoh akhir

Kecuali untuk kelas object, setiap kelas memiliki tepat satu kelas dasar langsung. Kelas ini object tidak memiliki kelas dasar langsung dan merupakan kelas dasar utama dari semua kelas lainnya.

Ini adalah kesalahan waktu kompilasi bagi kelas yang bergantung pada dirinya sendiri. Untuk tujuan aturan ini, kelas secara langsung tergantung pada kelas dasar langsungnya (jika ada) dan secara langsung tergantung pada kelas penutup terdekat di mana kelas tersebut disarangkan (jika ada). Mengingat definisi ini, set lengkap kelas yang menjadi dasar ketergantungan sebuah kelas adalah penutupan transitif dari hubungan ketergantungan langsung.

Contoh: Contoh

class A : A {}

keliru karena kelas ini bergantung pada dirinya sendiri. Demikian juga, contoh

class A : B {}
class B : C {}
class C : A {}

terjadi kesalahan karena kelas bergantung secara melingkar pada diri mereka sendiri. Terakhir, contoh

class A : B.C {}
class B : A
{
    public class C {}
}

Menghasilkan kesalahan pada waktu kompilasi karena A bergantung pada B.C (kelas dasar langsungnya), yang bergantung pada B (kelas yang langsung melingkupinya), yang secara sirkuler bergantung pada A.

contoh akhir

Sebuah kelas tidak bergantung pada kelas-kelas yang ditempatkan di dalamnya.

Contoh: Dalam kode berikut

class A
{
    class B : A {}
}

B tergantung pada A (karena A merupakan kelas dasar langsung dan kelas yang segera tertutup), tetapi A tidak bergantung pada B (karena B bukan kelas dasar atau kelas Apenutup ). Dengan demikian, contohnya valid.

contoh akhir

Tidak mungkin melakukan turunan dari sealed class.

Contoh: Dalam kode berikut

sealed class A {}
class B : A {} // Error, cannot derive from a sealed class

Kelas B mengalami kesalahan karena mencoba mewarisi dari kelas A yang disegel.

contoh akhir

15.2.4.3 Implementasi antarmuka

Spesifikasi class_base dapat mencakup daftar jenis antarmuka, dalam hal ini kelas dikatakan untuk mengimplementasikan jenis antarmuka yang diberikan. Untuk jenis kelas yang terkontruksi, termasuk jenis bersarang yang dideklarasikan dalam deklarasi jenis generik (§15.3.9.7), setiap jenis antarmuka yang diimplementasikan diperoleh dengan mengganti, untuk setiap type_parameter di antarmuka tertentu, type_argument yang sesuai dari jenis yang terkontruksi.

Sekumpulan antarmuka untuk jenis yang dideklarasikan dalam beberapa bagian (§15.2.7) merupakan keseluruhan antarmuka yang ditentukan pada setiap bagian. Suatu antarmuka tertentu hanya dapat diberi nama sekali pada setiap bagian, tetapi beberapa bagian dapat memberikan nama yang sama untuk antarmuka dasar yang sama. Hanya akan ada satu implementasi dari setiap anggota antarmuka tertentu.

Contoh: Dalam hal berikut:

partial class C : IA, IB {...}
partial class C : IC {...}
partial class C : IA, IB {...}

set antarmuka dasar untuk kelas C adalah IA, IB, dan IC.

contoh akhir

Biasanya, setiap bagian menyediakan implementasi dari antarmuka yang dideklarasikan pada bagian tersebut; namun, ini bukanlah sebuah keharusan. Bagian dapat menyediakan implementasi untuk antarmuka yang dideklarasikan pada bagian yang berbeda.

Contoh:

partial class X
{
    int IComparable.CompareTo(object o) {...}
}

partial class X : IComparable
{
    ...
}

contoh akhir

Antarmuka dasar yang ditentukan dalam deklarasi kelas dapat dibangun jenis antarmuka (§8.4, §18.2). Antarmuka dasar tidak dapat menjadi parameter jenis sendiri, meskipun dapat melibatkan parameter jenis yang berada dalam cakupan.

Contoh: Kode berikut menggambarkan bagaimana kelas dapat mengimplementasikan dan memperluas jenis yang dibangun:

class C<U, V> {}
interface I1<V> {}
class D : C<string, int>, I1<string> {}
class E<T> : C<int, T>, I1<T> {}

contoh akhir

Implementasi antarmuka dibahas lebih lanjut dalam §18.6.

15.2.5 Jenis batasan parameter

Deklarasi jenis dan metode generik dapat secara opsional menentukan batasan parameter jenis dengan menyertakan type_parameter_constraints_clauses.

type_parameter_constraints_clause
    : 'where' type_parameter ':' type_parameter_constraints
    ;

type_parameter_constraints
    : primary_constraint (',' secondary_constraints)? (',' constructor_constraint)?
    | secondary_constraints (',' constructor_constraint)?
    | constructor_constraint
    ;

primary_constraint
    : class_type nullable_type_annotation?
    | 'class' nullable_type_annotation?
    | 'struct'
    | 'notnull'
    | 'unmanaged'
    ;

secondary_constraint
    : interface_type nullable_type_annotation?
    | type_parameter nullable_type_annotation?
    ;

secondary_constraints
    : secondary_constraint (',' secondary_constraint)*
    ;

constructor_constraint
    : 'new' '(' ')'
    ;

Setiap type_parameter_constraints_clause terdiri dari token where, diikuti dengan nama parameter jenis, diikuti oleh titik dua dan daftar batasan untuk parameter jenis tersebut. Mungkin ada paling banyak satu where klausul untuk setiap parameter jenis, dan where klausul dapat dicantumkan dalam urutan apa pun. Seperti token get dan set dalam akses properti, token where bukan kata kunci.

Daftar batasan yang where diberikan dalam klausul dapat mencakup salah satu komponen berikut, dalam urutan ini: batasan utama tunggal, satu atau beberapa batasan sekunder, dan batasan konstruktor, new().

Batasan utama dapat berupa jenis kelas, batasan tipe referensi, batasan tipe nilai, batasan tidak null, atau batasan tipe tidak dikelola. Jenis kelas dan batasan jenis referensi dapat menyertakan nullable_type_annotation.

Batasan sekunder dapat berupa interface_type atau type_parameter, secara opsional diikuti oleh nullable_type_annotation. Kehadiran nullable_type_annotation menunjukkan bahwa argumen jenis diizinkan untuk menjadi jenis referensi nullable yang sesuai dengan jenis referensi yang tidak dapat diubah ke null yang memenuhi batasan.

Batasan jenis referensi menentukan bahwa argumen jenis yang digunakan untuk parameter jenis harus merupakan jenis referensi. Semua jenis kelas, jenis antarmuka, jenis delegasi, jenis array, dan parameter jenis yang diketahui sebagai jenis referensi (seperti yang didefinisikan di bawah) memenuhi batasan ini.

Tipe kelas, batasan tipe referensi, dan batasan sekunder dapat mencakup anotasi tipe nullable. Kehadiran atau ketidakhadiran anotasi ini pada parameter tipe menunjukkan ekspektasi nullability untuk argumen tipe.

  • Jika batasan tidak menyertakan anotasi jenis yang dapat bernilai null, argumen jenis diharapkan menjadi tipe referensi yang tidak dapat bernilai null. Pengkompilasi dapat mengeluarkan peringatan jika argumen jenis adalah jenis referensi nullable.
  • Jika batasan menyertakan anotasi jenis nullable, batasan dipenuhi oleh jenis referensi yang tidak dapat bernilai null dan jenis referensi nullable.

Ketidakpastian null dari argumen tipe tidak perlu sesuai dengan ketidakpastian null dari parameter tipe. Pengkompilasi dapat mengeluarkan peringatan jika kenuluran parameter tipe tidak cocok dengan kenuluran argumen tipe.

Catatan: Untuk menentukan bahwa argumen jenis adalah jenis referensi yang dapat diubah ke null, jangan tambahkan anotasi tipe yang dapat diubah ke null sebagai batasan (gunakan T : class atau T : BaseClass), tetapi gunakan T? di seluruh deklarasi generik untuk menunjukkan jenis referensi null yang sesuai untuk argumen jenis. catatan akhir

Anotasi jenis nullable, ?, tidak dapat digunakan pada argumen jenis yang tidak dibatasi.

Untuk jenis parameter T ketika argumen jenis adalah jenis C? referensi nullable, instans dari T? ditafsirkan sebagai C?, bukan C??.

Contoh: Contoh berikut menunjukkan bagaimana nullability argumen jenis berdampak pada nullability deklarasi parameter jenisnya:

public class C
{
}

public static class  Extensions
{
    public static void M<T>(this T? arg) where T : notnull
    {

    }
}

public class Test
{
    public void M()
    {
        C? mightBeNull = new C();
        C notNull = new C();

        int number = 5;
        int? missing = null;

        mightBeNull.M(); // arg is C?
        notNull.M(); //  arg is C?
        number.M(); // arg is int?
        missing.M(); // arg is int?
    }
}

Ketika argumen tipe adalah tipe yang tidak dapat bernilai null, anotasi tipe ? menunjukkan bahwa parameter adalah tipe nullable yang sesuai. Ketika argumen tipe sudah merupakan tipe referensi nullable, parameter adalah tipe nullable yang sama.

contoh akhir

Batasan bukan null menentukan bahwa argumen jenis yang digunakan untuk parameter jenis harus berupa jenis nilai yang tidak dapat diubah ke null atau jenis referensi yang tidak dapat diubah ke null. Argumen jenis yang bukan tipe nilai yang tidak dapat diubah ke null atau tipe referensi yang tidak dapat diubah ke null diizinkan, tetapi pengkompilasi dapat menghasilkan peringatan diagnostik.

Karena notnull bukan kata kunci, dalam primary_constraint batasan bukan null selalu ambigu secara sintetis dengan class_type. Untuk alasan kompatibilitas, jika pencarian nama (§12.8.4) dari namanya berhasil, nama notnull tersebut akan diperlakukan sebagai class_type. Jika tidak, itu akan diperlakukan sebagai batasan bukan null.

Contoh: Kelas berikut menunjukkan penggunaan berbagai argumen jenis terhadap batasan yang berbeda, menunjukkan peringatan yang mungkin dikeluarkan oleh kompilator.

#nullable enable
public class C { }
public class A<T> where T : notnull { }
public class B1<T> where T : C { }
public class B2<T> where T : C? { }
class Test
{
    static void M()
    {
        // nonnull constraint allows nonnullable struct type argument
        A<int> x1;
        // possible warning: nonnull constraint prohibits nullable struct type argument
        A<int?> x2;
        // nonnull constraint allows nonnullable class type argument
        A<C> x3;
        // possible warning: nonnull constraint prohibits nullable class type argument
        A<C?> x4;
        // nonnullable base class requirement allows nonnullable class type argument
        B1<C> x5;
        // possible warning: nonnullable base class requirement prohibits nullable class type argument
        B1<C?> x6;
        // nullable base class requirement allows nonnullable class type argument
        B2<C> x7;
        // nullable base class requirement allows nullable class type argument
        B2<C?> x8;
    }
}

Batasan jenis nilai menentukan bahwa argumen jenis yang digunakan untuk parameter jenis harus merupakan jenis nilai yang tidak dapat diubah ke null. Semua jenis struct yang tidak dapat diubah ke null, jenis enum, dan parameter jenis yang memiliki batasan jenis nilai memenuhi batasan ini. Perhatikan bahwa meskipun diklasifikasikan sebagai jenis nilai, jenis nilai yang dapat diubah ke null (§8.3.12) tidak memenuhi batasan jenis nilai. Parameter jenis yang memiliki batasan jenis nilai tidak akan juga memiliki constructor_constraint, meskipun dapat digunakan sebagai argumen jenis untuk parameter jenis lain dengan constructor_constraint.

Catatan: System.Nullable<T> jenis menetapkan batasan tipe nilai yang tidak boleh null untuk T. Dengan demikian, jenis formulir T?? yang dibuat secara rekursif dan Nullable<Nullable<T>> dilarang. catatan akhir

Batasan jenis yang tidak dikelola menentukan bahwa argumen jenis yang digunakan untuk parameter jenis harus merupakan jenis tidak terkelola yang tidak dapat diubah ke null (§8,8).

Karena unmanaged bukan kata kunci, dalam primary_constraint batasan yang tidak dikelola selalu ambigu secara sintetis dengan class_type. Untuk alasan kompatibilitas, jika pencarian nama (§12.8.4) dari namanya berhasil, nama unmanaged tersebut akan diperlakukan sebagai class_type. Jika tidak, itu diperlakukan sebagai kendala tidak terkelola.

Jenis penunjuk tidak pernah diizinkan untuk menjadi argumen jenis, dan tidak memenuhi batasan jenis apa pun, bahkan tanpa pengelolaan, meskipun merupakan jenis yang tidak dikelola.

Jika batasan adalah jenis kelas, jenis antarmuka, atau parameter jenis, jenis tersebut menentukan "jenis dasar" minimal yang harus didukung oleh setiap argumen jenis yang digunakan untuk parameter jenis tersebut. Setiap kali jenis yang dibangun atau metode generik digunakan, argumen jenis diperiksa terhadap batasan pada parameter jenis pada waktu kompilasi. Argumen jenis yang disediakan harus memenuhi kondisi yang dijelaskan dalam §8.4.5.

Batasan class_type harus memenuhi aturan berikut:

  • Tipe tersebut harus berupa tipe kelas.
  • Tipe tidak boleh sealed.
  • Jenisnya tidak boleh salah satu dari jenis berikut: System.Array atau System.ValueType.
  • Tipe tidak boleh object.
  • Paling banyak satu batasan untuk parameter jenis tertentu mungkin merupakan jenis kelas.

Jenis yang ditentukan sebagai batasan interface_type harus memenuhi aturan berikut:

  • Jenisnya adalah jenis antarmuka.
  • Jenis tidak boleh ditentukan lebih dari sekali dalam klausul tertentu where .

Dalam kedua kasus, batasan dapat melibatkan salah satu parameter jenis dari jenis atau deklarasi metode terkait sebagai bagian dari jenis yang dibangun, dan dapat melibatkan jenis yang dideklarasikan.

Setiap kelas atau jenis antarmuka yang ditentukan sebagai batasan parameter jenis harus setidaknya dapat diakses (§7.5.5) sebagai jenis atau metode generik yang dideklarasikan.

Jenis yang ditentukan sebagai batasan type_parameter harus memenuhi aturan berikut:

  • Tipe harus menjadi parameter tipe.
  • Jenis tidak boleh ditentukan lebih dari sekali dalam klausul tertentu where .

Selain itu, tidak boleh ada siklus dalam grafik dependensi parameter jenis, di mana dependensi adalah hubungan transitif yang ditentukan oleh:

  • Jika parameter jenis T digunakan sebagai batasan untuk parameter jenis S, makaS bergantung padaT.
  • Jika parameter S jenis bergantung pada parameter T jenis dan T bergantung pada parameter U jenis, maka Sbergantung padaU.

Mengingat hubungan ini, terjadi kesalahan pada saat kompilasi jika parameter jenis bergantung pada dirinya sendiri (secara langsung atau tidak langsung).

Batasan apa pun harus konsisten di antara parameter jenis dependen. Jika parameter S jenis tergantung pada parameter T jenis, maka:

  • T tidak boleh memiliki batasan jenis nilai. Jika tidak, T secara efektif disegel sehingga S akan dipaksa untuk menjadi jenis yang sama dengan T, menghilangkan kebutuhan untuk dua parameter jenis.
  • Jika S memiliki batasan jenis nilai maka T tidak akan memiliki batasan class_type .
  • Jika memiliki batasan tipe kelas dan memiliki batasan tipe kelas , maka harus ada konversi identitas atau konversi referensi implisit dari ke atau konversi referensi implisit dari ke .
  • Jika S juga bergantung pada parameter jenis U dan U memiliki kendala class_typeA dan T memiliki kendala class_typeB maka harus ada konversi identitas atau konversi referensi implisit dari A ke B atau konversi referensi implisit dari B ke A.

Ini berlaku untuk S memiliki batasan jenis nilai dan T memiliki batasan jenis referensi. Secara efektif ini membatasi T ke jenis System.Object, System.ValueType, System.Enum, dan jenis antarmuka apa pun.

where Jika klausul untuk parameter jenis menyertakan batasan konstruktor (yang memiliki formulir new()), dimungkinkan untuk menggunakan new operator untuk membuat instans jenis (§12.8.17.2). Argumen jenis apa pun yang digunakan untuk parameter jenis dengan batasan konstruktor adalah jenis nilai, kelas non-abstrak yang memiliki konstruktor tanpa parameter publik, atau parameter jenis yang memiliki batasan jenis nilai atau batasan konstruktor.

Ini adalah kesalahan waktu kompilasi untuk type_parameter_constraints yang memiliki primary_constraint dari struct atau unmanaged untuk juga memiliki constructor_constraint.

Contoh: Berikut ini adalah contoh batasan:

interface IPrintable
{
    void Print();
}

interface IComparable<T>
{
    int CompareTo(T value);
}

interface IKeyProvider<T>
{
    T GetKey();
}

class Printer<T> where T : IPrintable {...}
class SortedList<T> where T : IComparable<T> {...}

class Dictionary<K,V>
    where K : IComparable<K>
    where V : IPrintable, IKeyProvider<K>, new()
{
    ...
}

Contoh berikut salah karena menyebabkan siklus pada grafik ketergantungan parameter tipe.

class Circular<S,T>
    where S: T
    where T: S // Error, circularity in dependency graph
{
    ...
}

Contoh berikut mengilustrasikan situasi tambahan yang tidak valid:

class Sealed<S,T>
    where S : T
    where T : struct // Error, `T` is sealed
{
    ...
}

class A {...}
class B {...}

class Incompat<S,T>
    where S : A, T
    where T : B // Error, incompatible class-type constraints
{
    ...
}

class StructWithClass<S,T,U>
    where S : struct, T
    where T : U
    where U : A // Error, A incompatible with struct
{
    ...
}

contoh akhir

Penghapusan dinamis dari tipe C adalah tipe Cₓ yang dibangun sebagai berikut:

  • Jika C adalah jenis Outer.Inner berlapis maka Cₓ adalah jenis Outerₓ.Innerₓberlapis .
  • Jika CCₓ adalah jenis konstruksi G<A¹, ..., Aⁿ> dengan argumen jenis A¹, ..., Aⁿ, maka Cₓ adalah jenis konstruksi G<A¹ₓ, ..., Aⁿₓ>.
  • Jika C adalah jenis E[] array, maka Cₓ adalah jenis Eₓ[]array .
  • Jika C dinamis maka Cₓ adalah object.
  • Bila tidak, maka Cₓ adalah C.

Kelas dasar yang efektif dari jenis parameter didefinisikan sebagai berikut:

Biarkan R menjadi sekumpulan jenis seperti itu:

  • Untuk setiap batasan T yang merupakan parameter jenis, R berisi kelas dasar yang efektif.
  • Untuk setiap batasan T yang merupakan jenis struct, R berisi System.ValueType.
  • Untuk setiap batasan T yang merupakan jenis enumerasi, R berisi System.Enum.
  • Untuk setiap batasan dari T yang merupakan tipe delegasi, R mengandung penghapusan dinamisnya.
  • Untuk setiap batasan T yang merupakan jenis array, R berisi System.Array.
  • Untuk setiap batasan T yang merupakan jenis kelas, R berisi penghapusan dinamisnya.

Kemudian

  • Jika T memiliki batasan jenis nilai, kelas dasarnya yang efektif adalah System.ValueType.
  • Jika tidak, jika R kosong maka kelas dasar yang efektif adalah object.
  • Selain itu, kelas dasar yang efektif T adalah jenis yang paling banyak mencakup (§10.5.3) dari kumpulan R. Jika set tidak memiliki jenis yang tercakup, kelas dasar efektif dari T adalah object. Aturan konsistensi memastikan bahwa jenis yang mencakup paling luas ada.

Jika parameter jenis adalah parameter jenis metode yang batasannya diwarisi dari metode dasar, kelas dasar yang efektif dihitung setelah penggantian jenis.

Aturan ini memastikan bahwa kelas dasar yang efektif selalu class_type.

Set antarmuka yang efektif dari jenis parameter T didefinisikan sebagai berikut:

  • Jika T tidak memunyai secondary_constraints, set antarmuka yang efektifnya kosong.
  • Jika T memiliki keterbatasan interface_type tetapi tidak ada keterbatasan type_parameter, set antarmuka yang efektif adalah serangkaian penghapusan dinamis dari keterbatasan interface_type.
  • Jika T tidak memiliki batasan interface_type tetapi memiliki batasan type_parameter, set antarmuka yang efektif adalah penyatuan set antarmuka yang efektif dari batasan type_parameter nya.
  • Jika T memiliki batasan interface_type dan batasan type_parameter, set antarmuka yang efektif adalah penyatuan kumpulan penghapusan dinamis batasan interface_type dan set antarmuka yang efektif dari batasan type_parameter nya.

Parameter jenis diketahui sebagai jenis referensi jika memiliki batasan jenis referensi atau kelas dasar yang efektif bukan object atau System.ValueType. Parameter jenis diketahui sebagai jenis referensi yang tidak dapat diubah ke null jika dikenal sebagai jenis referensi dan memiliki batasan jenis referensi yang tidak dapat diubah ke null.

Nilai parameter tipe yang dibatasi dapat digunakan untuk mengakses anggota instance yang tersirat oleh pembatasan.

Contoh: Dalam hal berikut:

interface IPrintable
{
    void Print();
}

class Printer<T> where T : IPrintable
{
    void PrintOne(T x) => x.Print();
}

metode pada IPrintable dapat dipanggil langsung di x karena T dibatasi untuk selalu mengimplementasikan IPrintable.

contoh akhir

Ketika deklarasi jenis generik parsial mencakup batasan, batasan akan setuju dengan semua bagian lain yang mencakup batasan. Secara khusus, setiap bagian yang mencakup batasan harus memiliki batasan untuk set parameter jenis yang sama, dan untuk setiap parameter jenis, set batasan primer, sekunder, dan konstruktor harus setara. Dua set batasan setara jika berisi anggota yang sama. Jika tidak ada bagian dari jenis generik parsial yang menentukan batasan parameter jenis, parameter jenis dianggap tidak dibatasi.

Contoh:

partial class Map<K,V>
    where K : IComparable<K>
    where V : IKeyProvider<K>, new()
{
    ...
}

partial class Map<K,V>
    where V : IKeyProvider<K>, new()
    where K : IComparable<K>
{
    ...
}

partial class Map<K,V>
{
    ...
}

benar karena bagian-bagian yang mencakup batasan (dua bagian pertama) secara efektif menentukan set batasan primer, sekunder, dan konstruktor yang sama untuk sekumpulan parameter tipe yang sama, masing-masing.

contoh akhir

15.2.6 Badan Kelas

Class_body kelas menentukan anggota kelas tersebut.

class_body
    : '{' class_member_declaration* '}'
    ;

15.2.7 Deklarasi tipe parsial

Pengubah partial digunakan saat menentukan jenis kelas, struct, atau antarmuka di beberapa bagian. Pengubah partial adalah kata kunci kontekstual (§6.4.4) dan memiliki arti khusus segera sebelum kata kunci class, struct, dan interface. (Jenis parsial mungkin berisi deklarasi metode parsial (§15.6.9).

Setiap bagian dari deklarasi jenis parsial harus mencakup pengubah partial dan harus dinyatakan dalam namespace yang sama atau berisi jenis sebagai bagian lain. Pengubah partial menunjukkan bahwa bagian tambahan dari deklarasi jenis mungkin ada di tempat lain, tetapi keberadaan bagian tambahan tersebut bukan persyaratan; itu berlaku untuk satu-satunya deklarasi jenis untuk menyertakan pengubah partial . Ini hanya berlaku untuk satu deklarasi jenis parsial untuk menyertakan kelas dasar atau antarmuka yang diimplementasikan. Namun, semua deklarasi kelas dasar atau antarmuka yang diimplementasikan harus sepenuhnya cocok, termasuk dalam hal nullability dari argumen-argumen tipe yang ditentukan.

Semua bagian dari jenis parsial harus dikompilasi bersama-sama sehingga bagian-bagian dapat digabungkan pada waktu kompilasi. Jenis parsial khususnya tidak memungkinkan jenis yang sudah dikompilasi diperluas.

Jenis berlapis dapat dideklarasikan dalam beberapa bagian dengan menggunakan pengubah partial. Biasanya, tipe penampung juga dideklarasikan menggunakan partial, dan setiap bagian dari tipe bersarang dideklarasikan di bagian yang berbeda dari tipe penampung.

Contoh: Kelas parsial berikut diimplementasikan dalam dua bagian, yang berada di unit kompilasi yang berbeda. Bagian pertama adalah mesin yang dihasilkan oleh alat pemetaan database sementara bagian kedua ditulis secara manual:

public partial class Customer
{
    private int id;
    private string name;
    private string address;
    private List<Order> orders;

    public Customer()
    {
        ...
    }
}

// File: Customer2.cs
public partial class Customer
{
    public void SubmitOrder(Order orderSubmitted) => orders.Add(orderSubmitted);

    public bool HasOutstandingOrders() => orders.Count > 0;
}

Ketika dua bagian di atas dikompilasi bersama-sama, kode yang dihasilkan bertingkah seolah-olah kelas telah ditulis sebagai satu unit, sebagai berikut:

public class Customer
{
    private int id;
    private string name;
    private string address;
    private List<Order> orders;

    public Customer()
    {
        ...
    }

    public void SubmitOrder(Order orderSubmitted) => orders.Add(orderSubmitted);

    public bool HasOutstandingOrders() => orders.Count > 0;
}

contoh akhir

Penanganan atribut yang ditentukan pada parameter jenis atau jenis tipe dari bagian berbeda dari deklarasi tipe parsial dibahas dalam §22.3.

15.3 Anggota kelas

15.3.1 Umum

Anggota kelas terdiri dari anggota yang diperkenalkan oleh class_member_declaration dan anggota yang diwarisi dari kelas dasar langsung.

class_member_declaration
    : constant_declaration
    | field_declaration
    | method_declaration
    | property_declaration
    | event_declaration
    | indexer_declaration
    | operator_declaration
    | constructor_declaration
    | finalizer_declaration
    | static_constructor_declaration
    | type_declaration
    ;

Anggota kelas dibagi menjadi kategori berikut:

  • Konstanta, yang mewakili nilai konstanta yang terkait dengan kelas (§15,4).
  • Bidang, yang merupakan variabel kelas (§15,5).
  • Metode, yang mengimplementasikan komputasi dan tindakan yang dapat dilakukan oleh kelas (§15.6).
  • Properti, yang menentukan karakteristik bernama dan tindakan yang terkait dengan membaca dan menulis karakteristik tersebut (§15.7).
  • Peristiwa, yang menentukan pemberitahuan yang dapat dihasilkan oleh kelas (§15,8).
  • Pengindeks, yang mengizinkan instans kelas untuk diindeks dengan cara yang sama (secara sintetis) sebagai array (§15,9).
  • Operator, yang menentukan operator ekspresi yang dapat diterapkan ke instans kelas (§15.10).
  • Konstruktor instans, yang mengimplementasikan tindakan yang diperlukan untuk menginisialisasi instans kelas (§15.11)
  • Pemusnah, yang melaksanakan tindakan yang harus dilakukan sebelum instance kelas dibuang secara permanen (§15.13).
  • Konstruktor statis, yang mengimplementasikan tindakan yang diperlukan untuk menginisialisasi kelas itu sendiri (§15.12).
  • Jenis, yang mewakili jenis yang bersifat lokal ke kelas (§14,7).

class_declaration membuat ruang deklarasi baru (§7.3), dan type_parameterdan class_member_declarationyang segera dimuat oleh class_declaration memperkenalkan anggota baru ke dalam ruang deklarasi ini. Aturan berikut berlaku untuk class_member_declaration:

  • Konstruktor instans, finalizer, dan konstruktor statis harus memiliki nama yang sama dengan kelas pembungkus langsung. Semua anggota lain harus memiliki nama yang berbeda dengan nama kelas pengapit langsung.

  • Nama parameter jenis dalam type_parameter_list deklarasi kelas harus berbeda dari nama semua parameter jenis lainnya dalam type_parameter_list yang sama dan harus berbeda dari nama kelas dan nama semua anggota kelas.

  • Nama jenis harus berbeda dari nama semua anggota non-jenis yang dideklarasikan dalam kelas yang sama. Jika dua deklarasi jenis atau lebih memiliki nama yang sepenuhnya memenuhi syarat yang sama, deklarasi harus memiliki partial pengubah (§15.2.7) dan deklarasi ini digabungkan untuk menentukan satu jenis.

Catatan: Karena nama deklarasi jenis yang sepenuhnya memenuhi syarat mengodekan jumlah parameter jenis, dua jenis yang berbeda dapat memiliki nama yang sama selama memiliki jumlah parameter jenis yang berbeda. catatan akhir

  • Nama konstanta, bidang, properti, atau peristiwa akan berbeda dari nama semua anggota lain yang dideklarasikan dalam kelas yang sama.

  • Nama metode akan berbeda dari nama semua non-metode lain yang dideklarasikan dalam kelas yang sama. Selain itu, tanda tangan (§7,6) dari metode harus berbeda dari tanda tangan semua metode lain yang dinyatakan dalam kelas yang sama, dan dua metode yang dinyatakan dalam kelas yang sama tidak akan memiliki tanda tangan yang hanya berbeda dengan in, , outdan ref.

  • Tanda tangan konstruktor instans akan berbeda dari tanda tangan semua konstruktor instans lain yang dideklarasikan dalam kelas yang sama, dan dua konstruktor yang dinyatakan dalam kelas yang sama tidak boleh memiliki tanda tangan yang hanya berbeda oleh ref dan out.

  • Tanda tangan pengindeks akan berbeda dari tanda tangan semua pengindeks lain yang dideklarasikan dalam kelas yang sama.

  • Tanda tangan operator akan berbeda dari tanda tangan semua operator lain yang dinyatakan di kelas yang sama.

Anggota kelas yang diwariskan (§15.3.4) bukan bagian dari ruang deklarasi kelas.

Catatan: Dengan demikian, kelas turunan diizinkan untuk mendeklarasikan anggota dengan nama atau tanda tangan yang sama dengan anggota yang diwariskan (yang berlaku menyembunyikan anggota yang diwariskan). catatan akhir

Kumpulan anggota dari suatu tipe yang dideklarasikan dalam beberapa bagian (§15.2.7) adalah gabungan dari anggota yang dideklarasikan di setiap bagian. Badan dari semua bagian deklarasi tipe memiliki ruang deklarasi yang sama (§7,3), dan cakupan setiap anggota (§7,7) meluas ke badan dari semua bagian. Ruang lingkup aksesibilitas dari anggota mana pun selalu menyertakan semua bagian dari tipe yang menyelubungi; anggota privat yang dideklarasikan dalam satu bagian dapat diakses secara bebas dari bagian lain. Ini adalah kesalahan pada waktu kompilasi untuk mendeklarasikan anggota yang sama di lebih dari satu bagian tipe, kecuali anggota tersebut memiliki pengubah partial.

Contoh:

partial class A
{
    int x;                   // Error, cannot declare x more than once
    partial void M();        // Ok, defining partial method declaration

    partial class Inner      // Ok, Inner is a partial type
    {
        int y;
    }
}

partial class A
{
    int x;                   // Error, cannot declare x more than once
    partial void M() { }     // Ok, implementing partial method declaration

    partial class Inner      // Ok, Inner is a partial type
    {
        int z;
    }
}

contoh akhir

Urutan inisialisasi bidang dapat signifikan dalam kode C#, dan beberapa jaminan disediakan, seperti yang didefinisikan dalam §15.5.6.1. Jika tidak, urutan anggota dalam suatu jenis jarang signifikan, tetapi mungkin signifikan saat berinteraksi dengan bahasa dan lingkungan lain. Dalam kasus ini, urutan anggota dalam jenis yang dideklarasikan dalam beberapa bagian tidak terdefinisi.

15.3.2 Jenis instance

Setiap deklarasi kelas memiliki tipe instans terkait. Untuk deklarasi kelas generik, jenis instans dibentuk dengan membuat jenis yang dibangun (§8,4) dari deklarasi jenis, dengan setiap argumen jenis yang disediakan menjadi parameter jenis yang sesuai. Karena tipe instance menggunakan parameter tipe, tipe tersebut hanya dapat digunakan di mana parameter tipe berada dalam cakupan; yaitu, di dalam deklarasi kelas. Jenis instans adalah jenis this untuk kode yang ditulis di dalam deklarasi kelas. Untuk kelas non-generik, jenis instans hanyalah kelas yang dideklarasikan.

Contoh: Berikut ini menunjukkan beberapa deklarasi kelas bersama dengan jenis instansnya:

class A<T>             // instance type: A<T>
{
    class B {}         // instance type: A<T>.B
    class C<U> {}      // instance type: A<T>.C<U>
}
class D {}             // instance type: D

contoh akhir

15.3.3 Anggota jenis terstruktur

Anggota yang bukan turunan dari tipe yang dibentuk diperoleh dengan mengganti, untuk setiap type_parameter dalam deklarasi anggota, type_argument yang sesuai dari tipe yang dibentuk. Proses penggantian didasarkan pada arti semantik dari deklarasi jenis, dan bukan hanya penggantian tekstual.

Contoh: Diberikan deklarasi kelas generik

class Gen<T,U>
{
    public T[,] a;
    public void G(int i, T t, Gen<U,T> gt) {...}
    public U Prop { get {...} set {...} }
    public int H(double d) {...}
}

jenis Gen<int[],IComparable<string>> yang dibuat memiliki anggota berikut:

public int[,][] a;
public void G(int i, int[] t, Gen<IComparable<string>,int[]> gt) {...}
public IComparable<string> Prop { get {...} set {...} }
public int H(double d) {...}

Jenis anggota a dalam deklarasi kelas generik Gen adalah "array dua dimensi dari T", sehingga jenis anggota a dalam jenis yang dibangun di atasnya adalah "array dua dimensi dari array berdimensi tunggal dari int", atau int[,][].

contoh akhir

Dalam anggota fungsi instans, jenis this merupakan jenis instans (§15.3.2) dari deklarasi yang mencakup.

Semua anggota kelas generik dapat menggunakan parameter jenis dari kelas penutup apa pun, baik secara langsung atau sebagai bagian dari jenis yang dibangun. Ketika tipe yang dibangun tertutup tertentu (§8.4.3) digunakan pada waktu proses, setiap penggunaan parameter tipe digantikan dengan argumen tipe yang disediakan untuk tipe yang dibangun tersebut.

Contoh:

class C<V>
{
    public V f1;
    public C<V> f2;

    public C(V x)
    {
        this.f1 = x;
        this.f2 = this;
    }
}

class Application
{
    static void Main()
    {
        C<int> x1 = new C<int>(1);
        Console.WriteLine(x1.f1);              // Prints 1

        C<double> x2 = new C<double>(3.1415);
        Console.WriteLine(x2.f1);              // Prints 3.1415
    }
}

contoh akhir

15.3.4 Pewarisan

Sebuah kelas mewarisi anggota dari kelas dasar langsungnya. Pewarisan berarti bahwa sebuah kelas secara implisit mengandung semua anggota dari kelas dasar langsungnya, kecuali untuk konstruktor instance, finalizer, dan konstruktor statik dari kelas dasar. Beberapa aspek penting dari warisan adalah:

  • Warisan bersifat transitif. Jika C berasal dari B, dan B berasal dari A, maka C mewarisi anggota yang dinyatakan dalam B serta anggota yang dinyatakan dalam A.

  • Kelas turunan memperluas kelas dasar langsungnya. Kelas turunan dapat menambahkan anggota baru ke anggota yang diwariskannya, tetapi tidak dapat menghapus definisi anggota yang diwariskan.

  • Konstruktor instans, finalizer, dan konstruktor statis tidak diwariskan, tetapi semua anggota lainnya, tanpa memandang aksesibilitas yang dinyatakan (§7,5). Namun, tergantung pada aksesibilitas yang dinyatakan, anggota yang diwariskan mungkin tidak dapat diakses di kelas turunan.

  • Kelas turunan dapat menyembunyikan (§7.7.2.3) anggota yang diwariskan dengan mendeklarasikan anggota baru dengan nama atau tanda tangan yang sama. Namun, menyembunyikan anggota yang diwariskan tidak menghapus anggota tersebut—itu hanya membuat anggota itu tidak dapat diakses langsung melalui kelas turunan.

  • Instans kelas berisi sekumpulan semua kolom instans yang dideklarasikan di kelas dan kelas dasarnya, dan terdapat konversi implisit (§10.2.8) dari tipe kelas turunan ke tipe kelas dasarnya. Dengan demikian, referensi ke instans beberapa kelas turunan dapat diperlakukan sebagai referensi ke instans dari salah satu kelas dasarnya.

  • Kelas dapat mendeklarasikan metode virtual, properti, pengindeks, dan peristiwa, dan kelas turunan dapat mengambil alih implementasi anggota fungsi ini. Ini memungkinkan kelas untuk menunjukkan perilaku polimorfik di mana tindakan yang dilakukan oleh pemanggilan anggota fungsi bervariasi tergantung pada jenis run-time instans tempat anggota fungsi tersebut dipanggil.

Anggota yang diwariskan dari jenis kelas yang dibangun adalah anggota dari jenis kelas dasar langsung (§15.2.4.2), yang ditemukan dengan mengganti argumen tipe dari jenis yang dibangun untuk setiap kemunculan parameter tipe yang sesuai dalam base_class_specification. Anggota ini kemudian diubah dengan mengganti, untuk setiap type_parameter dalam deklarasi anggota, type_argument yang sesuai dari base_class_specification.

Contoh:

class B<U>
{
    public U F(long index) {...}
}

class D<T> : B<T[]>
{
    public T G(string s) {...}
}

Dalam kode di atas, tipe D<int> yang dibangun memiliki anggota publik intG(string s) yang tidak diwariskan, yang diperoleh dengan mengganti argumen tipe int untuk parameter tipe T. D<int> juga memiliki anggota yang diwariskan dari deklarasi kelas B. Anggota yang diwariskan ini ditentukan dengan terlebih dahulu menentukan jenis B<int[]>D<int> kelas dasar dengan menggantikan intT dalam spesifikasi B<T[]>kelas dasar . Kemudian, sebagai argumen jenis ke B, int[] diganti dengan U dalam public U F(long index), menghasilkan anggota public int[] F(long index)yang diwariskan .

contoh akhir

15.3.5 Pengubah baru

class_member_declaration diizinkan untuk mendeklarasikan anggota dengan nama atau tanda tangan yang sama dengan anggota yang diwariskan. Ketika ini terjadi, anggota kelas turunan dikatakan menyembunyikan anggota kelas dasar. Lihat §7.7.2.3 untuk spesifikasi yang tepat saat anggota menyembunyikan anggota yang diwariskan.

Anggota M yang diwariskan dianggap tersedia jika M dapat diakses dan tidak ada anggota lain yang dapat diakses yang diwariskan N yang sudah menyembunyikan M. Secara implisit menyembunyikan anggota yang diwariskan tidak dianggap sebagai kesalahan, tetapi kompilator harus mengeluarkan peringatan kecuali deklarasi anggota kelas turunan mencakup pengubah new untuk secara eksplisit menunjukkan bahwa anggota turunan dimaksudkan untuk menyembunyikan anggota dasar. Jika satu atau beberapa bagian dari deklarasi parsial (§15.2.7) dari tipe bersarang menyertakan pengubah new, tidak ada peringatan yang dikeluarkan jika tipe bersarang tersebut menyembunyikan anggota yang diwariskan yang tersedia.

Jika sebuah pengubah new disertakan dalam deklarasi yang tidak menyembunyikan anggota yang diwariskan yang tersedia, maka akan dikeluarkan peringatan terkait hal ini.

15.3.6 Pengubah akses

Deklarasi_anggota_kelas dapat memiliki salah satu jenis aksesibilitas yang dinyatakan yang diizinkan (§7.5.2): public, protected internal, protected, private protected, internal, atau private. Kecuali untuk kombinasi protected internal dan private protected, akan menghasilkan kesalahan waktu kompilasi jika menentukan lebih dari satu pengubah akses. Ketika class_member_declaration tidak menyertakan pengubah akses apa pun, private diasumsikan.

15.3.7 Jenis konstituen

Jenis yang digunakan dalam deklarasi anggota disebut jenis konstituen anggota tersebut. Jenis konstituen yang mungkin adalah jenis konstanta, bidang, properti, peristiwa, atau pengindeks, jenis pengembalian metode atau operator, dan jenis parameter metode, pengindeks, operator, atau konstruktor instans. Jenis konstituen dari anggota harus setidaknya sama mudah diaksesnya dengan anggota itu sendiri (§7.5.5).

15.3.8 Anggota statis dan instans

Anggota kelas adalah anggota statis atau anggota instans.

Catatan: Secara umum, berguna untuk menganggap anggota statis sebagai milik kelas dan anggota instans sebagai milik objek (instans kelas). catatan akhir

Saat bidang, metode, properti, peristiwa, operator, atau deklarasi konstruktor menyertakan static pengubah, bidang mendeklarasikan anggota statis. Selain itu, deklarasi konstanta atau jenis secara implisit menyatakan anggota statis. Anggota statis memiliki karakteristik berikut:

  • Ketika anggota M statis dirujuk dalam member_access (§12.8.7) formulir E.M, E harus menunjukkan jenis yang memiliki anggota M. Ini adalah kesalahan waktu kompilasi jika E digunakan untuk menunjukkan sebuah instans.
  • Bidang statis di kelas non-generik mengidentifikasi persis satu lokasi penyimpanan. Tidak peduli berapa banyak instans kelas non-generik yang dibuat, hanya ada satu salinan bidang statis. Setiap jenis konstruksi tertutup yang berbeda (§8.4.3) memiliki sekumpulan bidang statisnya sendiri, terlepas dari jumlah instans jenis konstruksi tertutup.
  • Anggota fungsi statis (metode, properti, peristiwa, operator, atau konstruktor) tidak beroperasi pada instans tertentu, dan merupakan kesalahan saat kompilasi untuk merujuk 'ini' dalam anggota fungsi tersebut.

Ketika bidang, metode, properti, peristiwa, pengindeks, konstruktor, atau deklarasi finalizer tidak menyertakan pengubah statis, itu mendeklarasikan anggota instans. (Anggota instans terkadang disebut anggota non-statis.) Anggota instans memiliki karakteristik berikut:

  • Ketika instans anggota M dirujuk dalam bentuk member_access (§12.8.7), E.ME harus menggambarkan sebuah instans dari jenis yang memiliki anggota M. Ini adalah kesalahan waktu pengikatan bagi E untuk menunjukkan jenis.
  • Setiap instance dari sebuah kelas memiliki satu set terpisah dari semua field instance kelas tersebut.
  • Anggota fungsi instans (metode, properti, pengindeks, konstruktor instans, atau finalizer) beroperasi pada instans kelas tertentu, dan instans ini dapat diakses sebagai this (§12.8.14).

Contoh: Contoh berikut mengilustrasikan aturan untuk mengakses anggota statis dan instans:

class Test
{
    int x;
    static int y;
    void F()
    {
        x = 1;               // Ok, same as this.x = 1
        y = 1;               // Ok, same as Test.y = 1
    }

    static void G()
    {
        x = 1;               // Error, cannot access this.x
        y = 1;               // Ok, same as Test.y = 1
    }

    static void Main()
    {
        Test t = new Test();
        t.x = 1;       // Ok
        t.y = 1;       // Error, cannot access static member through instance
        Test.x = 1;    // Error, cannot access instance member through type
        Test.y = 1;    // Ok
    }
}

Metode F ini menunjukkan bahwa dalam fungsi anggota instans, simple_name (§12.8.4) dapat digunakan untuk mengakses anggota instans dan anggota statis. Metode G ini menunjukkan bahwa dalam anggota fungsi statis, merupakan kesalahan saat waktu kompilasi untuk mengakses anggota instans melalui simple_name. Metode ini Main menunjukkan bahwa dalam member_access (§12.8.7), anggota instans harus diakses melalui instans, dan anggota statis harus diakses melalui tipe.

contoh akhir

15.3.9 Jenis berlapis

15.3.9.1 Umum

Jenis yang dideklarasikan dalam kelas atau struktur disebut jenis berlapis. Jenis yang dideklarasikan dalam unit kompilasi atau namespace disebut tipe non-sarang.

Contoh: Dalam contoh berikut:

class A
{
    class B
    {
        static void F()
        {
            Console.WriteLine("A.B.F");
        }
    }
}

kelas B adalah jenis berlapis karena dideklarasikan dalam kelas A, dan kelas A adalah jenis yang tidak berlapis karena dideklarasikan dalam unit kompilasi.

contoh akhir

15.3.9.2 Nama yang sepenuhnya memenuhi syarat

Nama yang sepenuhnya memenuhi syarat (§7.8.3) untuk deklarasi jenis bersarang adalah S.N, di mana S adalah nama yang sepenuhnya memenuhi syarat dari deklarasi jenis di mana jenis N dideklarasikan dan N adalah nama yang tidak memenuhi syarat (§7.8.2) dari deklarasi jenis bersarang (termasuk spesifikasi dimensi generik (§12.8.18)).

15.3.9.3 Menyatakan aksesibilitas

Jenis yang tidak berlapis dapat mendeklarasikan aksesibilitas sebagai public atau internal dan secara default internal adalah aksesibilitas yang dideklarasikan. Jenis berlapis juga dapat memiliki bentuk aksesibilitas yang dinyatakan ini, ditambah satu atau beberapa bentuk tambahan aksesibilitas yang dinyatakan, tergantung pada apakah jenis yang berisi adalah kelas atau struktur:

  • Tipe bersarang yang dideklarasikan dalam kelas dapat memiliki semua jenis aksesibilitas yang diizinkan dan, seperti anggota kelas lainnya, secara default adalah private aksesibilitas yang telah dideklarasikan.
  • Tipe bersarang yang dideklarasikan dalam struktur dapat memiliki satu dari tiga bentuk aksesibilitas yang dinyatakan (public, internal, atau private), dan, seperti anggota struct lainnya, default ke aksesibilitas yang dinyatakan private.

Contoh: Contoh

public class List
{
    // Private data structure
    private class Node
    {
        public object Data;
        public Node? Next;

        public Node(object data, Node? next)
        {
            this.Data = data;
            this.Next = next;
        }
    }

    private Node? first = null;
    private Node? last = null;

    // Public interface
    public void AddToFront(object o) {...}
    public void AddToBack(object o) {...}
    public object RemoveFromFront() {...}
    public object RemoveFromBack() {...}
    public int Count { get {...} }
}

Mendeklarasikan kelas bersarang privat Node.

contoh akhir

15.3.9.4 Menyembunyikan

Jenis berlapis dapat menyembunyikan (§7.7.2.2) anggota dasar. Pengubah new (§15.3.5) diizinkan pada deklarasi jenis berlapis sehingga persembunyian dapat diekspresikan secara eksplisit.

Contoh: Contoh

class Base
{
    public static void M()
    {
        Console.WriteLine("Base.M");
    }
}

class Derived: Base
{
    public new class M
    {
        public static void F()
        {
            Console.WriteLine("Derived.M.F");
        }
    }
}

class Test
{
    static void Main()
    {
        Derived.M.F();
    }
}

menunjukkan kelas M berlapis yang menyembunyikan metode M yang ditentukan dalam Base.

contoh akhir

15.3.9.5 akses ini

Jenis bersarang dan tipe yang mengandung tidak memiliki hubungan khusus sehubungan dengan this_access (§12.8.14). Secara khusus, this dalam tipe bersarang tidak dapat digunakan untuk merujuk ke anggota instance dari tipe yang mengandung. Dalam kasus di mana tipe bertingkat memerlukan akses ke anggota instans dari tipe yang berisi, akses dapat disediakan dengan memberikan this yang merupakan instans dari tipe yang berisi sebagai argumen konstruktor untuk tipe bertingkat.

Contoh: Contoh berikut

class C
{
    int i = 123;
    public void F()
    {
        Nested n = new Nested(this);
        n.G();
    }

    public class Nested
    {
        C this_c;

        public Nested(C c)
        {
            this_c = c;
        }

        public void G()
        {
            Console.WriteLine(this_c.i);
        }
    }
}

class Test
{
    static void Main()
    {
        C c = new C();
        c.F();
    }
}

menunjukkan teknik ini Instans C membuat instans Nested, dan meneruskan this-nya sendiri ke konstruktor Nested untuk menyediakan akses berikutnya ke anggota instans C.

contoh akhir

15.3.9.6 Akses ke anggota privat dan terlindungi dari tipe yang berisi

Jenis bersarang memiliki akses ke semua anggota yang dapat diakses oleh tipe penampungnya, termasuk anggota tipe penampung yang telah dideklarasikan tingkat aksesibilitasnya dengan private dan protected.

Contoh: Contoh

class C
{
    private static void F() => Console.WriteLine("C.F");

    public class Nested
    {
        public static void G() => F();
    }
}

class Test
{
    static void Main() => C.Nested.G();
}

menunjukkan kelas C yang berisi kelas Nested tertanam. Dalam Nested, metode G memanggil metode F statis yang ditentukan dalam C, dan F memiliki aksesibilitas yang dinyatakan privat.

contoh akhir

Tipe bertingkat juga dapat mengakses anggota yang dilindungi yang ditetapkan dalam tipe dasar dari tipe yang memuatnya.

Contoh: Dalam kode berikut

class Base
{
    protected void F() => Console.WriteLine("Base.F");
}

class Derived: Base
{
    public class Nested
    {
        public void G()
        {
            Derived d = new Derived();
            d.F(); // ok
        }
    }
}

class Test
{
    static void Main()
    {
        Derived.Nested n = new Derived.Nested();
        n.G();
    }
}

kelas Derived.Nested berlapis mengakses metode F yang dilindungi yang ditentukan dalam Derivedkelas dasar, Base, dengan memanggil melalui instans Derived.

contoh akhir

15.3.9.7 Jenis berlapis di kelas generik

Deklarasi kelas generik mungkin berisi deklarasi jenis berlapis. Parameter tipe dari kelas pembungkus dapat digunakan di dalam tipe bersarang. Deklarasi jenis berlapis mungkin berisi parameter jenis tambahan yang hanya berlaku untuk jenis berlapis.

Setiap deklarasi jenis yang terkandung dalam deklarasi kelas generik secara implisit adalah deklarasi jenis generik. Ketika menulis referensi untuk tipe yang bersarang dalam tipe generik, tipe terbangun yang memuat, termasuk argumen tipenya, harus disebutkan namanya. Namun, dari dalam kelas luar, tipe bersarang dapat digunakan tanpa kualifikasi; tipe instans dari kelas luar mungkin digunakan secara implisit saat membangun tipe bersarang.

Contoh: Berikut ini menunjukkan tiga cara yang benar yang berbeda untuk merujuk ke jenis konstruksi yang dibuat dari Inner; dua cara pertama setara:

class Outer<T>
{
    class Inner<U>
    {
        public static void F(T t, U u) {...}
    }

    static void F(T t)
    {
        Outer<T>.Inner<string>.F(t, "abc");    // These two statements have
        Inner<string>.F(t, "abc");             // the same effect
        Outer<int>.Inner<string>.F(3, "abc");  // This type is different
        Outer.Inner<string>.F(t, "abc");       // Error, Outer needs type arg
    }
}

contoh akhir

Meskipun gaya pemrogramannya buruk, parameter jenis dalam jenis berlapis dapat menyembunyikan parameter anggota atau jenis yang telah dideklarasikan pada jenis luar.

Contoh:

class Outer<T>
{
    class Inner<T>                                  // Valid, hides Outer's T
    {
        public T t;                                 // Refers to Inner's T
    }
}

contoh akhir

15.3.10 Nama anggota yang dicadangkan

15.3.10.1 Umum

Untuk memfasilitasi implementasi run-time C# yang mendasarinya, untuk setiap deklarasi anggota sumber yang merupakan properti, peristiwa, atau pengindeks, implementasi harus mencadangkan dua tanda tangan metode berdasarkan jenis deklarasi anggota, namanya, dan jenisnya (§15.3.10.2, §15.3.10.3, §15.3.10.4). Ini adalah kesalahan waktu kompilasi bagi program untuk menyatakan anggota yang tanda tangannya cocok dengan tanda tangan yang dicadangkan oleh anggota yang dinyatakan dalam cakupan yang sama, bahkan jika implementasi run-time yang mendasarinya tidak menggunakan reservasi ini.

Nama yang dipesan tidak memperkenalkan deklarasi, sehingga mereka tidak berpartisipasi dalam pencarian anggota. Namun, tanda tangan metode yang terpelihara terkait dengan deklarasi ikut serta dalam pewarisan (§15.3.4), dan dapat disembunyikan dengan pengubah new (§15.3.5).

Catatan: Reservasi nama-nama ini melayani tiga tujuan:

  1. Untuk memungkinkan implementasi yang mendasarinya menggunakan pengidentifikasi biasa sebagai nama metode untuk mendapatkan atau mengatur akses ke fitur bahasa C#.
  2. Untuk memungkinkan bahasa lain beroperasi menggunakan pengidentifikasi biasa sebagai nama metode untuk mendapatkan atau mengatur akses ke fitur bahasa C#.
  3. Untuk membantu memastikan bahwa kode sumber yang diterima oleh satu kompiler yang mematuhi spesifikasi diterima oleh kompiler lain, dengan menjadikan konsistensi nama elemen yang sudah ditentukan di semua implementasi C#.

catatan akhir

Deklarasi finalizer (§15.13) juga menyebabkan tanda tangan dicadangkan (§15.3.10.5).

Nama tertentu dialokasikan untuk digunakan sebagai nama metode operator (§15.3.10.6).

15.3.10.2 Nama anggota yang diperuntukkan untuk properti

Untuk properti P (§15.7) dengan tipe T, tanda tangan berikut dicadangkan:

T get_P();
void set_P(T value);

Kedua penanda disediakan, bahkan jika properti hanya-baca atau hanya-tulis.

Contoh: Dalam kode berikut

class A
{
    public int P
    {
        get => 123;
    }
}

class B : A
{
    public new int get_P() => 456;

    public new void set_P(int value)
    {
    }
}

class Test
{
    static void Main()
    {
        B b = new B();
        A a = b;
        Console.WriteLine(a.P);
        Console.WriteLine(b.P);
        Console.WriteLine(b.get_P());
    }
}

Kelas A mendefinisikan properti baca-saja P, sehingga menjaga tanda tangan untuk metode get_P dan set_P. A class B diturunkan dari A dan menyembunyikan kedua signature yang dipesan ini. Contoh menghasilkan output:

123
123
456

contoh akhir

Nama-nama anggota yang dipersiapkan untuk acara

Untuk peristiwa E (§15.8) jenis T delegasi, tanda tangan berikut dicadangkan:

void add_E(T handler);
void remove_E(T handler);

15.3.10.4 Nama anggota yang dicadangkan untuk pengindeks

Untuk pengindeks (§15.9) dari tipe T dengan daftar parameter L, tanda tangan berikut dicadangkan:

T get_Item(L);
void set_Item(L, T value);

Kedua tanda tangan tetap dicadangkan, bahkan jika pengindeks bersifat baca-saja atau tulis-saja.

Selain itu, nama Item dicadangkan untuk anggota.

15.3.10.5 Nama anggota yang dicadangkan untuk pembersih akhir

Untuk kelas yang berisi finalizer (§15.13), signature berikut dicadangkan:

void Finalize();

15.3.10.6 Nama metode yang dicadangkan untuk operator

Nama metode berikut ini dicadangkan. Meskipun banyak yang memiliki operator yang sesuai dalam spesifikasi ini, beberapa dicadangkan untuk versi mendatang, sementara beberapa dicadangkan untuk interoperabilitas dengan bahasa lain.

Nama Metode Operator C# (Operator Pemrograman C#)
op_Addition + (biner)
op_AdditionAssignment (dicadangkan)
op_AddressOf (dicadangkan)
op_Assign (dicadangkan)
op_BitwiseAnd & (biner)
op_BitwiseAndAssignment (dicadangkan)
op_BitwiseOr \|
op_BitwiseOrAssignment (dicadangkan)
op_CheckedAddition (dicadangkan untuk digunakan di masa mendatang)
op_CheckedDecrement (dicadangkan untuk digunakan di masa mendatang)
op_CheckedDivision (dicadangkan untuk digunakan di masa mendatang)
op_CheckedExplicit (dicadangkan untuk digunakan di masa mendatang)
op_CheckedIncrement (dicadangkan untuk digunakan di masa mendatang)
op_CheckedMultiply (dicadangkan untuk digunakan di masa mendatang)
op_CheckedSubtraction (dicadangkan untuk digunakan di masa mendatang)
op_CheckedUnaryNegation (dicadangkan untuk digunakan di masa mendatang)
op_Comma (dicadangkan)
op_Decrement -- (awalan dan sufiks)
op_Division /
op_DivisionAssignment (dicadangkan)
op_Equality ==
op_ExclusiveOr ^
op_ExclusiveOrAssignment (dicadangkan)
op_Explicit pemaksaan (penyempitan) eksplisit
op_False false
op_GreaterThan >
op_GreaterThanOrEqual >=
op_Implicit pemaksaan implisit (pelebaran)
op_Increment ++ (awalan dan sufiks)
op_Inequality !=
op_LeftShift <<
op_LeftShiftAssignment (dicadangkan)
op_LessThan <
op_LessThanOrEqual <=
op_LogicalAnd (dicadangkan)
op_LogicalNot !
op_LogicalOr (dicadangkan)
op_MemberSelection (dicadangkan)
op_Modulus %
op_ModulusAssignment (dicadangkan)
op_MultiplicationAssignment (dicadangkan)
op_Multiply * (biner)
op_OnesComplement ~
op_PointerDereference (dicadangkan)
op_PointerToMemberSelection (dicadangkan)
op_RightShift >>
op_RightShiftAssignment (dicadangkan)
op_SignedRightShift (dicadangkan)
op_Subtraction - (biner)
op_SubtractionAssignment (dicadangkan)
op_True true
op_UnaryNegation - (unari)
op_UnaryPlus + (unari)
op_UnsignedRightShift (dicadangkan untuk digunakan di masa mendatang)
op_UnsignedRightShiftAssignment (dicadangkan)

15.4 Konstanta

Konstanta adalah anggota kelas yang mewakili nilai konstanta: nilai yang dapat dikomputasi pada waktu kompilasi. constant_declaration memperkenalkan satu atau beberapa konstanta dari jenis tertentu.

constant_declaration
    : attributes? constant_modifier* 'const' type constant_declarators ';'
    ;

constant_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    ;

Sebuah constant_declaration dapat mencakup sekumpulan atribut (§22), sebuah new pengubah (§15.3.5), dan salah satu dari jenis aksesibilitas yang diizinkan (§15.3.6). Atribut dan pengubah berlaku untuk semua anggota yang dideklarasikan oleh constant_declaration. Meskipun konstanta dianggap sebagai anggota statis, constant_declaration tidak memerlukan atau mengizinkan pengubah static . Ini adalah kesalahan bagi pengubah yang sama muncul beberapa kali dalam deklarasi konstanta.

Jenis dari constant_declaration menentukan tipe anggota yang diperkenalkan oleh deklarasi. Jenisnya diikuti oleh daftar constant_declarator(§13.6.3), yang masing-masing memperkenalkan anggota baru. constant_declarator terdiri dari pengidentifikasi yang menamai anggota, diikuti dengan token ""=, diikuti oleh constant_expression (§12,23) yang memberikan nilai anggota.

Jenis yang ditentukan dalam deklarasi konstanta haruslah , sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, bool, string, sebuah enum_type, atau sebuah reference_type. Setiap constant_expression akan menghasilkan nilai jenis target atau jenis yang dapat dikonversi ke jenis target dengan konversi implisit (§10,2).

Jenis dari suatu konstanta harus setidaknya sama dapat diaksesnya dengan konstanta itu sendiri (§7.5.5).

Nilai konstanta diperoleh dalam ekspresi menggunakan simple_name (§12.8.4) atau member_access (§12.8.7).

Konstanta dapat berpartisipasi dalam constant_expression. Dengan demikian, konstanta dapat digunakan dalam konstruksi apa pun yang memerlukan constant_expression.

Catatan: Contoh konstruksi tersebut termasuk case label, goto case pernyataan, enum deklarasi anggota, atribut, dan deklarasi konstan lainnya. catatan akhir

Catatan: Seperti yang dijelaskan dalam §12.23, constant_expression adalah ekspresi yang dapat dievaluasi sepenuhnya pada waktu kompilasi. Karena satu-satunya cara untuk membuat nilai non-null dari reference_type selain string adalah dengan menerapkan operator new, dan karena operator new tidak diizinkan dalam constant_expression, satu-satunya nilai yang mungkin untuk konstanta dari reference_type selain string adalah null. catatan akhir

Ketika nama simbolis untuk nilai konstanta diinginkan, tetapi ketika jenis nilai tersebut tidak diizinkan dalam deklarasi konstanta, atau ketika nilai tidak dapat dihitung pada waktu kompilasi oleh constant_expression, bidang baca-saja (§15,5,3) dapat digunakan sebagai gantinya.

Catatan: Semantik versi dari const dan readonly berbeda (§15.5.3.3). catatan akhir

Deklarasi konstanta yang menyatakan beberapa konstanta setara dengan beberapa deklarasi konstanta tunggal dengan atribut, pengubah, dan jenis yang sama.

Contoh:

class A
{
    public const double X = 1.0, Y = 2.0, Z = 3.0;
}

setara dengan:

class A
{
    public const double X = 1.0;
    public const double Y = 2.0;
    public const double Z = 3.0;
}

contoh akhir

Konstanta diizinkan untuk bergantung pada konstanta lain dalam program yang sama selama dependensi tidak bersifat melingkar.

Contoh: Dalam kode berikut

class A
{
    public const int X = B.Z + 1;
    public const int Y = 10;
}

class B
{
    public const int Z = A.Y + 1;
}

kompilator harus terlebih dahulu mengevaluasi A.Y, lalu mengevaluasi B.Z, dan akhirnya mengevaluasi A.X, menghasilkan nilai 10, 11, dan 12.

contoh akhir

Deklarasi konstanta dapat bergantung pada konstanta dari program lain, tetapi dependensi tersebut hanya dimungkinkan dalam satu arah.

Contoh: Mengacu pada contoh di atas, jika A dan B dinyatakan dalam program terpisah, akan mungkin untuk A.X bergantung pada B.Z, tetapi B.Z kemudian tidak dapat secara bersamaan bergantung pada A.Y. contoh akhir

15.5 Bidang

15.5.1 Umum

Bidang adalah anggota yang mewakili variabel yang terkait dengan objek atau kelas. field_declaration memperkenalkan satu atau beberapa bidang dari jenis tertentu.

field_declaration
    : attributes? field_modifier* type variable_declarators ';'
    ;

field_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | 'static'
    | 'readonly'
    | 'volatile'
    | unsafe_modifier   // unsafe code support
    ;

variable_declarators
    : variable_declarator (',' variable_declarator)*
    ;

variable_declarator
    : identifier ('=' variable_initializer)?
    ;

unsafe_modifier (§23.2) hanya tersedia dalam kode tidak aman (§23).

Field_declaration dapat mencakup sekumpulan atribut (§22), sebuah (new), kombinasi yang valid dari empat pengubah akses (§15.3.6), dan sebuah (static). Selain itu, field_declaration dapat mencakup readonly modifier (§15.5.3) atau volatile modifier (§15.5.4), tetapi tidak keduanya. Atribut dan pengubah berlaku untuk semua anggota yang dideklarasikan oleh field_declaration. Ini adalah kesalahan bagi pengubah yang sama muncul beberapa kali dalam field_declaration.

Jenis dari type sebuah field_declaration menentukan jenis anggota yang diperkenalkan oleh deklarasi tersebut. Jenisnya diikuti oleh daftar variable_declarator, yang masing-masing memperkenalkan anggota baru. variable_declarator terdiri dari pengidentifikasi yang memberi nama anggota tersebut, secara opsional diikuti dengan token "=" dan variable_initializer (§15,5,6) yang memberikan nilai awal anggota tersebut.

Jenis bidang harus memiliki tingkat aksesibilitas yang setidaknya sama dengan bidang itu sendiri (§7.5.5).

Nilai sebuah field diperoleh dalam suatu ekspresi menggunakan simple_name (§12.8.4), member_access (§12.8.7) atau base_access (§12.8.15). Nilai bidang non-baca dimodifikasi menggunakan penugasan (§12.21). Nilai field non-readonly dapat diperoleh dan dimodifikasi dengan menggunakan operator penambahan dan pengurangan postfix (§12.8.16) serta operator penambahan dan pengurangan prefix (§12.9.6).

Deklarasi bidang yang menyatakan beberapa bidang setara dengan beberapa deklarasi bidang tunggal dengan atribut, pengubah, dan jenis yang sama.

Contoh:

class A
{
    public static int X = 1, Y, Z = 100;
}

setara dengan:

class A
{
    public static int X = 1;
    public static int Y;
    public static int Z = 100;
}

contoh akhir

15.5.2 Bidang statis dan instans

Saat deklarasi bidang menyertakan static pengubah, bidang yang diperkenalkan oleh deklarasi adalah bidang statis. Ketika tidak ada pengubah static, bidang yang diperkenalkan oleh deklarasi adalah bidang instans. Bidang statis dan bidang instans adalah dua dari beberapa jenis variabel (§9) yang didukung oleh C#, dan kadang-kadang disebut sebagai variabel statis dan variabel instans, masing-masing.

Seperti yang dijelaskan dalam §15.3.8, setiap instans kelas berisi sekumpulan lengkap bidang instans kelas, sementara hanya ada satu set bidang statis untuk setiap kelas non-generik atau jenis konstruksi tertutup, terlepas dari jumlah instans kelas atau jenis konstruksi tertutup.

15.5.3 Kolom yang hanya dapat dibaca

15.5.3.1 Umum

Saat deklarasi_bidang menyertakan readonly pengubah, bidang yang diperkenalkan oleh deklarasi adalah bidang hanya-baca. Penugasan langsung ke field readonly hanya dapat dilakukan sebagai bagian dari deklarasi tersebut atau dalam konstruktor instance atau konstruktor statis di kelas yang sama. (Bidang hanya baca dapat ditetapkan beberapa kali dalam konteks ini.) Secara khusus, penggunaan langsung ke bidang hanya baca hanya diizinkan dalam konteks berikut:

  • Dalam variable_declarator yang mendeklarasikan bidang (dengan mencantumkan variable_initializer dalam deklarasi).
  • Untuk sebuah bidang instance, di konstruktor instance dari kelas yang mengandung deklarasi bidang tersebut; untuk sebuah bidang statis, di konstruktor statis dari kelas yang mengandung deklarasi bidang tersebut. Ini juga merupakan satu-satunya konteks di mana valid untuk meneruskan bidang baca-saja sebagai parameter output atau referensi.

Upaya untuk menetapkan ke field hanya baca atau meneruskannya sebagai parameter output atau referensi dalam konteks lain mana pun adalah kesalahan saat waktu kompilasi.

15.5.3.2 Menggunakan bidang baca-saja statis untuk konstanta

Bidang baca-saja statis berguna ketika nama simbolis untuk nilai konstanta diinginkan, tetapi ketika jenis nilai tidak diizinkan dalam deklarasi const, atau ketika nilai tidak dapat dikomputasi pada waktu kompilasi.

Contoh: Dalam kode berikut

public class Color
{
    public static readonly Color Black = new Color(0, 0, 0);
    public static readonly Color White = new Color(255, 255, 255);
    public static readonly Color Red = new Color(255, 0, 0);
    public static readonly Color Green = new Color(0, 255, 0);
    public static readonly Color Blue = new Color(0, 0, 255);

    private byte red, green, blue;

    public Color(byte r, byte g, byte b)
    {
        red = r;
        green = g;
        blue = b;
    }
}

Anggota Black, White, Red, Green, dan Blue tidak dapat dideklarasikan sebagai const karena nilai mereka tidak dapat dihitung pada waktu kompilasi. Namun, mendeklarasikannya sebagai gantinya static readonly memiliki efek yang sama.

contoh akhir

15.5.3.3 Pengelolaan versi konstanta dan bidang baca-saja statis

Konstanta dan bidang hanya baca memiliki semantik penerapan versi biner yang berbeda. Ketika sebuah ekspresi merujuk pada sebuah konstanta, nilai dari konstanta diperoleh saat waktu kompilasi, tetapi ketika sebuah ekspresi merujuk pada field readonly, nilai dari field tersebut tidak diperoleh sampai waktu eksekusi.

Contoh: Pertimbangkan aplikasi yang terdiri dari dua program terpisah:

namespace Program1
{
    public class Utils
    {
        public static readonly int x = 1;
    }
}

dan

namespace Program2
{
    class Test
    {
        static void Main()
        {
            Console.WriteLine(Program1.Utils.X);
        }
    }
}

Namespace Program1 dan Program2 menunjukkan dua program yang dikompilasi secara terpisah. Karena Program1.Utils.X dinyatakan sebagai static readonly field, output nilai oleh perintah Console.WriteLine tidak diketahui pada waktu kompilasi, melainkan diperoleh pada waktu eksekusi. Dengan demikian, jika nilai X diubah dan Program1 dikompresi ulang, Console.WriteLine pernyataan akan menghasilkan nilai baru meskipun Program2 tidak dikompresi ulang. Namun, telah X menjadi konstanta, nilai X akan diperoleh pada saat Program2 itu dikompilasi, dan akan tetap tidak terpengaruh oleh perubahan di Program1 sampai Program2 dikompilasi ulang.

contoh akhir

15.5.4 Bidang volatil

Ketika field_declaration menyertakan volatile pengubah, bidang yang diperkenalkan oleh deklarasi tersebut adalah bidang volatil. Untuk bidang non-volatil, teknik pengoptimalan yang menyusun ulang instruksi dapat menyebabkan hasil yang tidak terduga dan tidak dapat diprediksi dalam program multi-utas yang mengakses bidang tanpa sinkronisasi seperti yang disediakan oleh lock_statement (§13.13). Pengoptimalan ini dapat dilakukan oleh pengkompilasi, oleh sistem run-time, atau oleh perangkat keras. Untuk bidang volatil, pengoptimalan penyusunan ulang tersebut dibatasi:

  • Pembacaan dari bidang volatil disebut pembacaan volatil. Baca volatil memiliki "semantik pengambilalihan"; artinya, ini dijamin terjadi sebelum referensi memori yang muncul sesudahnya dalam urutan instruksi.
  • Penulisan bidang volatil disebut penulisan volatil. Penulisan volatil memiliki semantik "release"; artinya, dijamin terjadi setelah referensi memori apa pun sebelum instruksi penulisan dalam urutan instruksi.

Pembatasan ini memastikan bahwa semua utas akan mengamati penulisan volatil yang dilakukan oleh utas lain dalam urutan di mana mereka dilakukan. Implementasi yang sesuai tidak harus menyediakan satu urutan keseluruhan total dari penulisan yang bersifat volatil sebagaimana terlihat dari semua utas eksekusi. Jenis bidang volatil harus salah satu dari yang berikut:

  • Sebuah reference_type.
  • Tipe_parameter yang diketahui sebagai tipe acuan (§15.2.5).
  • Jenis byte, sbyte, short, ushort, int, uint, char, float, bool, System.IntPtr, atau System.UIntPtr.
  • enum_type yang memiliki tipe enum_base berupa byte, sbyte, short, ushort, int, atau uint.

Contoh: Contoh

class Test
{
    public static int result;
    public static volatile bool finished;

    static void Thread2()
    {
        result = 143;
        finished = true;
    }

    static void Main()
    {
        finished = false;

        // Run Thread2() in a new thread
        new Thread(new ThreadStart(Thread2)).Start();    

        // Wait for Thread2() to signal that it has a result
        // by setting finished to true.
        for (;;)
        {
            if (finished)
            {
                Console.WriteLine($"result = {result}");
                return;
            }
        }
    }
}

menghasilkan output:

result = 143

Dalam contoh ini, metode Main memulai utas baru yang menjalankan metode Thread2. Metode ini menyimpan nilai ke dalam bidang non-volatil yang disebut result, lalu disimpan true di bidang finishedvolatil . Thread utama menunggu bidang finished diatur ke true, lalu membaca bidang result. Karena finished telah dinyatakan volatile, utas utama harus membaca nilai 143 dari bidang result. Jika bidang finished belum dinyatakan volatile, maka akan diperbolehkan bagi penyimpanan ke result untuk terlihat oleh utas utama setelah penyimpanan ke finished, dan karenanya utas utama dapat membaca nilai 0 dari bidang result. Mendeklarasikan finished sebagai volatile bidang mencegah inkonsistensi tersebut.

contoh akhir

15.5.5 Inisialisasi Lapangan

Nilai awal bidang, baik bidang statis atau bidang instans, adalah nilai default (§9,3) dari jenis bidang. Tidak mungkin mengamati nilai dari suatu variabel sebelum inisialisasi default ini terjadi, sehingga variabel tersebut tidak pernah “tidak diinisialisasi”.

Contoh: Contoh

class Test
{
    static bool b;
    int i;

    static void Main()
    {
        Test t = new Test();
        Console.WriteLine($"b = {b}, i = {t.i}");
    }
}

menghasilkan keluaran

b = False, i = 0

karena b dan i keduanya secara otomatis diinisialisasi ke nilai default.

contoh akhir

15.5.6 Penginisialisasi variabel

15.5.6.1 Umum

Deklarasi bidang dapat mencakup variable_initializer. Untuk bidang statis, penginisialisasi variabel sesuai dengan pernyataan penugasan yang dijalankan selama inisialisasi kelas. Untuk bidang instance, penginisialisasian variabel berhubungan dengan pernyataan penugasan yang dijalankan ketika sebuah instance dari kelas dibuat.

Contoh: Contoh

class Test
{
    static double x = Math.Sqrt(2.0);
    int i = 100;
    string s = "Hello";

    static void Main()
    {
        Test a = new Test();
        Console.WriteLine($"x = {x}, i = {a.i}, s = {a.s}");
    }
}

menghasilkan keluaran

x = 1.4142135623730951, i = 100, s = Hello

karena penugasan ke x terjadi ketika penginisialisasi bidang statis dijalankan, dan penugasan ke i dan s terjadi ketika penginisialisasi bidang instance dijalankan.

contoh akhir

Inisialisasi nilai default yang dijelaskan dalam §15.5.5 terjadi untuk semua bidang, termasuk bidang yang memiliki penginisialisasi variabel. Dengan demikian, ketika kelas diinisialisasi, semua bidang statis di kelas tersebut pertama kali diinisialisasi ke nilai defaultnya, lalu penginisialisasi bidang statis dijalankan dalam urutan tekstual. Demikian juga, ketika instans kelas dibuat, semua bidang instans dalam instans tersebut pertama kali diinisialisasi ke nilai defaultnya, lalu penginisialisasi bidang instans dijalankan dalam urutan tekstual. Ketika ada deklarasi bidang dalam beberapa deklarasi tipe parsial untuk tipe yang sama, urutan bagiannya tidak ditentukan. Namun, dalam setiap bagian, inisialisasi bidang dieksekusi secara berurutan.

Dimungkinkan bagi bidang statis dengan penginisialisasi variabel untuk diamati dalam status nilai defaultnya.

Contoh: Namun, ini sangat tidak dianjurkan sebagai masalah gaya. Contoh

class Test
{
    static int a = b + 1;
    static int b = a + 1;

    static void Main()
    {
        Console.WriteLine($"a = {a}, b = {b}");
    }
}

menunjukkan perilaku ini. Terlepas dari definisi melingkar a dan b, program ini valid. Ini menghasilkan output

a = 1, b = 2

karena bidang statis a dan b diinisialisasi ke 0 (nilai default untuk int) sebelum penginisialisasiannya dijalankan. Ketika penginisialisasi untuk a dijalankan, nilainya b adalah nol, sehingga a diinisialisasi ke 1. Ketika inisialisasi untuk b dijalankan, nilai dari a sudah 1, dan oleh karena itu b diinisialisasi menjadi 2.

contoh akhir

15.5.6.2 Inisialisasi bidang statis

Penginisialisasi variabel bidang statis dari kelas mengacu pada urutan penugasan yang dieksekusi sesuai dengan urutan teks sebagaimana mereka muncul dalam deklarasi kelas (§15.5.6.1). Dalam kelas parsial, arti "urutan tekstual" ditentukan oleh §15.5.6.1. Jika konstruktor statis (§15.12) ada di kelas , eksekusi penginisialisasi bidang statis terjadi segera sebelum mengeksekusi konstruktor statis tersebut. Jika tidak, penginisialisasi bidang statis dijalankan pada waktu yang bergantung pada implementasi sebelum penggunaan pertama bidang statis sebuah kelas tersebut.

Contoh: Contoh

class Test
{
    static void Main()
    {
        Console.WriteLine($"{B.Y} {A.X}");
    }

    public static int F(string s)
    {
        Console.WriteLine(s);
        return 1;
    }
}

class A
{
    public static int X = Test.F("Init A");
}

class B
{
    public static int Y = Test.F("Init B");
}

mungkin menghasilkan salah satu output:

Init A
Init B
1 1

atau keluaran:

Init B
Init A
1 1

karena eksekusi penginisialisasi X dan penginisialisasi Y dapat terjadi dalam salah satu urutan; mereka hanya dibatasi untuk terjadi sebelum referensi ke field tersebut. Namun, dalam contoh:

class Test
{
    static void Main()
    {
        Console.WriteLine($"{B.Y} {A.X}");
    }

    public static int F(string s)
    {
        Console.WriteLine(s);
        return 1;
    }
}

class A
{
    static A() {}
    public static int X = Test.F("Init A");
}

class B
{
    static B() {}
    public static int Y = Test.F("Init B");
}

outputnya adalah:

Init B
Init A
1 1

karena aturan yang menentukan kapan konstruktor statis dijalankan (seperti yang didefinisikan dalam §15.12) menyatakan bahwa konstruktor statis B (dan karenanya penginisialisasi field statis B) harus berjalan sebelum konstruktor statis dan penginisialisasi field A.

contoh akhir

15.5.6.3 Inisialisasi variabel instance

Penginisialisasi variabel bidang instans kelas sesuai dengan urutan penugasan yang dijalankan segera setelah masuk ke salah satu konstruktor instans (§15.11.3) dari kelas tersebut. Dalam kelas parsial, arti "urutan tekstual" ditentukan oleh §15.5.6.1. Penginisialisasi variabel dijalankan dalam urutan tekstual di mana mereka muncul dalam deklarasi kelas (§15.5.6.1). Proses pembuatan dan inisialisasi instans kelas dijelaskan lebih lanjut dalam §15.11.

Penginisialisasi variabel untuk bidang instans tidak dapat mereferensikan instans yang sedang dibuat. Dengan demikian, ini adalah kesalahan waktu kompilasi untuk mereferensikan this dalam penginisialisasi variabel, karena merupakan kesalahan waktu kompilasi bagi penginisialisasi variabel untuk mereferensikan anggota instans apa pun melalui simple_name.

Contoh: Dalam kode berikut

class A
{
    int x = 1;
    int y = x + 1;     // Error, reference to instance member of this
}

Penginisialisasi variabel untuk y menghasilkan kesalahan waktu kompilasi karena mengacu pada anggota dari instance yang sedang dibuat.

contoh akhir

15.6 Metode

15.6.1 Umum

Metode adalah anggota yang mengimplementasikan komputasi atau tindakan yang dapat dilakukan oleh objek atau kelas. Metode dinyatakan menggunakan method_declaration:

method_declaration
    : attributes? method_modifiers return_type method_header method_body
    | attributes? ref_method_modifiers ref_kind ref_return_type method_header
      ref_method_body
    ;

method_modifiers
    : method_modifier* 'partial'?
    ;

ref_kind
    : 'ref'
    | 'ref' 'readonly'
    ;

ref_method_modifiers
    : ref_method_modifier*
    ;

method_header
    : member_name '(' parameter_list? ')'
    | member_name type_parameter_list '(' parameter_list? ')'
      type_parameter_constraints_clause*
    ;

method_modifier
    : ref_method_modifier
    | 'async'
    ;

ref_method_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | 'static'
    | 'virtual'
    | 'sealed'
    | 'override'
    | 'abstract'
    | 'extern'
    | 'readonly'        // direct struct members only
    | unsafe_modifier   // unsafe code support
    ;

return_type
    : ref_return_type
    | 'void'
    ;

ref_return_type
    : type
    ;

member_name
    : identifier
    | interface_type '.' identifier
    ;

method_body
    : block
    | '=>' null_conditional_invocation_expression ';'
    | '=>' expression ';'
    | ';'
    ;

ref_method_body
    : block
    | '=>' 'ref' variable_reference ';'
    | ';'
    ;

Catatan tata bahasa:

  • unsafe_modifier (§23.2) hanya tersedia dalam kode tidak aman (§23).
  • saat mengenali method_body, jika kedua alternatif null_conditional_invocation_expression dan ekspresi dapat diterapkan, maka yang pertama akan dipilih.

Catatan: Tumpang tindih dan prioritas antara alternatif di sini semata-mata untuk kenyamanan deskriptif; aturan tata bahasa dapat dijelaskan lebih lanjut untuk menghilangkan tumpang tindih. ANTLR, dan sistem tata bahasa lainnya, mengadopsi kenyamanan yang sama sehingga method_body memiliki semantik yang ditentukan secara otomatis. catatan akhir

Method_declaration dapat mencakup sekumpulan atribut (§22) dan salah satu jenis aksesibilitas yang diizinkan (§15.3.6), new (§15.3.5), static (§15.6.3), virtual (§15.6.4), override (§15.6.5), sealed (§15.6.6), abstract (§15.6.7), extern (§15.6.8) dan async (§15.14). Selain itu method_declaration yang dimuat langsung oleh struct_declaration dapat mencakup pengubah readonly (§16.4.12).

Deklarasi memiliki kombinasi pengubah yang valid jika semua hal berikut ini benar:

  • Deklarasi ini mencakup kombinasi pengubah akses yang valid (§15.3.6).
  • Deklarasi tidak menyertakan pengubah yang sama berulang kali.
  • Deklarasi ini mencakup paling banyak salah satu pengubah berikut: static, , virtualdan override.
  • Deklarasi mencakup paling banyak salah satu pengubah berikut: new dan override.
  • Jika deklarasi menyertakan pengubahabstract, maka deklarasi tidak menyertakan salah satu pengubah berikut: static, , virtualsealed, atau extern.
  • Jika deklarasi menyertakan pengubah private , maka deklarasi tidak menyertakan salah satu pengubah berikut: virtual, , overrideatau abstract.
  • Jika deklarasi menyertakan pengubah sealed , maka deklarasi juga menyertakan pengubah override .
  • Jika deklarasi menyertakan partial pengubah, maka itu tidak termasuk salah satu pengubah berikut: new, , public, protected, internal, privatevirtual, , sealedoverride, , abstract, atau extern.

Metode diklasifikasikan sesuai dengan apa yang, jika ada, mereka berikan.

  • Jika ref ada, metodenya mengembalikan-referensi dan menghasilkan referensi variabel, yang bersifat opsional hanya bisa dibaca;
  • Jika tidak, jika return_type adalah void, metodenya adalah returns-no-value dan tidak mengembalikan nilai;
  • Jika tidak, metode ini adalah returns-by-value dan mengembalikan nilai.

return_type dalam deklarasi metode returns-by-value atau returns-no-value menentukan jenis hasil, jika ada, yang dikembalikan oleh metode tersebut. Hanya metode yang tidak mengembalikan nilai dapat mencakup pengubah partial (§15.6.9). Jika deklarasi menyertakan async pengubah maka return_type adalah void atau metode mengembalikan nilai demi nilai dan jenis pengembalian adalah jenis tugas (§15.14.1).

Ref_return_type deklarasi metode returns-by-ref menentukan jenis variabel yang direferensikan oleh variable_reference yang dikembalikan oleh metode .

Metode generik adalah metode yang deklarasinya mencakup type_parameter_list. Ini menentukan parameter jenis untuk metode . type_parameter_constraints_clause opsional menentukan batasan untuk parameter tipe.

Deklarasi method_declaration generik untuk implementasi anggota antarmuka eksplisit tidak boleh memiliki type_parameter_constraints_clause apa pun; deklarasi mewarisi batasan apa pun dari batasan pada metode antarmuka.

Demikian pula, deklarasi metode dengan pengubah override tidak boleh memiliki type_parameter_constraints_clause dan batasan pada parameter jenis metode tersebut diwariskan dari metode virtual yang ditimpa.

member_name menentukan nama metode . Kecuali metode ini adalah implementasi anggota antarmuka eksplisit (§18.6.2), member_name hanyalah pengidentifikasi.

Untuk implementasi anggota antarmuka eksplisit, member_name terdiri dari interface_type diikuti oleh "." dan pengidentifikasi. Dalam hal ini, deklarasi tidak boleh mencakup pengubah selain (mungkin) extern atau async.

Daftar_parameter opsional menentukan parameter dari metode (§15.6.2).

return_type atau ref_return_type, dan setiap jenis yang dirujuk dalam parameter_list suatu metode, harus setidaknya sama dapat diaksesnya seperti metode itu sendiri (§7.5.5).

method_body dari metode returns-by-value atau returns-no-value adalah berupa titik koma, tubuh blok, atau tubuh ekspresi. Badan blok terdiri dari blok, yang menentukan pernyataan yang akan dijalankan ketika metode dipanggil. Bentuk ekspresi terdiri dari =>, diikuti oleh null_conditional_invocation_expression atau ekspresi, kemudian diakhiri dengan titik koma, yang menunjukkan ekspresi tunggal untuk dilakukan ketika metode dipanggil.

Untuk metode abstrak dan ekstern, method_body hanya terdiri dari titik koma. Untuk metode parsial, method_body dapat terdiri dari titik koma, badan blok, atau badan ekspresi. Untuk semua metode lainnya, method_body adalah badan blok atau badan ekspresi.

Jika method_body terdiri dari titik koma, deklarasi tidak boleh menyertakan pengubahasync.

Ref_method_body metode returns-by-ref adalah titik koma, isi blok, atau isi ekspresi. Badan blok terdiri dari blok, yang menentukan pernyataan yang akan dijalankan ketika metode dipanggil. Badan ekspresi terdiri dari =>, diikuti oleh ref dan variable_reference, serta diakhiri dengan titik koma, dan menunjukkan satu variable_reference untuk dievaluasi ketika metode dipanggil.

Untuk metode abstrak dan ekstern, ref_method_body hanya terdiri dari titik koma; untuk semua metode lainnya, ref_method_body adalah badan blok atau badan ekspresi.

Nama, jumlah parameter jenis, dan daftar parameter metode menentukan tanda tangan (§7,6) metode. Secara khusus, tanda tangan sebuah metode terdiri dari namanya, jumlah parameter tipe yang dimilikinya, serta jumlah, parameter_mode_modifier (§15.6.2.1), dan jenis dari parameter-parameternya. Jenis pengembalian bukanlah bagian dari tanda tangan metode, begitu juga dengan nama parameter, nama parameter jenis, maupun batasan yang ada. Ketika jenis parameter mereferensikan parameter jenis metode, posisi ordinal parameter jenis (bukan nama parameter jenis) digunakan untuk kesetaraan jenis.

Nama metode akan berbeda dari nama semua non-metode lain yang dideklarasikan dalam kelas yang sama. Selain itu, tanda tangan metode akan berbeda dari tanda tangan semua metode lain yang dideklarasikan dalam kelas yang sama, dan dua metode yang dinyatakan dalam kelas yang sama tidak akan memiliki tanda tangan yang hanya berbeda dengan in, , outdan ref.

type_parameter metode berada dalam cakupan di seluruh method_declaration, dan dapat digunakan untuk membentuk jenis di seluruh cakupan tersebut dalam return_type atau ref_return_type, method_body atau ref_method_body, dan type_parameter_constraints_clause, tetapi tidak dalam atribut.

Semua parameter dan parameter jenis harus memiliki nama yang berbeda.

15.6.2 Parameter metode

15.6.2.1 Umum

Parameter metode, jika ada, dideklarasikan oleh parameter_list metode.

parameter_list
    : fixed_parameters
    | fixed_parameters ',' parameter_array
    | parameter_array
    ;

fixed_parameters
    : fixed_parameter (',' fixed_parameter)*
    ;

fixed_parameter
    : attributes? parameter_modifier? type identifier default_argument?
    ;

default_argument
    : '=' expression
    ;

parameter_modifier
    : parameter_mode_modifier
    | 'this'
    ;

parameter_mode_modifier
    : 'ref'
    | 'out'
    | 'in'
    ;

parameter_array
    : attributes? 'params' array_type identifier
    ;

Daftar parameter terdiri dari satu atau beberapa parameter yang dipisahkan koma yang hanya terakhir mungkin merupakan parameter_array.

fixed_parameter terdiri dari sekumpulan atribut opsional (§22); pengubah opsional in, , out, refatau this ; jenis; pengidentifikasi; dan default_argument opsional. Setiap fixed_parameter mendeklarasikan parameter dari jenis yang diberikan dengan nama yang diberikan. Modifier this menetapkan metode sebagai metode ekstensi dan hanya diperbolehkan pada parameter pertama dari metode statis dalam kelas statis non-generik dan non-bersarang. Jika parameter adalah struct jenis atau parameter jenis yang dibatasi ke struct, this pengubah dapat dikombinasikan dengan pengubah ref atau in , tetapi bukan pengubah out . Metode ekstensi dijelaskan lebih lanjut dalam §15.6.10. A fixed_parameter dengan default_argument dikenal sebagai parameter opsional, sedangkan fixed_parameter tanpa default_argument adalah parameter yang diperlukan. Parameter yang diperlukan tidak akan muncul setelah parameter opsional dalam parameter_list.

Parameter dengan pengubah ref, out, atau this tidak dapat memiliki default_argument. Parameter input mungkin memiliki default_argument. Ekspresi dalam default_argument akan menjadi salah satu hal berikut:

  • constant_expression
  • ekspresi formulir new S() di mana S adalah jenis nilai
  • ekspresi formulir default(S) di mana S adalah jenis nilai

Ekspresi harus dapat secara implisit dikonversi oleh identitas atau konversi nullable ke tipe parameter.

Jika parameter opsional terjadi dalam deklarasi metode parsial yang menerapkan (§15,6,9), implementasi anggota antarmuka eksplisit (§18,6,2), deklarasi pengindeks parameter tunggal (§15,9), atau dalam deklarasi operator (§15.10.1) pengkompilasi harus memberikan peringatan, karena anggota ini tidak pernah dapat dipanggil dengan cara yang memungkinkan argumen dihilangkan.

parameter_array terdiri dari sekumpulan atribut opsional (§22), params pengubah, array_type, dan pengidentifikasi. Array parameter mendeklarasikan satu parameter dengan nama yang ditentukan dari jenis array yang diberikan. array_type dari array parameter haruslah jenis array dimensi tunggal (§17.2). Dalam pemanggilan metode, sebuah array parameter mengizinkan baik satu argumen dari tipe array yang diberikan untuk ditentukan, atau mengizinkan nol atau lebih argumen dari tipe elemen array untuk ditentukan. Array parameter dijelaskan lebih lanjut dalam §15.6.2.4.

parameter_array dapat terjadi setelah parameter opsional, tetapi tidak dapat memiliki nilai default – kelalaian argumen untuk parameter_array akan mengakibatkan pembuatan array kosong.

Contoh: Berikut ini menggambarkan berbagai jenis parameter:

void M<T>(
    ref int i,
    decimal d,
    bool b = false,
    bool? n = false,
    string s = "Hello",
    object o = null,
    T t = default(T),
    params int[] a
) { }

Dalam parameter_list untuk M, i adalah parameter yang diperlukanref, d adalah parameter nilai yang diperlukan, b, , so dan t merupakan parameter nilai opsional dan a merupakan array parameter.

contoh akhir

Deklarasi metode membuat ruang deklarasi terpisah (§7,3) untuk parameter dan parameter jenis. Nama dimasukkan ke dalam ruang deklarasi ini oleh daftar parameter jenis dan daftar parameter metode . Isi metode, jika ada, dianggap sebagai bagian dari ruang deklarasi ini. Ini adalah kesalahan bagi dua anggota ruang deklarasi metode untuk memiliki nama yang sama.

Pemanggilan metode (§12.8.10.2) membuat salinan, khusus untuk pemanggilan tersebut, parameter dan variabel lokal metode, dan daftar argumen pemanggilan menetapkan nilai atau referensi variabel ke parameter yang baru dibuat. Dalam blok sebuah metode, parameter dapat direferensikan melalui pengidentifikasinya dalam ekspresi simple_name (§12.8.4).

Jenis parameter berikut ada:

Catatan: Seperti yang dijelaskan dalam §7.6, inoutref adalah bagian dari tanda tangan metode, tetapi params tidak. catatan akhir

15.6.2.2 Parameter nilai

Parameter yang dideklarasikan tanpa pengubah adalah parameter nilai. Parameter nilai adalah variabel lokal yang mendapatkan nilai awalnya dari argumen terkait yang disediakan dalam pemanggilan metode.

Untuk aturan penetapan pasti, lihat §9.2.5.

Argumen yang sesuai dalam pemanggilan metode harus berupa ekspresi yang secara implisit dapat dikonversi (§10.2) ke jenis parameter.

Metode diizinkan untuk menetapkan nilai baru ke parameter nilai. Penetapan tersebut hanya memengaruhi lokasi penyimpanan lokal yang diwakili oleh parameter nilai—tidak berpengaruh pada argumen aktual yang diberikan dalam pemanggilan metode.

15.6.2.3 Parameter melalui referensi

15.6.2.3.1 Umum

Parameter input, output, dan referensi adalah parameter referensi. Parameter by-reference adalah variabel referensi lokal (§9,7); referensi awal dari parameter ini diperoleh dari argumen terkait yang diberikan saat pemanggilan metode.

Catatan: Referen dari parameter by-reference dapat diubah menggunakan operator penugasan ref (= ref).

Ketika parameter adalah parameter oleh referensi, argumen yang sesuai dalam pemanggilan metode harus terdiri dari kata kunci yang sesuai, in, ref, atau out, diikuti dengan variable_reference (§9.5) dari jenis yang sama dengan parameter. Namun, ketika parameter adalah in parameter, argumen mungkin merupakan ekspresi di mana konversi implisit (§10.2) ada dari ekspresi argumen tersebut ke jenis parameter yang sesuai.

Parameter referensi tidak diizinkan pada fungsi yang dinyatakan sebagai iterator (§15,15) atau fungsi asinkron (§15.14).

Dalam metode yang mengambil beberapa parameter yang diteruskan secara referensi, memungkinkan beberapa nama untuk mewakili lokasi penyimpanan yang sama.

Parameter Masukan

Parameter yang dideklarasikan dengan pengubah in adalah parameter input. Argumen yang sesuai dengan parameter input adalah variabel yang ada pada titik pemanggilan metode, atau yang dibuat oleh implementasi (§12.6.2.3) dalam pemanggilan metode. Untuk aturan penetapan pasti, lihat §9.2.8.

Ini adalah kesalahan waktu kompilasi untuk memodifikasi nilai parameter input.

Catatan: Tujuan utama parameter input adalah untuk efisiensi. Ketika tipe parameter dari sebuah metode adalah struct yang berukuran besar dari segi kebutuhan memori, berguna untuk dapat menghindari penyalinan seluruh nilai argumen ketika memanggil metode. Parameter input memungkinkan metode untuk merujuk ke nilai yang ada dalam memori, sambil memberikan perlindungan terhadap perubahan yang tidak diinginkan pada nilai-nilai tersebut. catatan akhir

15.6.2.3.3 Parameter referensi

Parameter yang dideklarasikan dengan ref pengubah adalah parameter referensi. Untuk aturan penetapan pasti, lihat §9.2.6.

Contoh: Contoh

class Test
{
    static void Swap(ref int x, ref int y)
    {
        int temp = x;
        x = y;
        y = temp;
    }

    static void Main()
    {
        int i = 1, j = 2;
        Swap(ref i, ref j);
        Console.WriteLine($"i = {i}, j = {j}");
    }
}

menghasilkan keluaran

i = 2, j = 1

Untuk pemanggilan Swap dalam Main, x mewakili i dan y mewakili j. Dengan demikian, pemanggilan memiliki efek bertukar nilai i dan j.

contoh akhir

Contoh: Dalam kode berikut

class A
{
    string s;
    void F(ref string a, ref string b)
    {
        s = "One";
        a = "Two";
        b = "Three";
    }

    void G()
    {
        F(ref s, ref s);
    }
}

pemanggilan F di G meneruskan referensi ke s untuk a dan b. Dengan demikian, untuk pemanggilan itu, nama s, , adan b semuanya merujuk ke lokasi penyimpanan yang sama, dan ketiga penugasan semuanya memodifikasi bidang sinstans .

contoh akhir

Untuk tipe struct, dalam metode instans, aksesor instans (§12.2.1), atau konstruktor instans dengan inisialisasi konstruktor, kata kunci this berperilaku persis seperti parameter referensi dari tipe struct (§12.8.14).

Parameter keluaran

Parameter yang dideklarasikan dengan pengubah out adalah parameter keluaran. Untuk aturan penetapan pasti, lihat §9.2.7.

Metode yang dinyatakan sebagai metode parsial (§15.6.9) tidak boleh memiliki parameter output.

Catatan: Parameter output biasanya digunakan dalam metode yang menghasilkan beberapa nilai pengembalian. catatan akhir

Contoh:

class Test
{
    static void SplitPath(string path, out string dir, out string name)
    {
        int i = path.Length;
        while (i > 0)
        {
            char ch = path[i - 1];
            if (ch == '\\' || ch == '/' || ch == ':')
            {
                break;
            }
            i--;
        }
        dir = path.Substring(0, i);
        name = path.Substring(i);
    }

    static void Main()
    {
        string dir, name;
        SplitPath(@"c:\Windows\System\hello.txt", out dir, out name);
        Console.WriteLine(dir);
        Console.WriteLine(name);
    }
}

Contoh menghasilkan output:

c:\Windows\System\
hello.txt

Perhatikan bahwa dir dan name variabel mungkin tidak ditetapkan sebelum diteruskan ke SplitPath, dan variabel tersebut dianggap pasti ditetapkan setelah pemanggilan.

contoh akhir

15.6.2.4 Array parameter

Parameter yang dideklarasikan dengan pengubah params adalah array parameter. Jika daftar parameter menyertakan array parameter, itu haruslah menjadi parameter terakhir dalam daftar dan harus berupa array berdimensi tunggal.

Contoh: Jenis string[] dan string[][] dapat digunakan sebagai jenis array parameter, tetapi jenisnya string[,] tidak bisa. contoh akhir

Catatan: Tidak dimungkinkan untuk menggabungkan pengubah params dengan pengubah in, , outatau ref. catatan akhir

Array parameter mengizinkan argumen ditentukan dalam salah satu dari dua cara dalam pemanggilan metode:

  • Argumen yang diberikan untuk array parameter dapat berupa ekspresi tunggal yang secara implisit dapat dikonversi (§10,2) ke jenis array parameter. Dalam hal ini, array parameter bertindak persis seperti parameter nilai.
  • Atau, pemanggilan dapat menentukan nol atau lebih argumen untuk array parameter, di mana setiap argumen adalah ekspresi yang secara implisit dapat dikonversi (§10,2) ke jenis elemen array parameter. Dalam hal ini, pemanggilan membuat instans jenis array parameter dengan panjang yang sesuai dengan jumlah argumen, menginisialisasi elemen instans array dengan nilai argumen yang diberikan, dan menggunakan instans array yang baru dibuat sebagai argumen aktual.

Kecuali untuk mengizinkan jumlah variabel argumen dalam pemanggilan, array parameter sama persis dengan parameter nilai (§15.6.2.2) dari jenis yang sama.

Contoh: Contoh

class Test
{
    static void F(params int[] args)
    {
        Console.Write($"Array contains {args.Length} elements:");
        foreach (int i in args)
        {
            Console.Write($" {i}");
        }
        Console.WriteLine();
    }

    static void Main()
    {
        int[] arr = {1, 2, 3};
        F(arr);
        F(10, 20, 30, 40);
        F();
    }
}

menghasilkan keluaran

Array contains 3 elements: 1 2 3
Array contains 4 elements: 10 20 30 40
Array contains 0 elements:

Pemanggilan pertama dari F hanya meneruskan array arr sebagai parameter nilai. Pemanggilan kedua fungsi F secara otomatis membuat array berisi empat elemen int[] dengan nilai elemen yang diberikan dan meneruskan instans array tersebut sebagai parameter nilai. Demikian juga, pemanggilan F ketiga membuat elemen int[] nol dan meneruskan instans tersebut sebagai parameter nilai. Pemanggilan kedua dan ketiga tepatnya setara dengan penulisan:

F(new int[] {10, 20, 30, 40});
F(new int[] {});

contoh akhir

Saat melakukan resolusi kelebihan beban, metode dengan array parameter mungkin berlaku, baik dalam bentuk normal atau dalam bentuk yang diperluas (§12.6.4.2). Bentuk metode yang diperluas hanya tersedia jika bentuk normal metode tidak berlaku dan hanya jika metode yang berlaku dengan tanda tangan yang sama dengan formulir yang diperluas belum dideklarasikan dalam jenis yang sama.

Contoh: Contoh

class Test
{
    static void F(params object[] a) =>
        Console.WriteLine("F(object[])");

    static void F() =>
        Console.WriteLine("F()");

    static void F(object a0, object a1) =>
        Console.WriteLine("F(object,object)");

    static void Main()
    {
        F();
        F(1);
        F(1, 2);
        F(1, 2, 3);
        F(1, 2, 3, 4);
    }
}

menghasilkan keluaran

F()
F(object[])
F(object,object)
F(object[])
F(object[])

Dalam contoh, dua kemungkinan bentuk metode yang diperluas dengan array parameter sudah disertakan dalam kelas sebagai metode reguler. Oleh karena itu, formulir yang diperluas ini tidak dipertimbangkan saat melakukan resolusi kelebihan beban, dan pemanggilan metode pertama dan ketiga dengan demikian memilih metode reguler. Ketika kelas mendeklarasikan metode dengan array parameter, tidak jarang juga menyertakan beberapa formulir yang diperluas sebagai metode reguler. Dengan demikian, dimungkinkan untuk menghindari alokasi instans array yang terjadi ketika bentuk metode yang diperluas dengan array parameter dipanggil.

contoh akhir

Array adalah jenis referensi, sehingga nilai yang diteruskan untuk array parameter bisa berupa null.

Contoh: Contoh:

class Test
{
    static void F(params string[] array) =>
        Console.WriteLine(array == null);

    static void Main()
    {
        F(null);
        F((string) null);
    }
}

menghasilkan output:

True
False

Pemanggilan kedua menghasilkan False karena setara dengan F(new string[] { null }) dan meneruskan array yang berisi referensi null tunggal.

contoh akhir

Ketika jenis dari array parameter adalah object[], potensi ambiguitas muncul antara bentuk normal dari metode dan bentuk yang diperluas untuk satu parameter object. Alasan ambiguitas adalah bahwa object[] itu sendiri secara implisit dapat dikonversi ke jenis object. Ambiguitas tidak menjadi masalah, karena dapat diselesaikan dengan memasukkan tipe konversi (casting) jika diperlukan.

Contoh: Contoh

class Test
{
    static void F(params object[] args)
    {
        foreach (object o in args)
        {
            Console.Write(o.GetType().FullName);
            Console.Write(" ");
        }
        Console.WriteLine();
    }

    static void Main()
    {
        object[] a = {1, "Hello", 123.456};
        object o = a;
        F(a);
        F((object)a);
        F(o);
        F((object[])o);
    }
}

menghasilkan keluaran

System.Int32 System.String System.Double
System.Object[]
System.Object[]
System.Int32 System.String System.Double

Dalam pemanggilan Fpertama dan terakhir , bentuk F normal berlaku karena konversi implisit ada dari jenis argumen ke jenis parameter (keduanya berjenis object[]). Dengan demikian, resolusi kelebihan beban memilih bentuk Fnormal , dan argumen diteruskan sebagai parameter nilai reguler. Dalam pemanggilan kedua dan ketiga, bentuk F normal tidak berlaku karena tidak ada konversi implisit dari jenis argumen ke jenis parameter (jenis object tidak dapat dikonversi secara implisit ke jenis object[]). Namun, bentuk terluas dari F dapat diterapkan, sehingga dipilih melalui resolusi kelebihan beban. Akibatnya, satu elemen object[] dibuat oleh pemanggilan, dan elemen tunggal array diinisialisasi dengan nilai argumen yang diberikan (yang itu sendiri adalah referensi ke object[]).

contoh akhir

15.6.3 Metode statis dan instans

Ketika deklarasi metode menyertakan static pengubah, metode itu dikatakan sebagai metode statis. Ketika tidak ada static pengubah yang ada, metode ini dikatakan sebagai metode instans.

Metode statis tidak beroperasi pada instans tertentu, dan merupakan kesalahan waktu kompilasi untuk merujuk this dalam metode statis.

Metode instans beroperasi pada instans kelas tertentu, dan instans tersebut dapat diakses sebagai this (§12.8.14).

Perbedaan antara anggota statis dan instans dibahas lebih lanjut dalam §15.3.8.

15.6.4 Metode virtual

Ketika deklarasi metode instans menyertakan pengubah virtual, metode tersebut dikatakan sebagai metode virtual. Ketika tidak terdapat pengubah virtual, metode tersebut disebut metode non-virtual.

Implementasi metode non-virtual tidak berubah: Implementasinya tetap sama, baik ketika metode dipanggil pada instans kelas tempat metode tersebut dideklarasikan maupun pada instans kelas turunan. Sebaliknya, implementasi metode virtual dapat digantikan oleh kelas turunan. Proses penggantian implementasi metode virtual yang diwariskan dikenal sebagai mengambil alih metode tersebut (§15.6.5).

Dalam pemanggilan metode virtual, jenis run-time instans tempat pemanggilan tersebut terjadi menentukan implementasi metode aktual yang akan dipanggil. Dalam pemanggilan metode non-virtual, tipe waktu kompilasi instans adalah faktor yang menentukan. Dalam istilah yang tepat, ketika metode bernama N dipanggil dengan daftar argumen A pada instans dengan jenis waktu kompilasi C dan jenis waktu run-time R (di mana R adalah C atau kelas yang berasal dari C), pemanggilan diproses sebagai berikut:

  • Pada waktu pengikatan, resolusi kelebihan beban diterapkan ke C, , Ndan A, untuk memilih metode M tertentu dari set metode yang dideklarasikan dan diwarisi oleh C. Ini dijelaskan dalam §12.8.10.2.
  • Kemudian pada run-time:
    • Jika M adalah metode non-virtual, M dipanggil.
    • Jika tidak, M adalah metode virtual, dan implementasi turunan paling akhir dari M sehubungan dengan R akan dipanggil.

Untuk setiap metode virtual yang dideklarasikan dalam atau diwariskan oleh kelas, ada implementasi metode yang paling turunan sehubungan dengan kelas tersebut. Implementasi metode M virtual yang paling turunan sehubungan dengan kelas R ditentukan sebagai berikut:

  • Jika R berisi deklarasi virtual pengenalan M, maka ini adalah implementasi turunan paling akhir sehubungan dengan MR.
  • Jika R berisi penimpaan M, maka ini adalah implementasi yang paling terderivasi sehubungan dengan M dan R.
  • Jika tidak, implementasi yang paling turunan dari M sehubungan dengan R adalah sama dengan implementasi yang paling turunan dari M sehubungan dengan kelas dasar langsung dari R.

Contoh: Contoh berikut mengilustrasikan perbedaan antara metode virtual dan non-virtual:

class A
{
    public void F() => Console.WriteLine("A.F");
    public virtual void G() => Console.WriteLine("A.G");
}

class B : A
{
    public new void F() => Console.WriteLine("B.F");
    public override void G() => Console.WriteLine("B.G");
}

class Test
{
    static void Main()
    {
        B b = new B();
        A a = b;
        a.F();
        b.F();
        a.G();
        b.G();
    }
}

Dalam contoh, A memperkenalkan metode non-virtual F dan metode virtual G. B Kelas ini memperkenalkan metode non-virtual baru , sehingga menyembunyikan metode F yang diwariskan , dan juga meng-override metode yang diwariskan F. Contoh menghasilkan output:

A.F
B.F
B.G
B.G

Perhatikan bahwa pernyataan a.G() memanggil B.G, bukan A.G. Ini karena jenis run-time instans (yaitu B), bukan jenis waktu kompilasi instans (yaitu A), menentukan implementasi metode aktual yang akan dipanggil.

contoh akhir

Karena metode diizinkan untuk menyembunyikan metode yang diwariskan, dimungkinkan bagi kelas untuk berisi beberapa metode virtual dengan tanda tangan yang sama. Ini tidak menghadirkan masalah ambiguitas, karena semua metode kecuali yang paling turunan disembunyikan.

Contoh: Dalam kode berikut

class A
{
    public virtual void F() => Console.WriteLine("A.F");
}

class B : A
{
    public override void F() => Console.WriteLine("B.F");
}

class C : B
{
    public new virtual void F() => Console.WriteLine("C.F");
}

class D : C
{
    public override void F() => Console.WriteLine("D.F");
}

class Test
{
    static void Main()
    {
        D d = new D();
        A a = d;
        B b = d;
        C c = d;
        a.F();
        b.F();
        c.F();
        d.F();
    }
}

Kelas C dan D berisi dua metode virtual dengan tanda tangan yang sama: satu diperkenalkan oleh A dan satu lagi diperkenalkan oleh C. Metode yang diperkenalkan dengan C menyembunyikan metode yang diwariskan dari A. Dengan demikian, deklarasi penimpaan dalam D mengambil alih metode yang diperkenalkan oleh C, dan tidak mungkin untuk D mengambil alih metode yang diperkenalkan oleh A. Contoh menghasilkan output:

B.F
B.F
D.F
D.F

Perhatikan bahwa dimungkinkan memanggil metode virtual tersembunyi dengan mengakses objek D melalui tipe yang lebih sedikit diturunkan di mana metode tersebut tidak disembunyikan.

contoh akhir

15.6.5 Menimpa Metode

Ketika deklarasi metode instan menyertakan override modifier, metode tersebut disebut sebagai metode override. Metode penggantian menggantikan metode virtual yang diwarisi dengan penanda yang sama. Sedangkan deklarasi metode virtual memperkenalkan metode baru, deklarasi metode penimpaan mengkhususkan metode virtual yang diwariskan yang ada dengan memberikan implementasi baru dari metode tersebut.

Metode yang ditimpa oleh deklarasi penimpaan dikenal sebagai metode dasar yang ditimpa. Untuk metode M penimpaan yang dideklarasikan dalam kelas C, metode dasar yang ditimpa ditentukan dengan memeriksa setiap kelas dasar C, dimulai dengan kelas dasar langsung C dan melanjutkan dengan setiap kelas dasar langsung berturut-turut, sampai dalam jenis kelas dasar tertentu setidaknya satu metode yang dapat diakses ditemukan yang memiliki tanda tangan yang sama seperti M setelah substitusi argumen jenis. Untuk tujuan menemukan metode dasar yang ditimpa, metode dianggap dapat diakses jika public, jika protected, jika protected internal, atau jika internal atau private protected dan dideklarasikan dalam program yang sama dengan C.

Kesalahan pada saat kompilasi terjadi kecuali semua hal berikut ini berlaku benar untuk deklarasi penggantian:

  • Metode dasar yang telah di-override dapat ditemukan seperti yang dijelaskan di atas.
  • Hanya ada satu metode dasar yang ditimpa. Pembatasan ini hanya berlaku jika jenis kelas dasar adalah jenis yang dibangun di mana penggantian argumen jenis membuat tanda tangan dua metode sama.
  • Metode dasar yang ditimpa adalah metode virtual, abstrak, atau override. Dengan kata lain, metode dasar yang di-override tidak boleh bersifat statis atau non-virtual.
  • Metode dasar yang diganti bukanlah metode yang disegel.
  • Ada konversi identitas antara tipe pengembalian metode dasar yang di-override dan metode override.
  • Deklarasi penggantian dan metode induk yang digantikan memiliki tingkat aksesibilitas yang sama. Dengan kata lain, deklarasi penggantian tidak dapat mengubah tingkat akses metode virtual. Namun, jika metode dasar yang diambil alih adalah terlindung internal dan dinyatakan dalam rakitan yang berbeda dari rakitan yang berisi deklarasi penimpaan, maka tingkat aksesibilitas yang dinyatakan dari deklarasi penimpaan harus terlindung.
  • Deklarasi override tidak menentukan type_parameter_constraints_clause apa pun. Sebaliknya, batasan tersebut diwarisi dari metode dasar yang diwarisi. Batasan yang merupakan parameter jenis dalam metode yang ditimpa dapat digantikan oleh argumen jenis dalam batasan yang diwariskan. Ini dapat menyebabkan batasan yang tidak valid ketika ditentukan secara eksplisit, seperti jenis nilai atau jenis tersegel.

Contoh: Berikut ini menunjukkan cara kerja aturan penimpaan untuk kelas generik:

abstract class C<T>
{
    public virtual T F() {...}
    public virtual C<T> G() {...}
    public virtual void H(C<T> x) {...}
}

class D : C<string>
{
    public override string F() {...}            // Ok
    public override C<string> G() {...}         // Ok
    public override void H(C<T> x) {...}        // Error, should be C<string>
}

class E<T,U> : C<U>
{
    public override U F() {...}                 // Ok
    public override C<U> G() {...}              // Ok
    public override void H(C<T> x) {...}        // Error, should be C<U>
}

contoh akhir

Deklarasi override dapat mengakses metode dasar yang dioverride menggunakan base_access (§12.8.15).

Contoh: Dalam kode berikut

class A
{
    int x;

    public virtual void PrintFields() => Console.WriteLine($"x = {x}");
}

class B : A
{
    int y;

    public override void PrintFields()
    {
        base.PrintFields();
        Console.WriteLine($"y = {y}");
    }
}

pemanggilan base.PrintFields() dalam B memanggil metode PrintFields yang dideklarasikan dalam A. base_access menonaktifkan mekanisme pemanggilan virtual dan hanya memperlakukan metode dasar sebagai metode non-virtual. Jika pemanggilan B ditulis ((A)this).PrintFields(), itu akan memanggil secara rekursif metode PrintFields yang dideklarasikan dalam B, bukan yang dideklarasikan dalam A, karena PrintFields bersifat virtual dan jenis run-time dari ((A)this) adalah B.

contoh akhir

Hanya dengan menyertakan modifier override sebuah metode dapat mengesampingkan metode lain. Dalam semua kasus lain, metode dengan signatur yang sama dengan metode yang diwariskan hanya menyembunyikan metode yang diwariskan.

Contoh: Dalam kode berikut

class A
{
    public virtual void F() {}
}

class B : A
{
    public virtual void F() {} // Warning, hiding inherited F()
}

F metode dalam B tidak termasuk pengubah override dan oleh karena itu tidak meng-override metode dalam F. Sebaliknya, metode F dalam B menyembunyikan metode dalam A, dan peringatan dilaporkan karena deklarasi tidak menyertakan pengubah baru.

contoh akhir

Contoh: Dalam kode berikut

class A
{
    public virtual void F() {}
}

class B : A
{
    private new void F() {} // Hides A.F within body of B
}

class C : B
{
    public override void F() {} // Ok, overrides A.F
}

F metode dalam B menyembunyikan metode virtual F yang diwarisi dari A. Karena F yang baru di B memiliki akses privat, cakupannya hanya mencakup badan kelas B dan tidak meluas ke C. Oleh karena itu, deklarasi F dalam C diizinkan untuk menggantikan F yang diwarisi dari A.

contoh akhir

15.6.6 Metode yang disegel

Ketika deklarasi metode instans menyertakan sealed pengubah, metode tersebut dikatakan sebagai metode yang disegel. Metode `sealed` menimpa metode virtual yang diwarisi dengan tanda tangan yang sama. Metode yang disegel juga harus ditandai dengan modifier override. Penggunaan pengubah sealed mencegah kelas turunan mengambil alih metode lebih lanjut.

Contoh: Contoh

class A
{
    public virtual void F() => Console.WriteLine("A.F");
    public virtual void G() => Console.WriteLine("A.G");
}

class B : A
{
    public sealed override void F() => Console.WriteLine("B.F");
    public override void G()        => Console.WriteLine("B.G");
}

class C : B
{
    public override void G() => Console.WriteLine("C.G");
}

kelas B menyediakan dua metode penimpaan: metode F yang memiliki modifier sealed dan metode G tanpa modifier. B menggunakan pengubah sealed untuk mencegah C agar tidak lebih lanjut menimpa F.

contoh akhir

15.6.7 Metode abstrak

Ketika deklarasi metode instans menyertakan pengubah abstract , metode tersebut dikatakan sebagai metode abstrak. Meskipun metode abstrak secara implisit juga merupakan metode virtual, metode tersebut tidak dapat memiliki pengubah virtual.

Deklarasi metode abstrak memperkenalkan metode virtual baru tetapi tidak memberikan implementasi metode tersebut. Sebaliknya, kelas turunan non-abstrak diperlukan untuk menyediakan implementasi mereka sendiri dengan mengambil alih metode tersebut. Karena metode abstrak tidak memberikan implementasi aktual, isi metode metode abstrak hanya terdiri dari titik koma.

Deklarasi metode abstrak hanya diizinkan dalam kelas abstrak (§15.2.2.2).

Contoh: Dalam kode berikut

public abstract class Shape
{
    public abstract void Paint(Graphics g, Rectangle r);
}

public class Ellipse : Shape
{
    public override void Paint(Graphics g, Rectangle r) => g.DrawEllipse(r);
}

public class Box : Shape
{
    public override void Paint(Graphics g, Rectangle r) => g.DrawRect(r);
}

Shape kelas mendefinisikan gagasan abstrak dari objek bentuk geometris yang dapat melukis dirinya sendiri. Metode Paint ini abstrak karena tidak ada implementasi default yang bermakna. Kelas Ellipse dan Box adalah implementasi konkret Shape . Karena kelas-kelas ini non-abstrak, mereka diharuskan untuk mengganti metode Paint dan memberikan implementasi yang sebenarnya.

contoh akhir

Merupakan kesalahan waktu kompilasi bagi base_access (§12.8.15) untuk mereferensikan metode abstrak.

Contoh: Dalam kode berikut

abstract class A
{
    public abstract void F();
}

class B : A
{
    // Error, base.F is abstract
    public override void F() => base.F();
}

Kesalahan waktu kompilasi dilaporkan untuk pemanggilan base.F() karena mereferensikan metode abstrak.

contoh akhir

Deklarasi metode abstrak diizinkan untuk mengambil alih metode virtual. Ini memungkinkan kelas abstrak untuk memaksa implementasi ulang metode dalam kelas turunan, dan membuat implementasi asli metode tidak tersedia.

Contoh: Dalam kode berikut

class A
{
    public virtual void F() => Console.WriteLine("A.F");
}

abstract class B: A
{
    public abstract override void F();
}

class C : B
{
    public override void F() => Console.WriteLine("C.F");
}

kelas A menyatakan metode virtual, kelas B mengambil alih metode ini dengan metode abstrak, dan kelas C mengambil alih metode abstrak untuk memberikan implementasinya sendiri.

contoh akhir

15.6.8 Metode eksternal

Ketika deklarasi metode menyertakan pengubah extern , metode ini dikatakan sebagai metode eksternal. Metode eksternal diimplementasikan secara eksternal, biasanya menggunakan bahasa selain C#. Karena deklarasi metode eksternal tidak memberikan implementasi aktual, isi metode metode eksternal hanya terdiri dari titik koma. Metode eksternal tidak boleh generik.

Mekanisme di mana tautan ke metode eksternal dicapai ditentukan oleh implementasi.

Contoh: Contoh berikut menunjukkan penggunaan extern pengubah dan DllImport atribut :

class Path
{
    [DllImport("kernel32", SetLastError=true)]
    static extern bool CreateDirectory(string name, SecurityAttribute sa);

    [DllImport("kernel32", SetLastError=true)]
    static extern bool RemoveDirectory(string name);

    [DllImport("kernel32", SetLastError=true)]
    static extern int GetCurrentDirectory(int bufSize, StringBuilder buf);

    [DllImport("kernel32", SetLastError=true)]
    static extern bool SetCurrentDirectory(string name);
}

contoh akhir

15.6.9 Metode parsial

Ketika deklarasi metode menyertakan partial pengubah, metode tersebut dikatakan sebagai metode parsial. Metode parsial hanya dapat dinyatakan sebagai anggota jenis parsial (§15.2.7), dan tunduk pada sejumlah batasan.

Metode parsial dapat didefinisikan dalam satu bagian dari deklarasi jenis dan diimplementasikan di bagian lain. Implementasinya bersifat opsional; jika tidak ada bagian yang mengimplementasikan metode parsial, deklarasi metode parsial dan semua panggilan ke dalamnya dihapus dari deklarasi jenis yang dihasilkan dari kombinasi bagian.

Metode parsial tidak akan menentukan pengubah akses; mereka secara implisit privat. Jenis pengembaliannya adalah void, dan parameternya tidak boleh menjadi parameter output. Pengidentifikasi partial dikenali sebagai kata kunci kontekstual (§6,4,4) dalam deklarasi metode hanya jika muncul tepat sebelum kata kunci void. Metode parsial tidak dapat menerapkan metode antarmuka secara eksplisit.

Ada dua jenis deklarasi metode parsial: Jika isi deklarasi metode adalah titik koma, deklarasi dikatakan sebagai deklarasi metode parsial yang menentukan. Jika bagian utama selain dari titik koma, deklarasi itu disebut sebagai implementing partial method declaration. Di seluruh bagian deklarasi jenis, hanya akan ada satu deklarasi metode parsial yang mendefinisikan dengan tanda tangan tertentu, dan paling banyak hanya ada satu penerapan deklarasi metode parsial dengan tanda tangan tertentu. Jika penerapan deklarasi metode parsial diberikan, harus ada deklarasi metode parsial yang sesuai, dan deklarasi tersebut harus cocok seperti yang ditentukan dalam hal berikut.

  • Deklarasi harus memiliki pengubah yang sama (meskipun tidak harus dalam urutan yang sama), nama metode, jumlah parameter jenis dan jumlah parameter.
  • Parameter yang sesuai dalam deklarasi harus memiliki modifier yang sama (meskipun tidak harus dalam urutan yang sama) dan tipe yang sama, atau tipe yang dapat diubah menjadi identitas (dengan pengecualian perbedaan dalam nama parameter tipe).
  • Parameter jenis yang sesuai dalam deklarasi harus memiliki batasan yang sama (perbedaan modulo dalam nama parameter jenis).

Deklarasi metode parsial yang diterapkan dapat muncul di bagian yang sama dengan deklarasi metode parsial yang mendefinisikan.

Hanya metode parsial yang ditentukan yang berpartisipasi dalam penyelesaian overload. Dengan demikian, apakah deklarasi penerapan diberikan atau tidak, ekspresi pemanggilan dapat mengarah pada pemanggilan metode parsial. Karena metode parsial selalu mengembalikan void, ekspresi pemanggilan itu akan selalu menjadi pernyataan ekspresi. Selain itu, karena metode parsial adalah secara implisit private, pernyataan tersebut akan selalu terjadi dalam salah satu bagian dari deklarasi tipe di mana metode parsial itu dideklarasikan.

Catatan: Definisi pencocokan mendefinisikan dan menerapkan deklarasi metode parsial tidak memerlukan nama parameter yang cocok. Ini dapat menghasilkan perilaku yang mengejutkan, meskipun terdefinisi dengan baik, ketika argumen bernama (§12.6.2.1) digunakan. Misalnya, dengan deklarasi metode parsial yang mendefinisikan M di satu file, dan deklarasi metode parsial yang mengimplementasikan di file lain.

// File P1.cs:
partial class P
{
    static partial void M(int x);
}

// File P2.cs:
partial class P
{
    static void Caller() => M(y: 0);
    static partial void M(int y) {}
}

adalah tidak valid karena pemanggilan menggunakan nama argumen dari pengimplementasian dan bukan deklarasi metode parsial yang mendefinisikan.

catatan akhir

Jika tidak ada bagian dari deklarasi jenis parsial yang berisi deklarasi penerapan untuk metode parsial tertentu, pernyataan ekspresi apa pun yang memanggilnya hanya dihapus dari deklarasi jenis gabungan. Ekspresi pemanggilan, termasuk segala subekspresi, tidak memiliki efek saat waktu eksekusi. Metode parsial itu sendiri juga dihapus dan tidak akan menjadi anggota deklarasi jenis gabungan.

Jika deklarasi penerapan ada untuk metode parsial tertentu, pemanggilan metode parsial dipertahankan. Metode parsial memunculkan deklarasi metode yang mirip dengan penerapan deklarasi metode parsial kecuali untuk yang berikut:

  • Pengubah partial tidak disertakan.

  • Atribut dalam deklarasi metode yang dihasilkan adalah atribut gabungan dari yang menentukan dan menerapkan deklarasi metode parsial dalam urutan yang tidak ditentukan. Duplikat tidak dihapus.

  • Atribut pada parameter deklarasi metode yang dihasilkan adalah atribut gabungan dari parameter yang sesuai dari yang menentukan dan menerapkan deklarasi metode parsial dalam urutan yang tidak ditentukan. Duplikat tidak dihapus.

Jika deklarasi yang mendefinisikan tetapi bukan deklarasi penerapan diberikan untuk metode Mparsial , pembatasan berikut berlaku:

  • Ini adalah kesalahan saat kompilasi untuk membuat delegasi dari M (§12.8.17.5).

  • Ini adalah kesalahan saat waktu kompilasi untuk merujuk ke M dalam fungsi anonim yang dikonversi ke tipe pohon ekspresi (§8.6).

  • Ekspresi yang terjadi sebagai bagian dari pemanggilan M tidak memengaruhi status penugasan definitif (§9.4), yang berpotensi menyebabkan kesalahan saat waktu kompilasi.

  • M tidak dapat menjadi titik masuk untuk aplikasi (§7.1).

Metode parsial berguna untuk memungkinkan satu bagian dari deklarasi jenis untuk menyesuaikan perilaku bagian lain, misalnya, yang dihasilkan oleh alat. Pertimbangkan deklarasi kelas parsial berikut ini:

partial class Customer
{
    string name;

    public string Name
    {
        get => name;
        set
        {
            OnNameChanging(value);
            name = value;
            OnNameChanged();
        }
    }

    partial void OnNameChanging(string newName);
    partial void OnNameChanged();
}

Jika kelas ini dikompilasi tanpa bagian lain, deklarasi metode parsial yang menentukan dan pemanggilannya akan dihapus, dan deklarasi kelas gabungan yang dihasilkan akan setara dengan yang berikut:

class Customer
{
    string name;

    public string Name
    {
        get => name;
        set => name = value;
    }
}

Asumsikan bahwa bagian lain diberikan, yang menyediakan deklarasi penerapan metode parsial.

partial class Customer
{
    partial void OnNameChanging(string newName) =>
        Console.WriteLine($"Changing {name} to {newName}");

    partial void OnNameChanged() =>
        Console.WriteLine($"Changed to {name}");
}

Kemudian deklarasi kelas gabungan yang dihasilkan akan setara dengan yang berikut:

class Customer
{
    string name;

    public string Name
    {
        get => name;
        set
        {
            OnNameChanging(value);
            name = value;
            OnNameChanged();
        }
    }

    void OnNameChanging(string newName) =>
        Console.WriteLine($"Changing {name} to {newName}");

    void OnNameChanged() =>
        Console.WriteLine($"Changed to {name}");
}

15.6.10 Metode ekstensi

Ketika parameter pertama metode menyertakan pengubah this , metode tersebut dikatakan sebagai metode ekstensi. Metode ekstensi hanya boleh dideklarasikan dalam kelas statis non-generik dan tidak bersarang. Parameter pertama metode ekstensi dibatasi, sebagai berikut:

  • Ini mungkin hanya menjadi parameter input jika memiliki tipe nilai
  • Ini mungkin hanya menjadi parameter referensi jika memiliki tipe nilai atau memiliki tipe generik yang dibatasi pada struct.
  • Ini tidak boleh menjadi tipe pointer.

Contoh: Berikut ini adalah contoh kelas statis yang mendeklarasikan dua metode ekstensi:

public static class Extensions
{
    public static int ToInt32(this string s) => Int32.Parse(s);

    public static T[] Slice<T>(this T[] source, int index, int count)
    {
        if (index < 0 || count < 0 || source.Length - index < count)
        {
            throw new ArgumentException();
        }
        T[] result = new T[count];
        Array.Copy(source, index, result, 0, count);
        return result;
    }
}

contoh akhir

Metode ekstensi adalah metode statis reguler. Selain itu, di mana kelas statis yang tertutup berada dalam cakupan, metode ekstensi dapat dipanggil menggunakan sintaks pemanggilan metode instans (§12.8.10.3), menggunakan ekspresi penerima sebagai argumen pertama.

Contoh: Program berikut menggunakan metode ekstensi yang dinyatakan di atas:

static class Program
{
    static void Main()
    {
        string[] strings = { "1", "22", "333", "4444" };
        foreach (string s in strings.Slice(1, 2))
        {
            Console.WriteLine(s.ToInt32());
        }
    }
}

Metode Slice ini tersedia pada string[], dan ToInt32 metode tersedia di string, karena metode tersebut telah dinyatakan sebagai metode ekstensi. Arti program sama dengan yang berikut ini, menggunakan panggilan metode statis biasa:

static class Program
{
    static void Main()
    {
        string[] strings = { "1", "22", "333", "4444" };
        foreach (string s in Extensions.Slice(strings, 1, 2))
        {
            Console.WriteLine(Extensions.ToInt32(s));
        }
    }
}

contoh akhir

15.6.11 Isi metode

Badan metode dari deklarasi metode terdiri dari badan blok, badan ekspresi, atau tanda titik koma.

Deklarasi metode abstrak dan eksternal tidak menyediakan implementasi metode, sehingga badan metode mereka hanya terdiri dari sebuah titik koma. Untuk metode lain, isi metode adalah blok (§13.3) yang berisi pernyataan yang akan dijalankan ketika metode tersebut dipanggil.

Jenis pengembalian metode yang efektif adalah void jika jenis pengembalian adalah void, atau jika metode asinkron dan jenis pengembaliannya adalah «TaskType» (§15.14.1). Jika tidak, jenis pengembalian efektif dari metode non-asinkron adalah jenis pengembaliannya, dan jenis pengembalian efektif dari metode asinkron dengan jenis «TaskType»<T>pengembalian (§15.14.1) adalah T.

Ketika jenis pengembalian yang efektif dari suatu metode adalah void dan metode memiliki badan blok, pernyataan return (§13.10.5) di dalam blok tidak boleh menentukan ekspresi. Jika eksekusi blok dari sebuah metode void selesai dengan normal (yaitu, kontrol mengalir sampai ke akhir tubuh metode), metode tersebut hanya akan mengembalikan ke pemanggilnya.

Ketika jenis pengembalian yang efektif dari metode adalah void dan metode memiliki isi ekspresi, ekspresi E harus menjadi statement_expression, dan isinya persis setara dengan badan blok formulir { E; }.

Untuk metode returns-by-value (§15.6.1), setiap pernyataan pengembalian dalam isi metode tersebut akan menentukan ekspresi yang secara implisit dapat dikonversi ke jenis pengembalian yang efektif.

Untuk metode returns-by-ref (§15.6.1), setiap pernyataan pengembalian dalam isi metode tersebut harus menentukan ekspresi yang jenisnya adalah jenis pengembalian yang efektif, dan memiliki konteks ref-safe dari caller-context (§9.7.2).

Untuk metode returns-by-value dan returns-by-ref, titik akhir isi metode tidak akan dapat dijangkau. Dengan kata lain, aliran kontrol tidak diizinkan keluar dari akhir badan metode.

Contoh: Dalam kode berikut

class A
{
    public int F() {} // Error, return value required

    public int G()
    {
        return 1;
    }

    public int H(bool b)
    {
        if (b)
        {
            return 1;
        }
        else
        {
            return 0;
        }
    }

    public int I(bool b) => b ? 1 : 0;
}

metode yang mengembalikan nilai F menghasilkan kesalahan waktu kompilasi karena aliran kontrol bisa keluar dari akhir tubuh metode. Metode G dan H benar karena semua jalur eksekusi yang mungkin berakhir dengan pernyataan pengembalian yang menentukan nilai pengembalian. Metode I ini benar, karena isinya setara dengan blok hanya dengan satu pernyataan pengembalian di dalamnya.

contoh akhir

15.7 Properti

15.7.1 Umum

Properti adalah anggota yang menyediakan akses ke karakteristik objek atau kelas. Contoh properti termasuk panjang string, ukuran font, keterangan jendela, dan nama pelanggan. Properti adalah perpanjangan alami dari lapangan—keduanya merupakan anggota bernama dengan tipe terkait, dan sintaks untuk mengakses lapangan dan properti sama. Namun, tidak seperti field, properti tidak merepresentasikan lokasi penyimpanan. Sebagai gantinya, properti memiliki aksesor yang menentukan pernyataan yang akan dijalankan saat nilainya dibaca atau ditulis. Properti dengan demikian menyediakan mekanisme untuk mengaitkan tindakan dengan membaca dan menulis karakteristik objek atau kelas; selain itu, mereka mengizinkan karakteristik tersebut untuk dihitung.

Properti dinyatakan menggunakan property_declaration:

property_declaration
    : attributes? property_modifier* type member_name property_body
    | attributes? property_modifier* ref_kind type member_name ref_property_body
    ;    

property_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | 'static'
    | 'virtual'
    | 'sealed'
    | 'override'
    | 'abstract'
    | 'extern'
    | 'readonly'        // direct struct members only
    | unsafe_modifier   // unsafe code support
    ;
    
property_body
    : '{' accessor_declarations '}' property_initializer?
    | '=>' expression ';'
    ;

property_initializer
    : '=' variable_initializer ';'
    ;

ref_property_body
    : '{' ref_get_accessor_declaration '}'
    | '=>' 'ref' variable_reference ';'
    ;

unsafe_modifier (§23.2) hanya tersedia dalam kode tidak aman (§23).

Property_declaration dapat mencakup sekumpulan atribut (§22) dan salah satu jenis aksesibilitas yang dinyatakan yang diizinkan (§15.3.6), new (§15.3.5), static (§15.7.2), virtual (§15.6.4, §15.7.6), override (§15.6.5, §15.7.6), sealed (§15.6.6), abstract (§15.6.7, §15.7.6) dan extern (§15.6.8). Selain itu, property_declaration yang langsung dimuat oleh struct_declaration dapat memasukkan pengubah readonly (§16.4.11).

  • Yang pertama mendeklarasikan properti bernilai non-ref. Nilainya memiliki jenis type. Properti semacam ini mungkin dapat dibaca dan/atau dapat ditulis.
  • Yang kedua mendeklarasikan properti bernilai referensi. Nilainya adalah variable_reference (§9.5), yang mungkin readonly, ke variabel dengan tipe type. Properti jenis ini hanya dapat dibaca.

Property_declaration dapat mencakup sekumpulan atribut (§22) dan salah satu jenis aksesibilitas yang dinyatakan yang diizinkan (§15.3.6), new (§15.3.5), static (§15.7.2), virtual (§15.6.4, §15.7.6), override (§15.6.5, §15.7.6), sealed (§15.6.6), abstract (§15.6.7, §15.7.6), dan extern (§15.6.8) pengubah.

Deklarasi properti tunduk pada aturan yang sama dengan deklarasi metode (§15,6) sehubungan dengan kombinasi pengubah yang valid.

member_name (§15.6.1) menentukan nama properti. Kecuali properti adalah implementasi anggota antarmuka eksplisit, member_name hanyalah pengidentifikasi. Untuk implementasi anggota antarmuka eksplisit (§18.6.2), member_name terdiri dari interface_type diikuti oleh "." dan pengidentifikasi.

Jenis properti harus setidaknya sama dapat diaksesnya dengan properti itu sendiri (§7.5.5).

Property_body dapat terdiri dari isi pernyataan atau isi ekspresi. Dalam tubuh pernyataan, deklarasi_akses, yang harus dilampirkan dalam token “{” dan “}”, mendeklarasikan akseor (§15.7.3) dari properti. Aksesor menentukan pernyataan yang dapat dieksekusi yang terkait dengan pembacaan dan penulisan properti.

Isi ekspresi dalam property_body terdiri dari => yang diikuti oleh ekspresiE dan titik koma, yang sama persis dengan isi { get { return E; } } pernyataan. Oleh karena itu, hanya dapat digunakan untuk menentukan properti baca-saja di mana hasil dari aksesor get ditentukan oleh ekspresi tunggal.

property_initializer hanya dapat diberikan untuk properti yang diimplementasikan secara otomatis (§15.7.4), dan menyebabkan inisialisasi bidang yang mendasar dari properti tersebut dengan nilai yang diberikan oleh ekspresi.

ref_property_body dapat terdiri dari isi pernyataan atau isi ekspresi. Dalam badan pernyataan, get_accessor_declaration menyatakan get accessor (§15.7.3) dari properti. Aksesornya menentukan pernyataan-pernyataan yang dapat dieksekusi terkait dengan pembacaan properti tersebut.

Dalam isi dari ref_property_body, ekspresi yang terdiri dari => diikuti oleh ref, variable_referenceV dan titik koma tersebut adalah sama persis dengan isi dari { get { return ref V; } }.

Catatan: Meskipun sintaks untuk mengakses properti sama dengan untuk bidang, properti tidak diklasifikasikan sebagai variabel. Dengan demikian, tidak dimungkinkan untuk meneruskan properti sebagai argumen in, out, atau ref kecuali properti bernilai referensi dan oleh karena itu mengembalikan referensi variabel (§9.7). catatan akhir

Ketika deklarasi properti menyertakan pengubah extern , properti dikatakan sebagai properti eksternal. Karena deklarasi properti eksternal tidak memberikan implementasi aktual, masing-masing accessor_body, dalam accessor_declarations harus berupa titik koma.

15.7.2 Properti statis dan instans

Ketika deklarasi properti menyertakan static pengubah, properti dikatakan sebagai properti statis. Ketika tidak ada static pengubah yang ada, properti dikatakan sebagai properti instans.

Properti statis tidak terkait dengan instans tertentu, dan terjadi kesalahan saat kompilasi jika merujuk ke this dalam aksesori properti statis.

Properti instans dikaitkan dengan instans kelas tertentu, dan instans tersebut dapat diakses sebagai this (§12.8.14) di aksesor properti tersebut.

Perbedaan antara anggota statis dan instans dibahas lebih lanjut dalam §15.3.8.

15.7.3 Aksesor

Catatan: Klausa ini berlaku untuk properti (§15.7) dan pengindeks (§15.9). Klausa ditulis dalam konteks properti, saat membaca untuk pengindeks, gantikan pengindeks/pengindeks dengan properti/properti dan periksa daftar perbedaan antara properti dan pengindeks yang diberikan dalam §15.9.2. catatan akhir

Accessor_declarations dari sebuah properti menentukan pernyataan eksekusi yang terkait dengan penulisan dan/atau pembacaan properti.

accessor_declarations
    : get_accessor_declaration set_accessor_declaration?
    | set_accessor_declaration get_accessor_declaration?
    ;

get_accessor_declaration
    : attributes? accessor_modifier? 'get' accessor_body
    ;

set_accessor_declaration
    : attributes? accessor_modifier? 'set' accessor_body
    ;

accessor_modifier
    : 'protected'
    | 'internal'
    | 'private'
    | 'protected' 'internal'
    | 'internal' 'protected'
    | 'protected' 'private'
    | 'private' 'protected'
    | 'readonly'        // direct struct members only
    ;

accessor_body
    : block
    | '=>' expression ';'
    | ';' 
    ;

ref_get_accessor_declaration
    : attributes? accessor_modifier? 'get' ref_accessor_body
    ;
    
ref_accessor_body
    : block
    | '=>' 'ref' variable_reference ';'
    | ';'
    ;

accessor_declarations terdiri dari get_accessor_declaration, set_accessor_declaration, atau keduanya. Setiap deklarasi aksesor terdiri dari atribut opsional, accessor_modifier opsional , token atau , diikuti oleh accessor_body .

Untuk properti bernilai ref, ref_get_accessor_declaration terdiri dari atribut opsional, accessor_modifier opsional, token get, diikuti oleh ref_accessor_body.

Penggunaan accessor_modifierdiatur oleh batasan berikut:

  • Accessor_modifier tidak boleh digunakan dalam antarmuka atau dalam implementasi anggota antarmuka eksplisit.
  • accessor_modifierreadonly hanya diizinkan dalam property_declaration atau indexer_declaration yang dimuat langsung oleh struct_declaration (§16.4.11, §16.4.13).
  • Untuk properti atau pengindeks yang tidak memiliki pengubah override, accessor_modifier hanya diizinkan jika properti atau pengindeks tersebut memiliki keduanya, aksesor get dan set, dan kemudian pengubah hanya diizinkan pada salah satu dari aksesor tersebut.
  • Untuk properti atau pengindeks yang menyertakan pengubah override, pengakses harus cocok dengan accessor_modifier, jika ada, dari pengakses yang digantikan.
  • accessor_modifier harus menyatakan aksesibilitas yang lebih ketat secara tegas daripada aksesibilitas yang dinyatakan untuk properti atau pengindeks itu sendiri. Tepatnya:
    • Jika properti atau pengindeks memiliki aksesibilitas yang dideklarasikan sebagai public, maka aksesibilitas yang dideklarasikan oleh accessor_modifier dapat berupa private protected, protected internal, internal, protected, atau private.
    • Jika properti atau pengindeks memiliki aksesibilitas yang dideklarasikan sebagai protected internal, maka aksesibilitas yang dideklarasikan oleh accessor_modifier dapat berupa private protected, protected private, internal, protected, atau private.
    • Jika properti atau pengindeks memiliki aksesibilitas yang dinyatakan sebagai internal atau protected, maka aksesibilitas yang dinyatakan oleh accessor_modifier haruslah private protected atau private.
    • Jika properti atau pengindeks memiliki aksesibilitas yang dideklarasikan sebagai private protected, maka aksesibilitas yang dinyatakan oleh accessor_modifier harus private.
    • Jika properti atau pengindeks memiliki aksesibilitas yang dinyatakan sebagai private, maka tidak ada accessor_modifier yang dapat digunakan.

Untuk properti abstract dan properti yang tidak bernilai ref extern, akses apa pun untuk masing-masing aksesor yang ditentukan hanyalah berupa titik koma. Properti non-abstrak, non-ekstern, tetapi bukan pengindeks dapat memiliki badan aksesor untuk semua aksesor yang ditentukan berupa titik koma; dalam kasus ini, merupakan properti yang diimplementasikan secara otomatis (§15.7.4). Properti yang diimplementasikan secara otomatis harus memiliki setidaknya satu aksesori pengambilan. Untuk pengakses properti lain yang non-abstrak dan non-extern, accessor_body adalah salah satu dari berikut:

  • blok yang menentukan pernyataan yang akan dijalankan ketika aksesor yang sesuai dipanggil; atau
  • isi ekspresi, yang terdiri dari => diikuti oleh ekspresi dan titik koma, dan menunjukkan ekspresi tunggal yang akan dijalankan saat aksesor yang sesuai dipanggil.

Untuk properti bernilai ref abstract dan extern, ref_accessor_body hanyalah sebuah titik koma. Untuk aksesor dari properti non-abstrak dan non-extern lainnya, ref_accessor_body adalah salah satu:

  • blok yang menentukan pernyataan yang akan dijalankan ketika aksesor get dipanggil; atau
  • badan ekspresi, yang terdiri dari => diikuti oleh ref, variable_reference dan titik koma. Referensi variabel dievaluasi ketika aksesor get dipanggil.

Akses get untuk properti yang bernilai non-ref adalah metode tanpa parameter dengan pengembalian nilai dari tipe properti tersebut. Kecuali sebagai target penugasan, ketika properti tersebut direferensikan dalam ekspresi, aksesor get-nya dipanggil untuk menghitung nilai properti (§12.2.2).

Badan get accessor untuk properti tanpa nilai ref harus sesuai dengan aturan untuk metode pengembalian nilai yang dijelaskan di §15.6.11. Secara khusus, semua pernyataan return dalam tubuh sebuah pengakses get harus menentukan ekspresi yang dapat dikonversi secara implisit ke tipe properti. Lebih lanjut, titik akhir dari pengakses get tidak boleh dapat dijangkau.

Sebuah aksesor get untuk properti bernilai ref sesuai dengan metode tanpa parameter dengan nilai pengembalian variable_reference ke variabel dari jenis properti. Ketika properti seperti itu direferensikan dalam ekspresi, aksesor get-nya dipanggil untuk menghitung nilai variable_reference properti. Referensi variabel itu, seperti referensi yang lainnya, kemudian digunakan untuk membaca atau, untuk referensi_variabel yang tidak readonly, menulis variabel yang dirujuk sesuai dengan kebutuhan konteks.

Contoh: Contoh berikut mengilustrasikan properti bernilai ref sebagai target penugasan:

class Program
{
    static int field;
    static ref int Property => ref field;

    static void Main()
    {
        field = 10;
        Console.WriteLine(Property); // Prints 10
        Property = 20;               // This invokes the get accessor, then assigns
                                     // via the resulting variable reference
        Console.WriteLine(field);    // Prints 20
    }
}

contoh akhir

Badan dari get accessor untuk properti bernilai ref harus sesuai dengan aturan untuk metode bernilai ref yang dijelaskan dalam §15.6.11.

Aksesor set berhubungan dengan sebuah metode yang memiliki parameter nilai tunggal dari jenis properti dan void jenis pengembalian. Parameter implisit dari aksesor set selalu diberi nama value. Saat properti direferensikan sebagai sasaran penugasan (§12.21), atau sebagai operan atau ++ (§12.8.16, §12.9.6), aksesor 'set' dipanggil dengan argumen untuk memberikan nilai baru (§12.21.2). Isi aksesor yang ditetapkan harus sesuai dengan aturan untuk void metode yang diuraikan dalam §15.6.11. Khususnya, pernyataan return dalam badan set accessor tidak diizinkan untuk menspesifikasikan sebuah ekspresi. Karena aksesor set secara implisit memiliki parameter dengan nama value, ini adalah kesalahan kompilasi jika deklarasi variabel lokal atau konstanta dalam aksesor set memiliki nama tersebut.

Berdasarkan ada atau tidaknya akses get dan set, properti diklasifikasikan sebagai berikut:

  • Properti yang mencakup aksesor get dan aksesor set disebut sebagai properti baca-tulis.
  • Properti yang hanya memiliki aksesor get disebut sebagai "properti baca-saja". Ini adalah kesalahan waktu kompilasi jika properti baca-saja menjadi target penugasan.
  • Properti yang hanya memiliki aksesor set disebut sebagai properti tulis-saja. Kecuali jika sebagai target penugasan, adalah kesalahan waktu kompilasi untuk merujuk properti yang hanya dapat ditulis dalam sebuah ekspresi.

Catatan: Operator pra-dan pascafiks ++-- serta operator penugasan gabungan tidak dapat diterapkan pada properti tulis-saja, karena operator-operator ini membutuhkan pembacaan nilai operand lama sebelum menulis nilai baru. catatan akhir

Contoh: Dalam kode berikut

public class Button : Control
{
    private string caption;

    public string Caption
    {
        get => caption;
        set
        {
            if (caption != value)
            {
                caption = value;
                Repaint();
            }
        }
    }

    public override void Paint(Graphics g, Rectangle r)
    {
        // Painting code goes here
    }
}

Button kontrol mendeklarasikan properti publikCaption. Akses dari properti Keterangan mengembalikan string yang disimpan di dalam bidang privat caption. Akses set memeriksa apakah nilai baru berbeda dari nilai saat ini, dan jika ya, ia menyimpan nilai baru tersebut dan mengecat ulang kontrol. Properti sering mengikuti pola yang ditunjukkan di atas: aksesor get hanya mengembalikan nilai yang disimpan di private bidang, dan aksesor set memodifikasi bidang tersebut dan kemudian melakukan tindakan tambahan yang private diperlukan untuk memperbarui sepenuhnya keadaan objek. Mengingat kelas di Button atas, berikut ini adalah contoh penggunaan Caption properti:

Button okButton = new Button();
okButton.Caption = "OK"; // Invokes set accessor
string s = okButton.Caption; // Invokes get accessor

Di sini, aksesor set dipanggil dengan menetapkan nilai ke properti , dan aksesor get dipanggil dengan mereferensikan properti dalam ekspresi.

contoh akhir

Akses get dan set dari properti bukanlah anggota yang berbeda, dan tidak dimungkinkan untuk mendeklarasikan akses dari properti secara terpisah.

Contoh: Contoh

class A
{
    private string name;

    // Error, duplicate member name
    public string Name
    { 
        get => name;
    }

    // Error, duplicate member name
    public string Name
    { 
        set => name = value;
    }
}

tidak mendeklarasikan satu pun properti baca-tulis. Sebaliknya, itu menyatakan dua properti dengan nama yang sama; satu hanya-baca dan satu hanya-tulis. Karena dua anggota yang dinyatakan dalam kelas yang sama tidak dapat memiliki nama yang sama, contoh menyebabkan kesalahan waktu kompilasi terjadi.

contoh akhir

Ketika kelas turunan mendeklarasikan properti dengan nama yang sama seperti properti yang diwarisi, properti turunan menyembunyikan properti yang diwarisi sehubungan dengan pembacaan dan penulisan.

Contoh: Dalam kode berikut

class A
{
    public int P
    {
        set {...}
    }
}

class B : A
{
    public new int P
    {
        get {...}
    }
}

Properti P di B menyembunyikan properti P di A baik dalam hal membaca maupun menulis. Oleh karena itu, dalam pernyataan

B b = new B();
b.P = 1;       // Error, B.P is read-only
((A)b).P = 1;  // Ok, reference to A.P

Penugasan terhadap b.P menyebabkan kesalahan saat kompilasi dilaporkan, karena properti baca-saja P di B menyembunyikan properti tulis-saja P di A. Namun, perlu dicatat bahwa sebuah cast dapat digunakan untuk mengakses properti P yang tersembunyi.

contoh akhir

Tidak seperti bidang publik, properti menyediakan pemisahan antara status internal objek dan antarmuka publiknya.

Contoh: Pertimbangkan kode berikut, yang menggunakan Point struct untuk mewakili lokasi:

class Label
{
    private int x, y;
    private string caption;

    public Label(int x, int y, string caption)
    {
        this.x = x;
        this.y = y;
        this.caption = caption;
    }

    public int X => x;
    public int Y => y;
    public Point Location => new Point(x, y);
    public string Caption => caption;
}

Di sini, Label kelas menggunakan dua int bidang, x dan y, untuk menyimpan lokasinya. Lokasi diekspos secara publik baik sebagai properti X dan Y maupun sebagai properti Location jenis Point. Jika, dalam versi Label yang akan datang, menjadi lebih praktis untuk menyimpan lokasi sebagai internal Point, perubahan dapat dilakukan tanpa memengaruhi antarmuka kelas yang publik.

class Label
{
    private Point location;
    private string caption;

    public Label(int x, int y, string caption)
    {
        this.location = new Point(x, y);
        this.caption = caption;
    }

    public int X => location.X;
    public int Y => location.Y;
    public Point Location => location;
    public string Caption => caption;
}

Seandainya x dan y adalah atribut public readonly, tidak mungkin untuk membuat perubahan seperti itu pada kelas Label.

contoh akhir

Catatan: Mengekspos status melalui properti tidak selalu kurang efisien daripada mengekspos bidang secara langsung. Terutama, ketika properti non-virtual dan hanya berisi sejumlah kecil kode, lingkungan eksekusi dapat menggantikan panggilan ke aksesor dengan kode asli dari aksesor. Proses ini dikenal sebagai inlining, dan membuat akses properti seefisien akses bidang, namun tetap mempertahankan fleksibilitas properti yang lebih tinggi. catatan akhir

Contoh: Karena memanggil aksesor get secara konseptual setara dengan membaca nilai field, dianggap gaya pemrograman yang buruk jika aksesor get memiliki efek samping yang terlihat. Dalam contoh

class Counter
{
    private int next;

    public int Next => next++;
}

nilai Next properti tergantung pada berapa kali properti sebelumnya diakses. Dengan demikian, mengakses properti menghasilkan efek samping yang dapat diamati, dan properti harus diimplementasikan sebagai metode sebagai gantinya.

Konvensi "tidak ada efek samping" untuk pengakses tidak berarti bahwa pengakses harus selalu ditulis secara sederhana hanya untuk mengembalikan nilai yang disimpan di bidang. Memang, fungsi akses get sering menghitung nilai properti dengan mengakses beberapa field atau memanggil metode. Namun, sebuah get accessor yang dirancang dengan baik tidak melakukan tindakan yang menyebabkan perubahan yang dapat diamati dalam keadaan objek.

contoh akhir

Properti dapat digunakan untuk menunda inisialisasi sumber daya sampai saat pertama kali direferensikan.

Contoh:

public class Console
{
    private static TextReader reader;
    private static TextWriter writer;
    private static TextWriter error;

    public static TextReader In
    {
        get
        {
            if (reader == null)
            {
                reader = new StreamReader(Console.OpenStandardInput());
            }
            return reader;
        }
    }

    public static TextWriter Out
    {
        get
        {
            if (writer == null)
            {
                writer = new StreamWriter(Console.OpenStandardOutput());
            }
            return writer;
        }
    }

    public static TextWriter Error
    {
        get
        {
            if (error == null)
            {
                error = new StreamWriter(Console.OpenStandardError());
            }
            return error;
        }
    }
...
}

Kelas Console berisi tiga properti, In, Out, dan Error, yang mewakili perangkat input, output, dan perangkat kesalahan standar. Dengan mengekspos anggota ini sebagai properti, Console kelas dapat menunda inisialisasi mereka sampai mereka benar-benar digunakan. Misalnya, saat pertama kali mengacu pada properti Out, seperti dalam

Console.Out.WriteLine("hello, world");

Dasar TextWriter dari perangkat keluaran telah dibuat. Namun, jika aplikasi tidak membuat referensi ke In properti dan Error , maka tidak ada objek yang dibuat untuk perangkat tersebut.

contoh akhir

15.7.4 Properti yang Diimplementasikan Otomatis

Properti yang diimplementasikan secara otomatis (atau properti otomatis untuk singkatnya) adalah properti non-abstrak, non-ekstern, non-ref-valued dengan accessor_body yang hanya menggunakan titik koma. Auto-property harus memiliki pengakses get dan secara opsional dapat memiliki pengakses set.

Ketika properti ditentukan sebagai properti yang diimplementasikan secara otomatis, bidang penyangga yang tersembunyi secara otomatis tersedia untuk properti tersebut, dan aksesor diimplementasikan untuk membaca dari bidang penyangga dan menulis pada bidang penyangga tersebut. Bidang pendukung tersembunyi tidak dapat diakses; hanya dapat dibaca dan ditulis melalui akses properti yang diimplementasikan secara otomatis, bahkan dalam tipe yang mengandung. Jika properti otomatis tidak memiliki akses penyetel, bidang pendukung dianggap readonly (§15.5.3). Sama seperti readonly field, sebuah auto-property yang hanya-baca juga dapat diberikan nilai di dalam tubuh konstruktor dari kelas yang melingkupinya. Penugasan semacam itu menetapkan langsung ke lapisan pendukung baca-saja dari atribut.

Properti otomatis dapat memiliki property_initializer secara opsional, yang diterapkan langsung ke field pendukung sebagai variable_initializer (§17.7).

Contoh:

public class Point
{
    public int X { get; set; } // Automatically implemented
    public int Y { get; set; } // Automatically implemented
}

setara dengan deklarasi berikut:

public class Point
{
    private int x;
    private int y;

    public int X { get { return x; } set { x = value; } }
    public int Y { get { return y; } set { y = value; } }
}

contoh akhir

Contoh: Dalam hal berikut ini

public class ReadOnlyPoint
{
    public int X { get; }
    public int Y { get; }

    public ReadOnlyPoint(int x, int y)
    {
        X = x;
        Y = y;
    }
}

setara dengan deklarasi berikut:

public class ReadOnlyPoint
{
    private readonly int __x;
    private readonly int __y;
    public int X { get { return __x; } }
    public int Y { get { return __y; } }

    public ReadOnlyPoint(int x, int y)
    {
        __x = x;
        __y = y;
    }
}

Penugasan ke bidang hanya-baca valid, karena terjadi di dalam konstruktor.

contoh akhir

Meskipun bidang pendukung disembunyikan, bidang tersebut dapat memiliki atribut yang ditargetkan untuk bidang yang diterapkan langsung melalui deklarasi properti yang diimplementasikan secara otomatis (§15.7.1).

Contoh: Kode berikut

[Serializable]
public class Foo
{
    [field: NonSerialized]
    public string MySecret { get; set; }
}

menghasilkan atribut NonSerialized yang ditargetkan ke bidang yang diterapkan ke bidang pendukung yang dihasilkan kompilator, seolah-olah kode tersebut telah ditulis sebagai berikut:

[Serializable]
public class Foo
{
    [NonSerialized]
    private string _mySecretBackingField;
    public string MySecret
    {
        get { return _mySecretBackingField; }
        set { _mySecretBackingField = value; }
    }
}

contoh akhir

15.7.5 Aksesibilitas

Jika aksesor memiliki accessor_modifier, domain aksesibilitas (§7.5.3) dari aksesor ditentukan menggunakan aksesibilitas accessor_modifier yang dinyatakan. Jika aksesor tidak memiliki accessor_modifier, domain aksesibilitas aksesor ditentukan dari aksesibilitas properti atau pengindeks yang dinyatakan.

Kehadiran accessor_modifier tidak pernah memengaruhi pencarian anggota (§12,5) atau resolusi kelebihan beban (§12.6.4). Pengubah pada properti atau pengindeks selalu menentukan properti atau pengindeks mana yang terikat, terlepas dari konteks akses.

Setelah properti non-ref-valued tertentu atau pengindeks non-ref-valued dipilih, domain aksesibilitas dari pengakses tertentu yang terlibat digunakan untuk menentukan apakah penggunaan tersebut valid:

  • Jika penggunaannya adalah sebagai nilai (§12.2.2), aksesor get akan ada dan dapat diakses.
  • Jika penggunaan adalah sebagai target penugasan sederhana (§12.21.2), aksesor yang ditetapkan akan ada dan dapat diakses.
  • Jika penggunaan adalah sebagai target penugasan gabungan (§12.21.4), atau sebagai target operator ++ atau -- (§12.8.16, §12.9.6), baik aksesor get maupun aksesor set harus ada dan dapat diakses.

Contoh: Dalam contoh berikut, properti A.Text disembunyikan oleh properti B.Text, bahkan dalam konteks di mana hanya aksesor yang ditetapkan yang dipanggil. Sebaliknya, properti B.Count tidak dapat diakses oleh kelas M, sehingga properti A.Count yang dapat diakses digunakan sebagai gantinya.

class A
{
    public string Text
    {
        get => "hello";
        set { }
    }

    public int Count
    {
        get => 5;
        set { }
    }
}

class B : A
{
    private string text = "goodbye";
    private int count = 0;

    public new string Text
    {
        get => text;
        protected set => text = value;
    }

    protected new int Count
    {
        get => count;
        set => count = value;
    }
}

class M
{
    static void Main()
    {
        B b = new B();
        b.Count = 12;       // Calls A.Count set accessor
        int i = b.Count;    // Calls A.Count get accessor
        b.Text = "howdy";   // Error, B.Text set accessor not accessible
        string s = b.Text;  // Calls B.Text get accessor
    }
}

contoh akhir

Setelah properti ref-valued atau indeks ref-valued tertentu telah dipilih—apakah penggunaannya sebagai nilai, target penugasan sederhana, atau target penugasan majemuk—domain aksesibilitas dari pengakses getter yang terlibat digunakan untuk menentukan apakah penggunaan tersebut valid.

Aksesor yang digunakan untuk mengimplementasikan antarmuka tidak boleh memiliki accessor_modifier. Jika hanya satu aksesor yang digunakan untuk mengimplementasikan antarmuka, aksesor lain dapat dideklarasikan dengan accessor_modifier:

Contoh:

public interface I
{
    string Prop { get; }
}

public class C : I
{
    public string Prop
    {
        get => "April";     // Must not have a modifier here
        internal set {...}  // Ok, because I.Prop has no set accessor
    }
}

contoh akhir

15.7.6 Aksesors Virtual, Tersegel, Meng-override, dan Abstrak

Catatan: Klausa ini berlaku untuk properti (§15.7) dan pengindeks (§15.9). Klausa ditulis dalam konteks properti, saat membaca untuk pengindeks, gantikan pengindeks/pengindeks dengan properti/properti dan periksa daftar perbedaan antara properti dan pengindeks yang diberikan dalam §15.9.2. catatan akhir

Deklarasi properti virtual menentukan bahwa aksesor properti bersifat virtual. Pengubah virtual berlaku untuk semua aksesor bukan privat dari sebuah properti. Ketika pengakses properti virtual memiliki privateaccessor_modifier, aksesor privat secara implisit tidak virtual.

Deklarasi properti abstrak menentukan bahwa aksesor properti bersifat virtual, tetapi tidak memberikan implementasi aktual dari aksesor. Sebagai gantinya, kelas turunan non-abstrak diharuskan memberikan implementasi mereka sendiri untuk aksesornya dengan menimpa properti tersebut. Karena aksesor untuk deklarasi properti abstrak tidak memberikan implementasi aktual, accessor_body hanya terdiri dari titik koma. Properti abstrak tidak boleh memiliki private aksesor.

Deklarasi properti yang mencakup pengubah abstract dan override menentukan bahwa properti abstrak dan mengambil alih properti dasar. Aksesori dari properti semacam itu juga abstrak.

Deklarasi properti abstrak hanya diizinkan dalam kelas abstrak (§15.2.2.2). Akses dari properti virtual yang diwariskan dapat ditimpa kembali di kelas turunan dengan menyertakan deklarasi properti yang menentukan petunjuk override. Ini dikenal sebagai deklarasi properti penggantian. Deklarasi properti overriding tidak mendeklarasikan properti baru. Sebagai gantinya, ini hanya mengspesialisasikan implementasi akses dari properti virtual yang ada.

Pernyataan override dan properti dasar yang di-override harus memiliki aksesibilitas yang dinyatakan sama. Dengan kata lain, deklarasi override tidak boleh mengubah aksesibilitas dari properti dasar. Namun, jika properti dasar yang ditimpa adalah protected internal dan dinyatakan dalam assembly yang berbeda dari assembly yang berisi deklarasi override, maka aksesibilitas yang dinyatakan pada deklarasi override harus protected. Jika properti yang diwarisi hanya memiliki satu aksesori (misalnya, jika properti yang diwarisi hanya-baca atau hanya-tulis), maka properti yang mengesampingkan tersebut harus mencakup hanya aksesori tersebut. Jika properti turunan memiliki kedua aksesor (yaitu, jika properti turunan dapat dibaca dan ditulis), properti yang mengesampingkan dapat mencakup satu aksesor atau kedua aksesor. Harus ada konversi identitas antara tipe yang menimpa dan properti yang diwariskan.

Deklarasi properti pengganti dapat mencakup pengubah sealed. Penggunaan pengubah ini mencegah kelas turunan mengambil alih properti lebih lanjut. Aksesor properti tertutup juga disegel.

Kecuali untuk perbedaan dalam sintaks deklarasi dan pemanggilan, aksesor virtual, sealed, override, dan abstract berfungsi persis seperti metode virtual, sealed, override, dan abstract. Secara khusus, aturan yang dijelaskan dalam §15.6.4, §15.6.5, §15.6.6, dan §15.6.7 berlaku seolah-olah aksesor adalah metode bentuk yang sesuai:

  • Aksesori get sesuai dengan metode tanpa parameter yang memiliki nilai pengembalian dari jenis properti dan pengubah yang sama dengan properti yang mengandungnya.
  • Pengakses setir sesuai dengan metode yang memiliki satu parameter nilai dari tipe properti, tipe pengembalian void, dan modifier yang sama dengan properti yang berisi.

Contoh: Dalam kode berikut

abstract class A
{
    int y;

    public virtual int X
    {
        get => 0;
    }

    public virtual int Y
    {
        get => y;
        set => y = value;
    }

    public abstract int Z { get; set; }
}

X adalah properti baca-saja virtual, Y adalah properti baca-tulis virtual, dan Z merupakan properti baca-tulis abstrak. Karena Z abstrak, kelas A yang berisi juga harus dinyatakan abstrak.

Kelas yang berasal dari A ditunjukkan di bawah ini:

class B : A
{
    int z;

    public override int X
    {
        get => base.X + 1;
    }

    public override int Y
    {
        set => base.Y = value < 0 ? 0: value;
    }

    public override int Z
    {
        get => z;
        set => z = value;
    }
}

Di sini, deklarasi X, Y, dan Z menggantikan deklarasi properti. Setiap deklarasi properti sama persis dengan pengubah aksesibilitas, jenis, dan nama properti yang diwariskan yang sesuai. Aksesor get dari X dan aksesor set dari Y menggunakan kata kunci 'base' untuk mengakses aksesor yang diwariskan. Deklarasi Z menggantikan kedua pengakses abstrak—dengan demikian, tidak ada anggota fungsi yang belum diimplementasikan abstract di B, dan B diizinkan untuk menjadi kelas non-abstrak.

contoh akhir

Ketika properti dinyatakan sebagai pengganti, setiap aksesor yang digantikan akan dapat diakses oleh kode yang menggantikannya. Selain itu, aksesibilitas yang dinyatakan dari properti atau pengindeks itu sendiri, dan dari aksesornya, harus sesuai dengan anggota dan aksesor yang digantikan.

Contoh:

public class B
{
    public virtual int P
    {
        get {...}
        protected set {...}
    }
}

public class D: B
{
    public override int P
    {
        get {...}            // Must not have a modifier here
        protected set {...}  // Must specify protected here
    }
}

contoh akhir

15.8 Peristiwa

15.8.1 Umum

Peristiwa adalah anggota yang memungkinkan objek atau kelas untuk memberikan pemberitahuan. Klien dapat melampirkan kode yang dapat dieksekusi untuk acara dengan menyediakan pengendali acara.

Peristiwa dinyatakan menggunakan event_declaration:

event_declaration
    : attributes? event_modifier* 'event' type variable_declarators ';'
    | attributes? event_modifier* 'event' type member_name
        '{' event_accessor_declarations '}'
    ;

event_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | 'static'
    | 'virtual'
    | 'sealed'
    | 'override'
    | 'abstract'
    | 'extern'
    | 'readonly'        // direct struct members only
    | unsafe_modifier   // unsafe code support
    ;

event_accessor_declarations
    : add_accessor_declaration remove_accessor_declaration
    | remove_accessor_declaration add_accessor_declaration
    ;

add_accessor_declaration
    : attributes? 'add' block
    ;

remove_accessor_declaration
    : attributes? 'remove' block
    ;

unsafe_modifier (§23.2) hanya tersedia dalam kode tidak aman (§23).

Event_declaration dapat mencakup sekumpulan atribut (§22) dan salah satu jenis aksesibilitas yang dinyatakan yang diizinkan (§15.3.6), new (§15.3.5), static (§15.6.3, §15.8.4), virtual (§15.6.4, §15.8.5), override (§15.6.5, §15.8.5), sealed (§15.6.6), abstract (§15.6.7, §15.8.5) dan extern (§15.6.8) pengubah. Selain itu event_declaration yang dimuat langsung oleh struct_declaration dapat mencakup readonly modifikator (§16.4.12).

Deklarasi peristiwa tunduk pada aturan yang sama dengan deklarasi metode (§15,6) sehubungan dengan kombinasi pengubah yang valid.

Jenis deklarasi peristiwa harus berupa delegate_type (§8.2.8), dan delegate_type tersebut harus setidaknya dapat diakses seperti peristiwa itu sendiri (§7.5.5).

Deklarasi peristiwa dapat mencakup event_accessor_declaration. Namun, jika tidak, untuk peristiwa non-ekstern dan non-abstrak, kompilator akan menyediakannya secara otomatis (§15.8.2); untuk peristiwa extern, aksesor disediakan secara eksternal.

Deklarasi peristiwa yang menghilangkan event_accessor_declarationmendefinisikan satu atau beberapa peristiwa—satu untuk setiap variable_declarator. Atribut dan pengubah berlaku untuk semua anggota yang dideklarasikan oleh event_declaration tersebut.

Ini adalah kesalahan waktu kompilasi jika event_declaration menyertakan abstract pengubah dan event_accessor_declaration.

Ketika deklarasi peristiwa menyertakan extern pengubah, peristiwa tersebut dikatakan sebagai peristiwa eksternal. Karena deklarasi peristiwa eksternal tidak menyediakan implementasi aktual, merupakan kesalahan jika menyertakan pengubah extern dan event_accessor_declaration secara bersamaan.

Ini adalah kesalahan waktu kompilasi untuk variable_declarator dalam deklarasi peristiwa dengan modifikator abstract atau external yang menyertakan variable_initializer.

Sebuah acara dapat digunakan sebagai operand kiri operator += dan -=. Operator ini digunakan, masing-masing, untuk melampirkan penanganan aktivitas ke, atau untuk menghapus penanganan aktivitas dari suatu peristiwa, dan pengubah akses peristiwa mengontrol konteks di mana operasi tersebut diizinkan.

Satu-satunya operasi yang diizinkan pada event oleh kode yang berada di luar tipe di mana event tersebut dideklarasikan, adalah += dan -=. Oleh karena itu, meskipun kode tersebut dapat menambahkan dan menghapus handler untuk suatu peristiwa, kode tersebut tidak dapat langsung mendapatkan atau memodifikasi daftar penanganan aktivitas yang mendasar.

Dalam operasi formulir x += y atau x –= y, ketika x adalah peristiwa hasil operasi bertipe void (§12.21.5), berbeda dengan bertipe x dengan nilai x setelah penugasan, seperti untuk operator += dan -= lain yang ditentukan pada jenis non-peristiwa. Ini mencegah kode eksternal mengakses delegasi dasar dari suatu peristiwa secara tidak langsung.

Contoh: Contoh berikut menunjukkan bagaimana penanganan aktivitas dilampirkan ke instans Button kelas:

public delegate void EventHandler(object sender, EventArgs e);

public class Button : Control
{
    public event EventHandler Click;
}

public class LoginDialog : Form
{
    Button okButton;
    Button cancelButton;

    public LoginDialog()
    {
        okButton = new Button(...);
        okButton.Click += new EventHandler(OkButtonClick);
        cancelButton = new Button(...);
        cancelButton.Click += new EventHandler(CancelButtonClick);
    }

    void OkButtonClick(object sender, EventArgs e)
    {
        // Handle okButton.Click event
    }

    void CancelButtonClick(object sender, EventArgs e)
    {
        // Handle cancelButton.Click event
    }
}

Di sini, konstruktor instans LoginDialog membuat dua instans Button dan melampirkan penangan kejadian ke kejadian Click.

contoh akhir

15.8.2 Acara Mirip Lapangan

Dalam teks program kelas atau struktur yang berisi deklarasi peristiwa, peristiwa tertentu dapat digunakan seperti bidang. Untuk digunakan dengan cara ini, suatu peristiwa tidak boleh abstrak atau eksternal, dan tidak boleh secara eksplisit menyertakan event_accessor_declaration. Acara semacam itu dapat digunakan dalam konteks apa pun yang mengizinkan sebuah bidang. Bidang ini berisi sebuah delegasi (§20), yang merujuk pada daftar penanganan acara yang telah ditambahkan ke acara tersebut. Jika tidak ada pengelola acara yang ditambahkan, kolom berisi null.

Contoh: Dalam kode berikut

public delegate void EventHandler(object sender, EventArgs e);

public class Button : Control
{
    public event EventHandler Click;

    protected void OnClick(EventArgs e)
    {
        EventHandler handler = Click;
        if (handler != null)
        {
            handler(this, e);
        }
    }

    public void Reset() => Click = null;
}

Click digunakan sebagai bidang dalam Button kelas . Seperti yang ditunjukkan dalam contoh, bidang tersebut dapat diperiksa, dimodifikasi, dan digunakan dalam ekspresi pemanggilan delegate. Metode OnClick pada kelas Button "memicu" Click event. Gagasan untuk menaikkan peristiwa justru setara dengan memanggil delegasi yang diwakili oleh peristiwa—dengan demikian, tidak ada konstruksi bahasa khusus untuk meningkatkan peristiwa. Perhatikan bahwa pemanggilan delegasi didahului oleh pemeriksaan yang memastikan bahwa delegasi tidak null dan bahwa pemeriksaan dilakukan pada salinan lokal untuk memastikan keamanan utas.

Di luar deklarasi kelas Button, anggota Click hanya dapat digunakan pada sisi kiri operator += dan –=, seperti dalam

b.Click += new EventHandler(...);

yang menambahkan delegasi ke daftar pemanggilan peristiwa Click, dan

Click –= new EventHandler(...);

yang menghapus delegate dari daftar pemanggilan dari peristiwa Click.

contoh akhir

Saat mengkompilasi peristiwa seperti bidang, pengkompilasi akan secara otomatis membuat penyimpanan untuk menahan delegasi, dan harus membuat aksesor untuk peristiwa yang menambahkan atau menghapus penanganan aktivitas ke bidang delegasi. Operasi penambahan dan penghapusan, aman utas, dan mungkin (tetapi tidak diperlukan) dilakukan sambil menahan kunci (§13.13) pada objek yang berisi sebuah peristiwa instans, atau pada objek System.Type (§12.8.18) untuk peristiwa statis.

Catatan: Dengan demikian, deklarasi peristiwa instansi dalam bentuk:

class X
{
    public event D Ev;
}

harus dikompilasi menjadi sesuatu yang setara dengan:

class X
{
    private D __Ev; // field to hold the delegate

    public event D Ev
    {
        add
        {
            /* Add the delegate in a thread safe way */
        }
        remove
        {
            /* Remove the delegate in a thread safe way */
        }
    }
}

Dalam kelas X, referensi ke Ev di sisi kiri operator += dan –= menyebabkan aksesor penambahan dan penghapusan dipanggil. Semua referensi lain ke Ev dikompilasi untuk merujuk ke bidang tersembunyi __Ev alih-alih (§12.8.7). Nama "__Ev" arbitrer; kolom tersembunyi bisa memiliki nama apapun atau tidak ada nama sama sekali.

catatan akhir

15.8.3 Pengakses acara

Catatan: Deklarasi peristiwa biasanya menghilangkan event_accessor_declaration, seperti pada contoh di Button atas. Misalnya, mereka mungkin disertakan jika biaya penyimpanan satu bidang per acara tidak dapat diterima. Dalam kasus seperti itu, kelas dapat menyertakan event_accessor_declarationdan menggunakan mekanisme privat untuk menyimpan daftar penanganan aktivitas. catatan akhir

event_accessor_declarations suatu event menentukan pernyataan yang dapat dieksekusi yang terkait dengan penambahan dan penghapusan penangan peristiwa.

Deklarasi aksesor terdiri dari add_accessor_declaration dan remove_accessor_declaration. Setiap deklarasi aksesor terdiri dari penambahan atau penghapusan token diikuti dengan blok. Blok yang terkait dengan add_accessor_declaration menentukan pernyataan yang akan dijalankan saat penanganan aktivitas ditambahkan, dan blok yang terkait dengan remove_accessor_declaration menentukan pernyataan yang akan dijalankan saat penanganan aktivitas dihapus.

Setiap add_accessor_declaration dan remove_accessor_declaration berkaitan dengan metode yang memiliki satu parameter nilai dari tipe acara, dan tipe pengembalian void. Pengakses peristiwa memiliki parameter implisit yang diberi nama value. Saat peristiwa digunakan dalam penetapan peristiwa, aksesor peristiwa yang sesuai digunakan. Secara khusus, jika operator penugasan adalah += maka aksesor add digunakan, dan jika operator penugasan adalah –= maka aksesor remove digunakan. Dalam setiap kasus, operan kanan dari operator penugasan digunakan sebagai argumen untuk aksesori acara. Blok dari add_accessor_declaration atau remove_accessor_declaration harus sesuai dengan aturan untuk metode yang dijelaskan dalam void. Secara khusus, return pernyataan dalam blok tersebut tidak diizinkan untuk menentukan ekspresi.

Karena aksesor peristiwa secara implisit memiliki parameter bernama value, ini adalah kesalahan waktu kompilasi untuk variabel lokal atau konstanta yang dideklarasikan dalam pengakses peristiwa untuk memiliki nama tersebut.

Contoh: Dalam kode berikut


class Control : Component
{
    // Unique keys for events
    static readonly object mouseDownEventKey = new object();
    static readonly object mouseUpEventKey = new object();

    // Return event handler associated with key
    protected Delegate GetEventHandler(object key) {...}

    // Add event handler associated with key
    protected void AddEventHandler(object key, Delegate handler) {...}

    // Remove event handler associated with key
    protected void RemoveEventHandler(object key, Delegate handler) {...}

    // MouseDown event
    public event MouseEventHandler MouseDown
    {
        add { AddEventHandler(mouseDownEventKey, value); }
        remove { RemoveEventHandler(mouseDownEventKey, value); }
    }

    // MouseUp event
    public event MouseEventHandler MouseUp
    {
        add { AddEventHandler(mouseUpEventKey, value); }
        remove { RemoveEventHandler(mouseUpEventKey, value); }
    }

    // Invoke the MouseUp event
    protected void OnMouseUp(MouseEventArgs args)
    {
        MouseEventHandler handler;
        handler = (MouseEventHandler)GetEventHandler(mouseUpEventKey);
        if (handler != null)
        {
            handler(this, args);
        }
    }
}

Control kelas menerapkan mekanisme penyimpanan internal untuk peristiwa. Metode AddEventHandler mengaitkan nilai delegasi dengan kunci, metode GetEventHandler mengembalikan delegasi yang saat ini terkait dengan kunci, dan metode RemoveEventHandler menghapus delegasi sebagai penanganan acara untuk peristiwa yang ditentukan. Mungkin saja, mekanisme penyimpanan yang mendasarinya dirancang sehingga tidak ada biaya untuk mengaitkan nilai delegasi null dengan kunci, dan dengan demikian peristiwa yang tidak tertangani tidak menggunakan penyimpanan.

contoh akhir

15.8.4 Peristiwa statis dan instans

Ketika deklarasi peristiwa menyertakan static pengubah, peristiwa tersebut dikatakan sebagai peristiwa statis. Ketika tidak ada static pengubah, peristiwa tersebut dikatakan sebagai sebuah peristiwa instans.

Sebuah acara statis tidak terkait dengan instance tertentu, dan itu adalah kesalahan waktu kompilasi untuk merujuk ke this dalam pengakses sebuah acara statis.

Peristiwa instans dikaitkan dengan instans kelas tertentu, dan instans ini dapat diakses sebagai this (§12.8.14) di aksesor peristiwa tersebut.

Perbedaan antara anggota statis dan instans dibahas lebih lanjut dalam §15.3.8.

15.8.5 Aksesori virtual, bersegel, tumpang tindih, dan abstrak

Deklarasi peristiwa virtual menentukan bahwa pengaktor peristiwa tersebut bersifat virtual. Pengubah virtual berlaku untuk kedua aksesor dari sebuah event.

Deklarasi event abstrak menentukan bahwa aksesor event bersifat virtual, tetapi tidak memberikan implementasi aktual dari aksesor tersebut. Sebaliknya, kelas turunan non-abstrak harus menyediakan implementasi mereka sendiri untuk pengakses dengan meng-override event. Karena aksesor untuk deklarasi event abstrak tidak memberikan implementasi nyata, maka tidak boleh menyediakan event_accessor_declarations.

Deklarasi peristiwa yang mencakup pengubah abstract dan override menentukan bahwa peristiwa tersebut abstrak dan mengambil alih peristiwa dasar. Aksesor dari peristiwa seperti itu juga abstrak.

Deklarasi peristiwa abstrak hanya diizinkan dalam kelas abstrak (§15.2.2.2).

Pengakses dari event virtual yang diwarisi dapat ditimpa di kelas turunan dengan menyertakan deklarasi event yang menentukan pengubah override. Ini dikenal sebagai deklarasi peristiwa mengungguli. Deklarasi peristiwa override tidak mendeklarasikan peristiwa baru. Sebaliknya, itu hanya mengkhususkan implementasi dari pengakses peristiwa virtual yang sudah ada.

Deklarasi peristiwa yang menimpa harus menentukan pengubah aksesibilitas dan nama yang sama persis dengan peristiwa yang ditimpa, akan ada konversi identitas antara jenis yang menimpa dan peristiwa yang ditimpa, dan aksesor penambahan serta penghapusan harus ditentukan dalam deklarasi.

Deklarasi penggantian event dapat mencakup pengubah sealed. Penggunaan this pengubah mencegah kelas turunan mengambil alih peristiwa lebih lanjut. Akses ke peristiwa yang disegel juga disegel.

Ini adalah kesalahan waktu kompilasi jika deklarasi event yang meng-override menyertakan modifier new.

Kecuali untuk perbedaan dalam sintaks deklarasi dan pemanggilan, aksesor virtual, sealed, override, dan abstract berfungsi persis seperti metode virtual, sealed, override, dan abstract. Secara khusus, aturan yang dijelaskan dalam §15.6.4, §15.6.5, §15.6.6, dan §15.6.7 berlaku seolah-olah aksesor adalah metode formulir yang sesuai. Setiap aksesor sesuai dengan metode dengan parameter nilai tunggal dari tipe acara, tipe pengembalian void, dan pengubah yang sama dengan acara yang memuatnya.

15.9 Pengindeks

15.9.1 Umum

Pengindeks adalah anggota yang memungkinkan objek diindeks dengan cara yang sama seperti array. Pengindeks dinyatakan menggunakan indexer_declaration:

indexer_declaration
    : attributes? indexer_modifier* indexer_declarator indexer_body
    | attributes? indexer_modifier* ref_kind indexer_declarator ref_indexer_body
    ;

indexer_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | 'virtual'
    | 'sealed'
    | 'override'
    | 'abstract'
    | 'extern'
    | 'readonly'        // direct struct members only
    | unsafe_modifier   // unsafe code support
    ;

indexer_declarator
    : type 'this' '[' parameter_list ']'
    | type interface_type '.' 'this' '[' parameter_list ']'
    ;

indexer_body
    : '{' accessor_declarations '}' 
    | '=>' expression ';'
    ;  

ref_indexer_body
    : '{' ref_get_accessor_declaration '}'
    | '=>' 'ref' variable_reference ';'
    ;

unsafe_modifier (§23.2) hanya tersedia dalam kode tidak aman (§23).

Indexer_declaration dapat mencakup sekumpulan atribut (§22) dan salah satu jenis aksesibilitas yang dinyatakan yang diizinkan (§15.3.6), new (§15.3.5), virtual (§15.6.4), override (§15.6.5), sealed (§15.6.6), abstract (§15.6.7) dan extern (§15.6.8) pengubah. Selain itu, indexer_declaration yang dimuat langsung oleh struct_declaration mungkin termasuk pengubah readonly (§16.4.12).

  • Yang pertama mendeklarasikan pengindeks dengan nilai referensi non. Nilainya memiliki jenis type. Pengindeks semacam ini mungkin dapat dibaca dan/atau dapat ditulis.
  • Yang kedua mendeklarasikan pengindeks bernilai ref. Nilainya adalah variable_reference (§9.5), yang mungkin readonly, ke variabel dengan tipe type. Pengindeks semacam ini hanya dapat dibaca.

Indexer_declaration dapat mencakup sekumpulan atribut (§22) dan salah satu jenis aksesibilitas yang dinyatakan yang diizinkan (§15.3.6), new (§15.3.5), virtual (§15.6.4), override (§15.6.5), sealed (§15.6.6), abstract (§15.6.7), dan extern (§15.6.8) pengubah.

Deklarasi pengindeks tunduk pada aturan yang sama dengan deklarasi metode (§15,6) sehubungan dengan kombinasi pengubah yang valid, dengan satu pengecualian adalah bahwa static pengubah tidak diizinkan pada deklarasi pengindeks.

Jenis deklarasi pengindeks menentukan jenis elemen pengindeks yang diperkenalkan oleh deklarasi.

Catatan: Karena pengindeks dirancang untuk digunakan dalam konteks seperti elemen array, istilah jenis elemen seperti yang didefinisikan untuk array juga digunakan dengan pengindeks. catatan akhir

Kecuali pengindeks adalah implementasi anggota antarmuka secara eksplisit, tipe diikuti oleh kata kunci this. Untuk implementasi anggota antarmuka eksplisit, type diikuti oleh , tanda kutip "", dan kata kunci . Tidak seperti anggota lain, pengindeks tidak memiliki nama yang ditentukan pengguna.

parameter_list menentukan parameter pengindeks. Daftar parameter pengindeks sesuai dengan metode (§15.6.2), kecuali bahwa setidaknya satu parameter harus ditentukan, dan bahwa thispengubah parameter , , refdan out tidak diizinkan.

Jenis dari sebuah pengindeks dan setiap jenis yang dirujuk dalam parameter_list harus memiliki tingkat aksesibilitas yang setidaknya sama dengan pengindeks itu sendiri (§7.5.5).

Indexer_body dapat terdiri dari isi pernyataan (§15.7.1) atau isi ekspresi (§15.6.1). Dalam badan pernyataan, deklarasi_akses, yang harus dibungkus dalam token “{” dan “}”, mendeklarasikan aksesori (§15.7.3) dari pengindeks. Aksesori menentukan pernyataan eksekusi yang berhubungan dengan membaca dan menulis elemen pengindeks.

Dalam indexer_body isi ekspresi yang terdiri dari "=>" diikuti oleh ekspresi E, dan titik koma adalah setara dengan isi pernyataan { get { return E; } }, dan oleh karena itu hanya dapat digunakan untuk menentukan pengindeks baca-saja di mana hasil aksesornya ditentukan oleh satu ekspresi.

ref_indexer_body dapat terdiri dari isi pernyataan atau isi ekspresi. Dalam isi tubuh pernyataan, get_accessor_declaration menyatakan get accessor (§15.7.3) dari pengindeks. Aksesornya menentukan pernyataan yang dapat dieksekusi yang terkait dengan pembacaan pengindeks.

Dalam ref_indexer_body terdapat isi ekspresi yang terdiri dari => diikuti oleh ref, variable_referenceV dan titik koma yang persis setara dengan badan pernyataan { get { return ref V; } }.

Catatan: Meskipun sintaks untuk mengakses elemen pengindeks sama dengan yang untuk elemen array, elemen pengindeks tidak diklasifikasikan sebagai variabel. Dengan demikian, tidak mungkin untuk meneruskan elemen pengindeks sebagai argumen in, out, atau ref kecuali pengindeks bernilai ref dan karenanya mengembalikan referensi (§9.7). catatan akhir

Parameter_list dari pengindeks menetapkan tanda pengenal (§7.6) dari pengindeks. Secara khusus, tanda tangan pengindeks terdiri dari jumlah dan jenis parameternya. Jenis elemen dan nama parameter bukan bagian dari tanda tangan pengindeks.

Tanda tangan pengindeks akan berbeda dari tanda tangan semua pengindeks lain yang dideklarasikan dalam kelas yang sama.

Ketika deklarasi pengindeks menyertakan extern pengubah, pengindeks dikatakan sebagai pengindeks eksternal. Karena deklarasi pengindeks eksternal tidak memberikan implementasi aktual, masing-masing accessor_body dalam accessor_declarations harus berupa titik koma.

Contoh: Contoh di bawah ini mendeklarasikan BitArray kelas yang mengimplementasikan pengindeks untuk mengakses bit individual dalam array bit.

class BitArray
{
    int[] bits;
    int length;

    public BitArray(int length)
    {
        if (length < 0)
        {
            throw new ArgumentException();
        }
        bits = new int[((length - 1) >> 5) + 1];
        this.length = length;
    }

    public int Length => length;

    public bool this[int index]
    {
        get
        {
            if (index < 0 || index >= length)
            {
                throw new IndexOutOfRangeException();
            }
            return (bits[index >> 5] & 1 << index) != 0;
        }
        set
        {
            if (index < 0 || index >= length)
            {
                throw new IndexOutOfRangeException();
            }
            if (value)
            {
                bits[index >> 5] |= 1 << index;
            }
            else
            {
                bits[index >> 5] &= ~(1 << index);
            }
        }
    }
}

Instans kelas BitArray mengonsumsi memori yang jauh lebih sedikit daripada kelas bool[] yang setara (karena setiap nilainya hanya membutuhkan satu bit ketimbang byte yang membutuhkan satu byte), tetapi mengizinkan operasi yang sama dengan .

Kelas berikut CountPrimes menggunakan BitArray dan algoritma "saringan" klasik untuk menghitung jumlah bilangan prima antara 2 dan batas maksimum yang diberikan.

class CountPrimes
{
    static int Count(int max)
    {
        BitArray flags = new BitArray(max + 1);
        int count = 0;
        for (int i = 2; i <= max; i++)
        {
            if (!flags[i])
            {
                for (int j = i * 2; j <= max; j += i)
                {
                    flags[j] = true;
                }
                count++;
            }
        }
        return count;
    }

    static void Main(string[] args)
    {
        int max = int.Parse(args[0]);
        int count = Count(max);
        Console.WriteLine($"Found {count} primes between 2 and {max}");
    }
}

Perhatikan bahwa sintaks untuk mengakses elemen BitArray sama persis dengan untuk bool[].

Contoh berikut menunjukkan kelas kisi 26×10 yang memiliki pengindeks dengan dua parameter. Parameter pertama diperlukan untuk menjadi huruf besar atau kecil dalam rentang A–Z, dan yang kedua diperlukan untuk menjadi bilangan bulat dalam rentang 0–9.

class Grid
{
    const int NumRows = 26;
    const int NumCols = 10;
    int[,] cells = new int[NumRows, NumCols];

    public int this[char row, int col]
    {
        get
        {
            row = Char.ToUpper(row);
            if (row < 'A' || row > 'Z')
            {
                throw new ArgumentOutOfRangeException("row");
            }
            if (col < 0 || col >= NumCols)
            {
                throw new ArgumentOutOfRangeException ("col");
            }
            return cells[row - 'A', col];
        }
        set
        {
            row = Char.ToUpper(row);
            if (row < 'A' || row > 'Z')
            {
                throw new ArgumentOutOfRangeException ("row");
            }
            if (col < 0 || col >= NumCols)
            {
                throw new ArgumentOutOfRangeException ("col");
            }
            cells[row - 'A', col] = value;
        }
    }
}

contoh akhir

15.9.2 Perbedaan Pengindeks dan Properti

Pengindeks dan properti sangat mirip dalam konsep, tetapi berbeda dengan cara berikut:

  • Properti diidentifikasi dengan namanya, sedangkan pengindeks diidentifikasi oleh tanda tangannya.
  • Properti diakses melalui simple_name (§12.8.4) atau member_access (§12.8.7), sedangkan elemen pengindeks diakses melalui element_access (§12.8.12.3).
  • Properti dapat menjadi anggota statis, sedangkan pengindeks selalu menjadi anggota instans.
  • Aksesor 'get' dari properti sesuai dengan metode tanpa parameter, sedangkan aksesor 'get' dari pengindeks sesuai dengan metode yang memiliki daftar parameter yang sama seperti pengindeks.
  • Pengakses set properti sesuai dengan metode dengan parameter tunggal bernama value, sedangkan pengakses set pengindeks sesuai dengan metode dengan daftar parameter yang sama dengan pengindeks, ditambah parameter tambahan bernama value.
  • Ini adalah kesalahan waktu kompilasi jika pengakses pengindeks mendeklarasikan variabel lokal atau konstanta lokal dengan nama yang sama dengan parameter pengindeks.
  • Dalam deklarasi properti penggantian, properti yang diwariskan diakses menggunakan sintaks base.P, di mana P adalah nama properti. Dalam deklarasi penganuliran pengindeks, pengindeks yang diwarisi diakses dengan menggunakan sintaks base[E], di mana E adalah daftar ekspresi yang dipisahkan oleh koma.
  • Tidak ada konsep "pengindeks yang diimplementasikan secara otomatis." Ini adalah kesalahan untuk memiliki pengindeks non-abstrak, non-eksternal dengan titik koma accessor_bodys.

Selain perbedaan ini, semua aturan yang ditentukan dalam §15.7.3, §15.7.5 dan §15.7.6 berlaku untuk aksesor pengindeks serta untuk aksesor properti.

Penggantian properti/properti ini dengan pengindeks/pengindeks ketika membaca §15.7.3, §15.7.5 dan §15.7.6 juga berlaku untuk istilah yang telah ditentukan. Secara khusus, properti baca-tulis menjadi pengindeks baca-tulis, properti baca-saja menjadi pengindeks baca-saja, dan properti tulis-saja menjadi pengindeks tulis-saja.

15.10 Operator

15.10.1 Umum

Operator adalah anggota yang menentukan arti operator ekspresi yang dapat diterapkan ke instans kelas. Operator dinyatakan menggunakan operator_declaration:

operator_declaration
    : attributes? operator_modifier+ operator_declarator operator_body
    ;

operator_modifier
    : 'public'
    | 'static'
    | 'extern'
    | unsafe_modifier   // unsafe code support
    ;

operator_declarator
    : unary_operator_declarator
    | binary_operator_declarator
    | conversion_operator_declarator
    ;

unary_operator_declarator
    : type 'operator' overloadable_unary_operator '(' fixed_parameter ')'
    ;

logical_negation_operator
    : '!'
    ;

overloadable_unary_operator
    : '+' | '-' | logical_negation_operator | '~' | '++' | '--' | 'true' | 'false'
    ;

binary_operator_declarator
    : type 'operator' overloadable_binary_operator
        '(' fixed_parameter ',' fixed_parameter ')'
    ;

overloadable_binary_operator
    : '+'  | '-'  | '*'  | '/'  | '%'  | '&' | '|' | '^'  | '<<' 
    | right_shift | '==' | '!=' | '>' | '<' | '>=' | '<='
    ;

conversion_operator_declarator
    : 'implicit' 'operator' type '(' fixed_parameter ')'
    | 'explicit' 'operator' type '(' fixed_parameter ')'
    ;

operator_body
    : block
    | '=>' expression ';'
    | ';'
    ;

unsafe_modifier (§23.2) hanya tersedia dalam kode tidak aman (§23).

Catatan: Negasi logis awalan (§12.9.4) dan operator null-forgiving postfix (§12.8.9), meskipun diwakili oleh token leksikal yang sama (!), adalah berbeda. Yang terakhir bukan operator yang dapat kelebihan beban. catatan akhir

Ada tiga kategori operator yang dapat kelebihan beban: Operator unary (§15.10.2), operator biner (§15.10.3), dan operator konversi (§15.10.4).

operator_body adalah titik koma, badan blok (§15.6.1) atau badan ekspresi (§15.6.1). Badan blok terdiri dari blok, yang menentukan pernyataan yang akan dijalankan saat operator dipanggil. Blok harus sesuai dengan aturan untuk metode pengembalian nilai yang dijelaskan dalam §15.6.11. Isi ekspresi terdiri dari => diikuti oleh ekspresi dan titik koma, dan menunjukkan ekspresi tunggal untuk dilakukan saat operator dipanggil.

Untuk extern operator, operator_body hanya terdiri dari titik koma. Untuk semua operator lain, operator_body adalah blok atau ekspresi.

Aturan berikut berlaku untuk semua deklarasi operator:

  • Deklarasi operator harus mencakup pengubah public dan pengubah static.
  • Parameter dari suatu operator tidak boleh memiliki pengubah selain in.
  • Tanda tangan operator (§15.10.2, §15.10.3, §15.10.4) akan berbeda dari tanda tangan semua operator lain yang dinyatakan dalam kelas yang sama.
  • Semua jenis yang direferensikan dalam deklarasi operator harus sekurang-kurangnya sama dapat diaksesnya dengan operator itu sendiri (§7.5.5).
  • Ini adalah kesalahan bagi pengubah yang sama muncul beberapa kali dalam deklarasi operator.

Setiap kategori operator memberlakukan pembatasan tambahan, seperti yang dijelaskan dalam subklasus berikut.

Seperti anggota lain, operator yang dideklarasikan dalam kelas dasar diwariskan oleh kelas turunan. Karena deklarasi operator selalu mengharuskan kelas atau struct tempat operator dinyatakan terlibat dalam tandatangan operator, maka operator yang dideklarasikan dalam kelas turunan tidak dapat menyembunyikan operator yang dideklarasikan dalam kelas dasar. Dengan demikian, pengubah new tidak pernah diperlukan, dan oleh karena itu tidak pernah diizinkan, dalam deklarasi operator.

Informasi tambahan tentang operator unary dan biner dapat ditemukan di §12.4.

Informasi tambahan tentang operator konversi dapat ditemukan di §10.5.

15.10.2 Operator unari

Aturan berikut berlaku untuk deklarasi operator unary, di mana T menunjukkan jenis instans kelas atau struct yang berisi deklarasi operator:

  • Unary +, -, ! (hanya negasi logis), atau ~ operator harus mengambil satu parameter jenis T atau T? dan dapat mengembalikan jenis apa pun.
  • Unary ++ atau -- operator harus mengambil satu parameter dari jenis T atau T? dan harus mengembalikan jenis yang sama atau jenis yang diturunkan darinya.
  • Operator unary true atau false harus menggunakan satu parameter jenis T atau T? dan harus mengembalikan jenis bool.

Tanda tangan operator unary terdiri dari token operator (+, , -, !, ~++, --, true, atau false) dan jenis parameter tunggal. Jenis pengembalian bukan bagian dari tanda tangan operator unary, juga bukan nama parameter.

Operator unary true dan false memerlukan deklarasi berpasangan. Kesalahan waktu kompilasi terjadi jika kelas mendeklarasikan salah satu operator ini tanpa juga mendeklarasikan yang lain. Operator true dan false dijelaskan lebih lanjut dalam §12.24.

Contoh: Contoh berikut menunjukkan implementasi dan penggunaan operator++ berikutnya untuk kelas vektor bilangan bulat:

public class IntVector
{
    public IntVector(int length) {...}
    public int Length { get { ... } }                      // Read-only property
    public int this[int index] { get { ... } set { ... } } // Read-write indexer

    public static IntVector operator++(IntVector iv)
    {
        IntVector temp = new IntVector(iv.Length);
        for (int i = 0; i < iv.Length; i++)
        {
            temp[i] = iv[i] + 1;
        }
        return temp;
    }
}

class Test
{
    static void Main()
    {
        IntVector iv1 = new IntVector(4); // Vector of 4 x 0
        IntVector iv2;
        iv2 = iv1++;              // iv2 contains 4 x 0, iv1 contains 4 x 1
        iv2 = ++iv1;              // iv2 contains 4 x 2, iv1 contains 4 x 2
    }
}

Perhatikan bagaimana metode operator mengembalikan nilai yang dihasilkan dengan menambahkan 1 ke operand, sama seperti operator kenaikan dan penurunan postfix (§12.8.16), dan operator kenaikan dan penurunan awalan (§12.9.6). Tidak seperti di C++, metode ini tidak boleh memodifikasi nilai operand-nya secara langsung karena ini akan melanggar semantik standar operator kenaikan postfix (§12.8.16).

contoh akhir

15.10.3 Operasi biner

Aturan berikut berlaku untuk deklarasi operator biner, di mana T menunjukkan jenis instans kelas atau struktur yang berisi deklarasi operator:

  • Operator biner non-shift harus mengambil dua parameter, setidaknya salah satunya harus memiliki jenis T atau T?, dan dapat mengembalikan jenis apa pun.
  • Operator biner << atau >> (§12,11) harus mengambil dua parameter, yang pertama harus memiliki jenis T atau T? dan yang kedua harus memiliki jenis int atau int?, dan dapat mengembalikan jenis apa pun.

Tanda tangan operator biner terdiri dari token operator (+, , , -*, /%, &|^<<>>==, , !=, >, , <, , >=, atau <=) dan jenis dua parameter. Jenis pengembalian dan nama parameter bukan bagian dari tanda tangan operator biner.

Operator biner tertentu memerlukan deklarasi berpasangan. Untuk setiap deklarasi salah satu operator dari pasangan, harus ada deklarasi yang sesuai untuk operator lainnya dari pasangan tersebut. Dua pernyataan operator dianggap cocok jika ada konversi identitas antara tipe pengembaliannya dan tipe parameternya yang sesuai. Operator berikut memerlukan deklarasi berpasangan:

  • operator == dan operator !=
  • operator > dan operator <
  • operator >= dan operator <=

15.10.4 Operator konversi

Deklarasi operator konversi memperkenalkan konversi yang ditentukan pengguna (§10,5), yang menambah konversi implisit dan eksplisit yang telah ditentukan sebelumnya.

Deklarasi operator konversi yang menyertakan implicit kata kunci memperkenalkan konversi implisit yang ditentukan pengguna. Konversi implisit dapat terjadi dalam berbagai situasi, termasuk pemanggilan anggota fungsi, ekspresi cast, dan penugasan. Ini dijelaskan lebih lanjut dalam §10.2.

Deklarasi operator konversi yang menyertakan explicit kata kunci memperkenalkan konversi eksplisit yang ditentukan pengguna. Konversi eksplisit dapat terjadi dalam ekspresi casting dan dijelaskan lebih lanjut di §10.3.

Operator konversi dikonversi dari jenis sumber, yang ditunjukkan oleh jenis parameter operator konversi, ke jenis target, yang ditunjukkan oleh jenis pengembalian operator konversi.

Untuk jenis sumber S dan jenis target T tertentu, jika S atau T merupakan jenis nilai yang dapat bernilai null, anggap S₀ dan T₀ mengacu pada jenis dasarnya; jika tidak, masing-masing S₀ dan T₀ sama dengan S dan T. Kelas atau struktur diizinkan untuk mendeklarasikan konversi dari jenis sumber ke jenis ST target hanya jika semua hal berikut ini benar:

  • S₀ dan T₀ merupakan jenis yang berbeda.

  • Baik S₀ atau T₀ merupakan jenis instans kelas atau struktur yang berisi deklarasi operator.

  • Baik S₀ maupun T₀ bukan interface_type.

  • Tidak termasuk konversi yang ditentukan pengguna, konversi tidak ada dari S ke T atau dari T ke S.

Untuk tujuan aturan ini, parameter jenis apa pun yang terkait dengan S atau T dianggap sebagai jenis unik yang tidak memiliki hubungan pewarisan dengan jenis lain, dan batasan apa pun pada parameter jenis tersebut diabaikan.

Contoh: Dalam hal berikut:

class C<T> {...}

class D<T> : C<T>
{
    public static implicit operator C<int>(D<T> value) {...}     // Ok
    public static implicit operator C<string>(D<T> value) {...}  // Ok
    public static implicit operator C<T>(D<T> value) {...}       // Error
}

dua deklarasi operator pertama diizinkan karena T dan int dan string, masing-masing dianggap sebagai jenis unik tanpa hubungan. Namun, operator ketiga adalah kesalahan karena C<T> merupakan kelas dasar dari D<T>.

contoh akhir

Dari aturan kedua, dapat disimpulkan bahwa operator konversi harus mengonversi ke atau dari kelas atau tipe struct tempat operator dideklarasikan.

Contoh: Dimungkinkan bagi kelas atau jenis C struktur untuk menentukan konversi dari C ke int dan dari int ke C, tetapi tidak dari int ke bool. contoh akhir

Tidak dimungkinkan untuk secara langsung mendefinisikan ulang konversi yang telah ditentukan sebelumnya. Dengan demikian, operator konversi tidak diizinkan untuk mengonversi dari atau ke object karena konversi implisit dan eksplisit sudah ada antara object dan semua jenis lainnya. Demikian juga, baik sumber maupun tipe target konversi tidak dapat menjadi tipe dasar dari yang lain, karena konversi kemudian akan ada. Namun, dimungkinkan untuk mendeklarasikan operator pada jenis generik yang, untuk argumen jenis tertentu, menentukan konversi yang sudah ada sebagai konversi yang telah ditentukan sebelumnya.

Contoh:

struct Convertible<T>
{
    public static implicit operator Convertible<T>(T value) {...}
    public static explicit operator T(Convertible<T> value) {...}
}

ketika jenis object ditentukan sebagai argumen jenis untuk T, operator kedua menyatakan konversi yang sudah ada (implisit, dan oleh karena itu juga eksplisit, konversi ada dari jenis apa pun ke objek jenis).

contoh akhir

Dalam kasus di mana konversi yang telah ditentukan sebelumnya ada di antara dua jenis, konversi apa pun yang ditentukan pengguna antara jenis tersebut diabaikan. Khususnya:

  • Jika konversi implisit yang telah ditentukan sebelumnya (§10.2) ada dari tipe S ke tipe T, semua konversi yang ditentukan pengguna (implisit atau eksplisit) dari S ke T diabaikan.
  • Jika konversi eksplisit yang sudah ditetapkan sebelumnya (§10.3) ada dari jenis S ke jenis T, konversi eksplisit yang ditentukan pengguna dari S ke T diabaikan. Selanjutnya:
    • Jika salah satu S atau T merupakan jenis antarmuka, konversi implisit yang ditentukan pengguna dari S ke T diabaikan.
    • Jika tidak, konversi implisit yang ditentukan pengguna dari S ke T masih dipertimbangkan.

Untuk semua jenis kecuali object, operator yang dideklarasikan oleh jenis Convertible<T> di atas tidak bertentangan dengan konversi yang telah ditentukan sebelumnya.

Contoh:

void F(int i, Convertible<int> n)
{
    i = n;                    // Error
    i = (int)n;               // User-defined explicit conversion
    n = i;                    // User-defined implicit conversion
    n = (Convertible<int>)i;  // User-defined implicit conversion
}

Namun, untuk jenis object, konversi yang telah ditentukan sebelumnya menyembunyikan konversi yang ditentukan pengguna dalam semua kasus tetapi satu:

void F(object o, Convertible<object> n)
{
    o = n;                       // Pre-defined boxing conversion
    o = (object)n;               // Pre-defined boxing conversion
    n = o;                       // User-defined implicit conversion
    n = (Convertible<object>)o;  // Pre-defined unboxing conversion
}

contoh akhir

Konversi buatan pengguna tidak diizinkan untuk mengonversi dari atau ke interface_type. Secara khusus, pembatasan ini memastikan bahwa tidak ada transformasi yang ditentukan pengguna yang terjadi saat mengonversi ke interface_type, dan bahwa konversi ke interface_type berhasil hanya jika object yang dikonversi benar-benar mengimplementasikan interface_type yang ditentukan.

Tanda tangan operator konversi terdiri dari jenis sumber dan jenis target. (Ini adalah satu-satunya bentuk anggota yang jenis pengembaliannya berpartisipasi dalam tanda tangan.) Klasifikasi implisit atau eksplisit operator konversi bukan bagian dari tanda tangan operator. Dengan demikian, kelas atau struktur tidak dapat mendeklarasikan operator konversi implisit dan eksplisit dengan jenis sumber dan target yang sama.

Catatan: Secara umum, konversi implisit yang ditentukan pengguna harus dirancang untuk tidak pernah melemparkan pengecualian dan tidak pernah kehilangan informasi. Jika konversi yang ditentukan pengguna dapat menimbulkan pengecualian (misalnya, karena argumen sumber berada di luar rentang) atau hilangnya informasi (seperti membuang bit berurutan tinggi), maka konversi tersebut harus didefinisikan sebagai konversi eksplisit. catatan akhir

Contoh: Dalam kode berikut

public struct Digit
{
    byte value;

    public Digit(byte value)
    {
        if (value < 0 || value > 9)
        {
            throw new ArgumentException();
        }
        this.value = value;
    }

    public static implicit operator byte(Digit d) => d.value;
    public static explicit operator Digit(byte b) => new Digit(b);
}

konversi dari Digit ke byte adalah implisit karena tidak pernah melemparkan pengecualian atau kehilangan informasi, tetapi konversi dari byte ke Digit adalah eksplisit karena Digit hanya dapat mewakili subset dari nilai yang bytemungkin dari .

contoh akhir

15.11 Konstruktor instans

15.11.1 Umum

Sebuah Konstruktor instans adalah anggota yang mengimplementasikan tindakan yang diperlukan untuk menginisialisasi instans kelas. Konstruktor instans dinyatakan menggunakan constructor_declaration:

constructor_declaration
    : attributes? constructor_modifier* constructor_declarator constructor_body
    ;

constructor_modifier
    : 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | 'extern'
    | unsafe_modifier   // unsafe code support
    ;

constructor_declarator
    : identifier '(' parameter_list? ')' constructor_initializer?
    ;

constructor_initializer
    : ':' 'base' '(' argument_list? ')'
    | ':' 'this' '(' argument_list? ')'
    ;

constructor_body
    : block
    | '=>' expression ';'
    | ';'
    ;

unsafe_modifier (§23.2) hanya tersedia dalam kode tidak aman (§23).

Constructor_declaration dapat mencakup sekumpulan atribut (§22), salah satu jenis aksesibilitas yang dinyatakan yang diizinkan (§15.3.6), dan pengubah (§15.6.8). Deklarasi konstruktor tidak diizinkan untuk menyertakan pengubah yang sama beberapa kali.

Pengidentifikasi identifier dari constructor_declarator harus menamai kelas di mana konstruktor instans dideklarasikan. Jika ada nama lain yang ditentukan, kesalahan waktu kompilasi terjadi.

Parameter_list opsional dari konstruktor instans tunduk pada aturan yang sama dengan parameter_list metode (§15.6). ** Karena pengubah this untuk parameter hanya berlaku untuk metode ekstensi (§15.6.10), tidak ada parameter dalam parameter_list konstruktor yang boleh berisi pengubah this. Daftar parameter menentukan tanda tangan (§7,6) dari konstruktor instans dan mengatur proses di mana resolusi kelebihan beban (§12.6.4) memilih konstruktor instans tertentu dalam pemanggilan.

Masing-masing jenis yang dirujuk dalam parameter_list dari konstruktor instans harus memiliki aksesibilitas yang sama atau lebih tinggi seperti konstruktor itu sendiri (§7.5.5).

Opsional constructor_initializer menentukan konstruktor instance lain yang harus dipanggil sebelum menjalankan pernyataan dalam constructor_body dari konstruktor instance ini. Ini dijelaskan lebih lanjut dalam §15.11.2.

Ketika deklarasi konstruktor menyertakan pengubah extern , konstruktor dikatakan sebagai konstruktor eksternal. Karena deklarasi konstruktor eksternal tidak menyediakan implementasi aktual, constructor_body-nya terdiri dari titik koma. Untuk semua konstruktor lainnya, constructor_body terdiri dari

  • blok, yang menentukan pernyataan untuk menginisialisasi instans baru dari kelas; atau
  • bentuk ekspresi, yang terdiri dari => diikuti oleh sebuah ekspresi dan titik koma, yang menunjukkan satu ekspresi untuk menginisialisasi instans baru dari kelas.

Constructor_body yang merupakan blok atau badan ekspresi persis sesuai dengan blok dari metode instans yang memiliki jenis pengembalian (void).

Konstruktor contoh tidak diwariskan. Dengan demikian, kelas tidak memiliki konstruktor instans selain yang benar-benar dideklarasikan di kelas, dengan pengecualian bahwa jika kelas tidak berisi deklarasi konstruktor instans, konstruktor instans default secara otomatis disediakan (§15.11.5).

Konstruktor instans dipanggil oleh object_creation_expression(§12.8.17.2) dan melalui constructor_initializer.

15.11.2 Inisialisasi konstruktor

Semua konstruktor instans (kecuali untuk kelas object) secara implisit menyertakan pemanggilan konstruktor instans lain segera sebelum constructor_body. Konstruktor yang dipanggil secara implisit ditentukan oleh constructor_initializer:

  • Inisialisasi konstruktor instans dalam bentuk base(argument_list) (di mana argument_list bersifat opsional) akan memanggil konstruktor instans dari kelas dasar langsung. Konstruktor tersebut dipilih menggunakan argument_list dan aturan resolusi kelebihan beban §12.6.4. Set kandidat konstruktor instance terdiri dari semua konstruktor instance yang dapat diakses dari kelas dasar langsung. Jika set ini kosong, atau jika satu konstruktor instans terbaik tidak dapat diidentifikasi, kesalahan waktu kompilasi terjadi.
  • Inisialisasi konstruktor instans dengan bentuk this(argument_list) (di mana argument_list bersifat opsional) memanggil konstruktor instans lain dari kelas yang sama. Konstruktor dipilih menggunakan argument_list dan aturan resolusi kelebihan beban §12.6.4. Kumpulan konstruktor instans kandidat terdiri dari semua konstruktor instans yang dideklarasikan di kelas itu sendiri. Jika kumpulan konstruktor instans yang berlaku yang dihasilkan kosong, atau jika satu konstruktor instans terbaik tidak dapat diidentifikasi, kesalahan waktu kompilasi terjadi. Jika deklarasi konstruktor instance memanggil dirinya sendiri melalui rantai satu atau beberapa penginisialisasi konstruktor, maka akan terjadi kesalahan kompilasi.

Jika sebuah konstruktor turunan tidak memiliki penginisialisasi konstruktor, maka secara implisit diberikan penginisialisasi konstruktor berbentuk base().

Catatan: Dengan demikian, deklarasi konstruktor instans dalam bentuk

C(...) {...}

sama persis dengan

C(...) : base() {...}

catatan akhir

Cakupan parameter yang diberikan oleh parameter_list deklarasi konstruktor instans mencakup inisialisasi konstruktor dari deklarasi tersebut. Dengan demikian, penginisialisasi konstruktor diizinkan untuk mengakses parameter dari konstruktor.

Contoh:

class A
{
    public A(int x, int y) {}
}

class B: A
{
    public B(int x, int y) : base(x + y, x - y) {}
}

contoh akhir

Penginisialisasi konstruktor instans tidak dapat mengakses instans yang dibuat. Oleh karena itu, merupakan kesalahan waktu kompilasi untuk mereferensikan ini dalam ekspresi argumen dari inisialisasi konstruktor, sebagaimana merupakan kesalahan waktu kompilasi bagi ekspresi argumen untuk mereferensikan anggota instance manapun melalui simple_name.

15.11.3 Penginisialisasi variabel Instans

Ketika konstruktor pada instans non-ekstern tidak memiliki inisialisasi konstruktor, atau memiliki inisialisasi konstruktor dalam bentuk base(...), konstruktor tersebut melakukan inisialisasi yang secara implisit ditentukan oleh variable_initializerdari bidang instans yang dideklarasikan di kelasnya. Ini sesuai dengan urutan penugasan yang dieksekusi segera setelah memasuki konstruktor dan sebelum pemanggilan implisit dari konstruktor kelas dasar langsung. Penginisialisasi variabel dijalankan dalam urutan tekstual di mana mereka muncul dalam deklarasi kelas (§15.5.6).

Penginisialisasi variabel tidak diperlukan untuk dijalankan oleh konstruktor instans ekstern.

15.11.4 Pelaksanaan constructor

Penginisialisasi variabel diubah menjadi pernyataan penugasan, dan pernyataan penugasan ini dijalankan sebelum pemanggilan konstruktor instans kelas dasar. Pengurutan ini memastikan bahwa semua bidang instans diinisialisasi oleh penginisialisasi variabel mereka sebelum pernyataan apa pun yang memiliki akses ke instans tersebut dijalankan.

Contoh: Diberikan hal berikut:

class A
{
    public A()
    {
        PrintFields();
    }

    public virtual void PrintFields() {}
}
class B: A
{
    int x = 1;
    int y;

    public B()
    {
        y = -1;
    }

    public override void PrintFields() =>
        Console.WriteLine($"x = {x}, y = {y}");
}

ketika B() baru digunakan untuk membuat instans B, output berikut dihasilkan:

x = 1, y = 0

Nilai x adalah 1 karena penginisialisasi variabel dijalankan sebelum konstruktor instans kelas dasar dipanggil. Namun, nilai y adalah 0 (nilai intdefault ) karena penugasan untuk y tidak dijalankan sampai setelah konstruktor kelas dasar kembali. Berguna untuk memikirkan inisialisasi variabel instans dan penginisialisasi konstruktor sebagai pernyataan yang secara otomatis dimasukkan sebelum constructor_body. Contoh

class A
{
    int x = 1, y = -1, count;

    public A()
    {
        count = 0;
    }

    public A(int n)
    {
        count = n;
    }
}

class B : A
{
    double sqrt2 = Math.Sqrt(2.0);
    ArrayList items = new ArrayList(100);
    int max;

    public B(): this(100)
    {
        items.Add("default");
    }

    public B(int n) : base(n - 1)
    {
        max = n;
    }
}

berisi beberapa penginisialisasi variabel; ini juga berisi inisialisasi konstruktor dalam kedua bentuk (base dan this). Contoh sesuai dengan kode yang ditunjukkan di bawah ini, di mana setiap komentar menunjukkan pernyataan yang disisipkan secara otomatis (sintaks yang digunakan untuk pemanggilan konstruktor yang dimasukkan secara otomatis tidak valid, tetapi hanya berfungsi untuk mengilustrasikan mekanisme).

class A
{
    int x, y, count;
    public A()
    {
        x = 1;      // Variable initializer
        y = -1;     // Variable initializer
        object();   // Invoke object() constructor
        count = 0;
    }

    public A(int n)
    {
        x = 1;      // Variable initializer
        y = -1;     // Variable initializer
        object();   // Invoke object() constructor
        count = n;
    }
}

class B : A
{
    double sqrt2;
    ArrayList items;
    int max;
    public B() : this(100)
    {
        B(100);                      // Invoke B(int) constructor
        items.Add("default");
    }

    public B(int n) : base(n - 1)
    {
        sqrt2 = Math.Sqrt(2.0);      // Variable initializer
        items = new ArrayList(100);  // Variable initializer
        A(n - 1);                    // Invoke A(int) constructor
        max = n;
    }
}

contoh akhir

15.11.5 Konstruktor bawaan

Jika kelas tidak berisi deklarasi konstruktor instans, konstruktor instans default secara otomatis disediakan. Konstruktor bawaan tersebut hanya memanggil konstruktor kelas dasar langsung, seolah-olah memiliki penginisialisasi konstruktor dalam bentuk base(). Jika kelas abstrak, maka aksesibilitas yang dinyatakan untuk konstruktor default dilindungi. Jika tidak, aksesibilitas yang dinyatakan untuk konstruktor bawaan adalah publik.

Catatan: Dengan demikian, konstruktor default selalu berbentuk

protected C(): base() {}

atau

public C(): base() {}

di mana C adalah nama kelas.

catatan akhir

Jika resolusi kelebihan beban tidak dapat menentukan kandidat terbaik unik untuk penginisialisasi konstruktor kelas dasar, kesalahan waktu kompilasi terjadi.

Contoh: Dalam kode berikut

class Message
{
    object sender;
    string text;
}

konstruktor default disediakan karena kelas tidak berisi deklarasi konstruktor instans. Dengan demikian, contohnya justru setara dengan

class Message
{
    object sender;
    string text;

    public Message() : base() {}
}

contoh akhir

15.12 Konstruktor statis

Konstruktor statis adalah anggota yang menerapkan tindakan yang diperlukan untuk menginisialisasi kelas tertutup. Konstruktor statis dinyatakan menggunakan static_constructor_declaration:

static_constructor_declaration
    : attributes? static_constructor_modifiers identifier '(' ')'
        static_constructor_body
    ;

static_constructor_modifiers
    : 'static'
    | 'static' 'extern' unsafe_modifier?
    | 'static' unsafe_modifier 'extern'?
    | 'extern' 'static' unsafe_modifier?
    | 'extern' unsafe_modifier 'static'
    | unsafe_modifier 'static' 'extern'?
    | unsafe_modifier 'extern' 'static'
    ;

static_constructor_body
    : block
    | '=>' expression ';'
    | ';'
    ;

unsafe_modifier (§23.2) hanya tersedia dalam kode tidak aman (§23).

Deklarasi_konstruktor_statis dapat mencakup sekumpulan atribut (§22) dan pengubah (§15.6.8).

Identifier dari static_constructor_declaration harus menamai kelas yang dideklarasikan konstruktor statis. Jika ada nama lain yang ditentukan, kesalahan waktu kompilasi terjadi.

Ketika deklarasi konstruktor statis menyertakan extern pengubah, konstruktor statis dikatakan sebagai konstruktor statis eksternal. Karena deklarasi konstruktor statis eksternal tidak menyediakan implementasi aktual, static_constructor_body terdiri dari titik koma. Untuk semua deklarasi konstruktor statis lainnya, static_constructor_body terdiri dari

  • blok, yang menentukan pernyataan yang akan dijalankan untuk menginisialisasi kelas; atau
  • badan ekspresi, yang terdiri dari => diikuti oleh ekspresi dan titik koma, yang menunjukkan satu ekspresi untuk dijalankan dalam rangka menginisialisasi kelas.

Static_constructor_body yang merupakan blok atau tubuh ekspresi persis sesuai dengan tubuh metode statis yang memiliki jenis pengembalian (), sebagaimana diatur dalam (§15.6.11).

Konstruktor statis tidak diwariskan, dan tidak dapat dipanggil secara langsung.

Konstruktor statis untuk kelas tertutup dijalankan paling banyak sekali di domain aplikasi tertentu. Eksekusi konstruktor statis dipicu oleh peristiwa pertama berikut yang terjadi dalam domain aplikasi:

  • Sebuah instance dari kelas dibuat.
  • Salah satu dari anggota statis kelas dapat dirujuk.

Jika kelas berisi Main metode (§7.1) di mana eksekusi dimulai, konstruktor statis untuk kelas tersebut dijalankan sebelum metode dipanggil Main .

Untuk menginisialisasi jenis kelas tertutup baru, pertama-tama sekumpulan bidang statis baru (§15.5.2) untuk jenis tertutup tertentu harus dibuat. Masing-masing bidang statis harus diinisialisasi ke nilai defaultnya (§15.5.5). Berikut ini:

  • Jika tidak ada konstruktor statis atau konstruktor statis non-ekstern maka:
    • penginisialisasi bidang statis (§15.5.6.2) harus dijalankan untuk bidang statis tersebut;
    • maka konstruktor statis non-ekstern, jika ada, harus dijalankan.
  • Jika tidak, maka jika ada konstruktor statis ekstern, itu akan dijalankan. Penginisialisasi variabel statis tidak diperlukan untuk dijalankan oleh konstruktor statis ekstern.

Contoh: Contoh

class Test
{
    static void Main()
    {
        A.F();
        B.F();
    }
}

class A
{
    static A()
    {
        Console.WriteLine("Init A");
    }

    public static void F()
    {
        Console.WriteLine("A.F");
    }
}

class B
{
    static B()
    {
        Console.WriteLine("Init B");
    }

    public static void F()
    {
        Console.WriteLine("B.F");
    }
}

harus menghasilkan output:

Init A
A.F
Init B
B.F

karena eksekusi Akonstruktor statis dipicu oleh panggilan ke A.F, dan eksekusi Bkonstruktor statis dipicu oleh panggilan ke B.F.

contoh akhir

Dimungkinkan untuk membangun dependensi melingkar yang memungkinkan bidang statis dengan penginisialisasi variabel diamati dalam status nilai defaultnya.

Contoh: Contoh

class A
{
    public static int X;

    static A()
    {
        X = B.Y + 1;
    }
}

class B
{
    public static int Y = A.X + 1;

    static B() {}

    static void Main()
    {
        Console.WriteLine($"X = {A.X}, Y = {B.Y}");
    }
}

menghasilkan keluaran

X = 1, Y = 2

Untuk menjalankan Main metode , sistem terlebih dahulu menjalankan inisialisasi untuk B.Y, sebelum konstruktor statis kelas B. Y penginisialisasi menyebabkan konstruktor Astatic untuk dijalankan karena nilai A.X dirujuk. Konstruktor statis pada A pada gilirannya melanjutkan menghitung nilai X, dan dalam melakukannya mengambil nilai default Y, yaitu nol. A.X dengan demikian diinisialisasi menjadi 1. Proses menjalankan A penginisialisasi bidang statis dan konstruktor statis kemudian selesai, kembali ke perhitungan nilai awal Y, yang hasilnya menjadi 2.

contoh akhir

Karena konstruktor statis dijalankan tepat sekali untuk setiap jenis kelas yang dibangun tertutup, ini adalah tempat yang nyaman untuk menerapkan pemeriksaan run-time pada parameter jenis yang tidak dapat diperiksa pada waktu kompilasi melalui batasan (§15.2.5).

Contoh: Jenis berikut menggunakan konstruktor statis untuk memberlakukan bahwa argumen jenis adalah enum:

class Gen<T> where T : struct
{
    static Gen()
    {
        if (!typeof(T).IsEnum)
        {
            throw new ArgumentException("T must be an enum");
        }
    }
}

contoh akhir

15.13 Pemfinalisasi

Catatan: Dalam spesifikasi versi sebelumnya, "finalizer" disebut sebagai "destructor". Pengalaman telah menunjukkan bahwa istilah "destructor" menyebabkan kebingungan dan sering mengakibatkan ekspektasi yang salah, terutama bagi pemrogram yang mengetahui C++. Di C++, destruktor dipanggil secara teratur, sedangkan dalam C#, finalizer tidak. Untuk mendapatkan perilaku penentuan dari C#, seseorang harus menggunakan Dispose. catatan akhir

Finalizer adalah anggota yang mengimplementasikan tindakan yang diperlukan untuk menyelesaikan instans kelas. Finalizer dideklarasikan menggunakan finalizer_declaration:

finalizer_declaration
    : attributes? '~' identifier '(' ')' finalizer_body
    | attributes? 'extern' unsafe_modifier? '~' identifier '(' ')'
      finalizer_body
    | attributes? unsafe_modifier 'extern'? '~' identifier '(' ')'
      finalizer_body
    ;

finalizer_body
    : block
    | '=>' expression ';'
    | ';'
    ;

unsafe_modifier (§23.2) hanya tersedia dalam kode tidak aman (§23).

Deklarasi finalizer dapat mencakup sekumpulan atribut (§22).

Pengidentifikasi identifier dari finalizer_declarator harus menamai kelas tempat finalizer dinyatakan. Jika ada nama lain yang ditentukan, kesalahan waktu kompilasi terjadi.

Ketika deklarasi finalizer menyertakan pengubah extern , finalizer dikatakan sebagai finalizer eksternal. Karena deklarasi finalizer eksternal tidak menyediakan implementasi aktual, finalizer_body-nya terdiri dari titik koma. Untuk semua finalizer lainnya, finalizer_body terdiri dari

  • blok, yang menentukan pernyataan apa yang harus dijalankan untuk mengakhiri sebuah instance dari kelas.
  • atau badan ekspresi, yang terdiri dari => diikuti oleh ekspresi dan titik koma, dan menunjukkan satu ekspresi yang dieksekusi untuk menyelesaikan instans kelas.

finalizer_body yang merupakan blok atau badan ekspresi berkorespondensi tepat dengan method_body metode instans dengan jenis pengembalian (§15.6.11).

Finalizer tidak diwariskan. Dengan demikian, kelas tidak memiliki finalizer selain yang mungkin dideklarasikan di kelas tersebut.

Catatan: Karena finalizer harus tidak memiliki parameter, itu tidak dapat dilebihkan, sehingga sebuah kelas hanya dapat memiliki satu finalizer. catatan akhir

Finalizer dipanggil secara otomatis, dan tidak dapat dipanggil secara eksplisit. Instans menjadi memenuhi syarat untuk finalisasi ketika tidak lagi memungkinkan kode apa pun untuk menggunakan instans tersebut. Eksekusi finalizer untuk instans dapat terjadi kapan saja setelah instans memenuhi syarat untuk finalisasi (§7,9). Ketika instans diselesaikan, finalizer dalam rantai warisan instans tersebut dipanggil, secara berurutan, dari yang paling berasal ke yang paling tidak diturunkan. Finalizer dapat dijalankan pada thread apa pun. Untuk diskusi lebih lanjut tentang aturan yang mengatur kapan dan bagaimana finalizer dijalankan, lihat §7.9.

Contoh: Output dari contoh

class A
{
    ~A()
    {
        Console.WriteLine("A's finalizer");
    }
}

class B : A
{
    ~B()
    {
        Console.WriteLine("B's finalizer");
    }
}

class Test
{
    static void Main()
    {
        B b = new B();
        b = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
}

adalah

B's finalizer
A's finalizer

karena finalizer dalam rantai warisan dipanggil secara berurutan, dari yang paling terderivasi ke yang paling tidak terderivasi.

contoh akhir

Finalizer diimplementasikan dengan cara menimpa metode virtual Finalize pada System.Object. Program C# tidak diizinkan untuk mengganti metode ini atau memanggil metode ini (atau versi yang menggantikan) secara langsung.

Contoh: Misalnya, program

class A
{
    override protected void Finalize() {}  // Error
    public void F()
    {
        this.Finalize();                   // Error
    }
}

berisi dua kesalahan.

contoh akhir

Pengkompilasi harus bertindak seolah-olah metode ini, dan penggantinya, tidak ada sama sekali.

Contoh: Dengan demikian, program ini:

class A
{
    void Finalize() {}  // Permitted
}

valid dan metode yang ditunjukkan menyembunyikan metode System.Object milik Finalize.

contoh akhir

Untuk diskusi tentang perilaku ketika pengecualian dilemparkan dari finalizer, lihat §21.4.

15.14 Fungsi Asinkron

15.14.1 Umum

Metode (§15,6) atau fungsi anonim (§12,19) dengan async pengubah disebut fungsi asinkron. Secara umum, istilah async digunakan untuk menggambarkan fungsi apapun yang memiliki pengubah async.

Ini adalah kesalahan waktu kompilasi jika daftar parameter dari fungsi asinkron menyertakan parameter in, out, ref, atau parameter dengan jenis ref struct.

Return_type metode asinkron adalah void, jenis tugas, atau jenis iterator asinkron (§15.15). Untuk metode asinkron yang menghasilkan nilai hasil, jenis tugas atau jenis iterator asinkron (§15.15.3) harus generik. Untuk metode asinkron yang tidak menghasilkan nilai hasil, jenis tugas tidak boleh generik. Jenis tersebut disebut dalam spesifikasi ini sebagai «TaskType»<T> dan «TaskType», masing-masing. Jenis System.Threading.Tasks.Task dan jenis pustaka Standar yang dibangun dari System.Threading.Tasks.Task<TResult> dan System.Threading.Tasks.ValueTask<T> merupakan jenis tugas, serta jenis kelas, struktur, atau antarmuka yang terkait dengan jenis pembuat tugas melalui atribut System.Runtime.CompilerServices.AsyncMethodBuilderAttribute. Jenis tersebut disebut dalam spesifikasi ini sebagai «TaskBuilderType»<T> dan «TaskBuilderType». Jenis tugas dapat memiliki paling banyak satu parameter jenis dan tidak dapat ditumpuk dalam jenis generik.

Metode async yang mengembalikan tipe tugas dikatakan pengembalian tugas.

Jenis tugas dapat bervariasi dalam definisi yang tepat, tetapi dari sudut pandang bahasa, jenis tugas berada di salah satu status yang tidak lengkap, berhasil, atau rusak. Tugas yang mengalami kesalahan mencatat pengecualian yang relevan. Berhasil merekam«TaskType»<T> hasil dari tipe T. Jenis tugas dapat ditunggu, dan oleh karena itu tugas dapat menjadi operan dari ekspresi tunggu (§12.9.8).

Contoh: Jenis tugas MyTask<T> dikaitkan dengan jenis pembuat tugas MyTaskMethodBuilder<T> dan jenis penunggu Awaiter<T>:

using System.Runtime.CompilerServices; 
[AsyncMethodBuilder(typeof(MyTaskMethodBuilder<>))]
class MyTask<T>
{
    public Awaiter<T> GetAwaiter() { ... }
}

class Awaiter<T> : INotifyCompletion
{
    public void OnCompleted(Action completion) { ... }
    public bool IsCompleted { get; }
    public T GetResult() { ... }
}

contoh akhir

Jenis penyusun tugas adalah jenis kelas atau struktur yang sesuai dengan jenis tugas tertentu (§15.14.2). Jenis pembuat tugas harus sama persis dengan aksesibilitas yang dinyatakan dari jenis tugas yang sesuai.

Catatan: Jika jenis tugas dideklarasikan internal, jenis penyusun yang sesuai juga harus dideklarasikan dan didefinisikan internal dalam rakitan yang sama. Jika jenis tugas ditumpuk dalam jenis lain, jenis pembangun tugas juga harus ditumpuk dalam jenis yang sama. catatan akhir

Fungsi async memiliki kemampuan untuk menangguhkan evaluasi dengan cara menggunakan ekspresi await (§12.9.8) dalam bagian dalamnya. Evaluasi nantinya dapat dilanjutkan pada titik ekspresi tunggu penangguhan dengan cara delegasi penerbitan ulang. Delegasi kelanjutan berjenis System.Action, dan ketika dipanggil, evaluasi pemanggilan fungsi asinkron akan dilanjutkan dari ekspresi tunggu tempat ia berhenti. Pemanggil saat ini fungsi async dari pemanggilan fungsi adalah pemanggil asli jika pemanggilan fungsi tidak pernah ditangguhkan atau sebaliknya, pemanggil terbaru dari delegasi kelanjutan.

15.14.2 Pola pembuat jenis tugas

Jenis penyusun tugas dapat memiliki paling banyak satu parameter jenis dan tidak dapat ditumpuk dalam jenis generik. Jenis pembuat tugas harus memiliki anggota berikut (untuk jenis pembuat tugas non-generik, SetResult tidak memiliki parameter) dengan aksesibilitas yang dinyatakan public :

class «TaskBuilderType»<T>
{
    public static «TaskBuilderType»<T> Create();
    public void Start<TStateMachine>(ref TStateMachine stateMachine)
                where TStateMachine : IAsyncStateMachine;
    public void SetStateMachine(IAsyncStateMachine stateMachine);
    public void SetException(Exception exception);
    public void SetResult(T result);
    public void AwaitOnCompleted<TAwaiter, TStateMachine>(
        ref TAwaiter awaiter, ref TStateMachine stateMachine)
        where TAwaiter : INotifyCompletion
        where TStateMachine : IAsyncStateMachine;
    public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
        ref TAwaiter awaiter, ref TStateMachine stateMachine)
        where TAwaiter : ICriticalNotifyCompletion
        where TStateMachine : IAsyncStateMachine;
    public «TaskType»<T> Task { get; }
}

Kompilator harus menghasilkan kode yang menggunakan «TaskBuilderType» untuk mengimplementasikan semantik menangguhkan dan melanjutkan evaluasi fungsi asinkron. Pengkompilasi akan menggunakan «TaskBuilderType» sebagai berikut:

  • «TaskBuilderType».Create() dipanggil untuk membuat instans dari «TaskBuilderType», bernama builder dalam daftar ini.
  • builder.Start(ref stateMachine) dipanggil untuk mengaitkan penyusun dengan instans komputer status yang dihasilkan kompilator, stateMachine.
    • Pembangun akan memanggil stateMachine.MoveNext() entah di Start() atau setelah Start() mengembalikan untuk memajukan mesin status.
  • Setelah Start() kembali, metode async memanggil builder.Task untuk fungsi kembali dari metode asinkron.
  • Setiap panggilan ke stateMachine.MoveNext() akan memajukan komputer status.
  • Jika mesin status menyelesaikan prosesnya dengan sukses, builder.SetResult() dipanggil, dengan nilai pengembalian metode, jika ada.
  • Jika tidak, jika pengecualian, e dilemparkan ke mesin status, builder.SetException(e) dipanggil.
  • Jika komputer status mencapai await expr ekspresi, expr.GetAwaiter() dipanggil.
  • Jika awaiter mengimplementasikan ICriticalNotifyCompletion dan IsCompleted false, mesin status memanggil builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine).
    • AwaitUnsafeOnCompleted() harus memanggil awaiter.UnsafeOnCompleted(action) dengan Action yang memanggil stateMachine.MoveNext() ketika awaiter selesai.
  • Jika tidak, state machine memanggil builder.AwaitOnCompleted(ref awaiter, ref stateMachine).
    • AwaitOnCompleted() harus memanggil awaiter.OnCompleted(action) dengan Action yang memanggil stateMachine.MoveNext() ketika awaiter selesai.
  • SetStateMachine(IAsyncStateMachine) dapat dipanggil oleh implementasi IAsyncStateMachine yang dihasilkan kompilator untuk mengidentifikasi instans pembangun yang terkait dengan instans mesin status, terutama untuk kasus di mana mesin status diimplementasikan sebagai tipe nilai.
    • Jika penyusun memanggil stateMachine.SetStateMachine(stateMachine), maka stateMachine akan memanggil builder.SetStateMachine(stateMachine) pada instans penyusun yang terkait denganstateMachine.

Catatan: Untuk SetResult(T result) dan «TaskType»<T> Task { get; }, parameter dan argumen masing-masing harus dapat dikonversi identitas ke T. Ini memungkinkan pembangun tipe tugas untuk mendukung tipe seperti tuple, di mana dua tipe yang tidak sama dapat dikonversi secara identitas. catatan akhir

15.14.3 Evaluasi fungsi asinkron yang mengembalikan tugas

Pemanggilan fungsi asinkron yang mengembalikan tugas akan menghasilkan sebuah instans dari jenis tugas yang dikembalikan. Ini disebut tugas pengembalian fungsi asinkron. Tugas awalnya dalam status tidak lengkap .

Isi fungsi asinkron kemudian dievaluasi hingga ditangguhkan (dengan mencapai ekspresi await) atau dihentikan, saat kontrol dikembalikan ke pemanggil bersama dengan tugas pengembaliannya.

Ketika isi fungsi asinkron berakhir, tugas pengembalian dipindahkan dari status tidak lengkap:

  • Jika isi fungsi berakhir sebagai hasil dari mencapai pernyataan pengembalian atau akhir isi, nilai hasil apa pun dicatat dalam tugas pengembalian, yang dimasukkan ke dalam status berhasil .
  • Jika isi fungsi berakhir karena tidak tertangkap OperationCanceledException, pengecualian direkam dalam tugas pengembalian yang dimasukkan ke dalam status dibatalkan .
  • Jika isi fungsi berakhir sebagai akibat dari pengecualian lain yang tidak tertangkap (§13.10.6) pengecualian dicatat dalam tugas pengembalian yang dimasukkan ke dalam status rusak .

15.14.4 Evaluasi fungsi asinkron yang mengembalikan void

Jika jenis pengembalian fungsi asinkron adalah void, evaluasi berbeda dari yang di atas dengan cara berikut: Karena tidak ada tugas yang dikembalikan, fungsi sebagai gantinya mengkomunikasikan penyelesaian dan pengecualian ke konteks sinkronisasi utas saat ini. Definisi yang tepat dari konteks sinkronisasi bergantung pada implementasi, tetapi merupakan gambaran tentang lokasi "di mana" utas saat ini berjalan. Konteks sinkronisasi diberi tahu ketika evaluasi fungsi asinkron void dimulai, berhasil diselesaikan, atau menyebabkan pengecualian yang tidak tertangkap dilemparkan.

Ini memungkinkan konteks untuk melacak berapa banyak fungsi asinkron void yang sedang berjalan di bawahnya dan untuk memutuskan cara menyebarluaskan pengecualian yang muncul dari mereka.

15.15 Iterator sinkron dan asinkron

15.15.1 Umum

Anggota fungsi (§12.6) atau fungsi lokal (§13.6.4) yang diimplementasikan menggunakan blok iterator (§13,3) disebut iterator. Blok iterator dapat digunakan sebagai isi anggota fungsi selama jenis pengembalian anggota fungsi yang sesuai adalah salah satu antarmuka enumerator (§15.15.2) atau salah satu antarmuka yang dapat dihitung (§15.15.3).

Fungsi asinkron (§15.14) yang diimplementasikan menggunakan blok iterator (§13,3) disebut iterator asinkron. Blok iterator asinkron dapat digunakan sebagai isi anggota fungsi selama jenis pengembalian anggota fungsi yang sesuai adalah antarmuka enumerator asinkron (§15.15.2) atau antarmuka enumerable asinkron (§15.15.3).

Blok iterator dapat terjadi sebagai method_body, operator_body atau accessor_body, sedangkan peristiwa, konstruktor instans, konstruktor statis dan finalizer tidak boleh diimplementasikan sebagai iterator sinkron atau asinkron.

Ketika anggota fungsi atau fungsi lokal diimplementasikan menggunakan blok iterator, terjadi kesalahan waktu kompilasi jika daftar parameter anggota fungsi menetapkan parameter in, out, atau ref mana pun, atau parameter jenis ref struct.

15.15.2 Antarmuka Enumerator

Antarmuka enumerator mencakup antarmuka System.Collections.IEnumerator yang non-generik dan semua instansiasi dari antarmuka System.Collections.Generic.IEnumerator<T> generik.

Antarmuka enumerator asinkron semua adalah instansiasi dari antarmuka generikSystem.Collections.Generic.IAsyncEnumerator<T>.

Demi ringkasnya, dalam subklaus ini dan saudara-saudaranya antarmuka ini dirujuk sebagai IEnumerator, IEnumerator<T>, dan IAsyncEnumerator<T>, masing-masing.

15.15.3 Antarmuka enumerable

Antarmuka enumerable adalah antarmuka non-generik System.Collections.IEnumerable dan semua instansiasi dari antarmuka generik System.Collections.Generic.IEnumerable<T>.

Antarmuka asinkron yang dapat dijumlahkan adalah semua instansiasi antarmuka System.Collections.Generic.IAsyncEnumerable<T>generik .

Demi ringkasnya, dalam subklaus ini dan saudara-saudaranya antarmuka ini dirujuk sebagai IEnumerable, IEnumerable<T>, dan IAsyncEnumerable<T>, masing-masing.

15.15.4 Jenis hasil

Iterator menghasilkan urutan nilai, semua jenis yang sama. Tipe ini disebut tipe hasil dari pengulang.

  • Jenis hasil iterator yang mengembalikan IEnumerator atau IEnumerable adalah object.
  • Jenis hasil iterator yang mengembalikan IEnumerator<T>, , IAsyncEnumerator<T>IEnumerable<T>, atau IAsyncEnumerable<T> adalah T.

15.15.5 Objek enumerator

15.15.5.1 Umum

Ketika anggota fungsi atau fungsi lokal yang mengembalikan jenis antarmuka enumerator diimplementasikan menggunakan blok iterator, memanggil fungsi tidak segera menjalankan kode di blok iterator. Sebagai gantinya, objek enumerator dibuat dan dikembalikan. Objek ini merangkum kode yang ditentukan dalam blok iterator, dan eksekusi kode di blok iterator terjadi ketika objek MoveNext atau MoveNextAsync metode enumerator dipanggil. Objek enumerator memiliki karakteristik berikut:

  • Ini mengimplementasikan System.IDisposable, IEnumerator dan IEnumerator<T>, atau System.IAsyncDisposable , IAsyncEnumerator<T>di mana T adalah jenis hasil iterator.
  • Ini diinisialisasi dengan salinan nilai argumen (jika ada) dan nilai instans yang diteruskan ke anggota fungsi.
  • Ini memiliki empat status potensial, sebelum, berjalan, ditangguhkan, dan setelahnya, dan awalnya dalam keadaan sebelumnya .

Objek enumerator biasanya merupakan instans kelas enumerator yang dihasilkan kompilator yang merangkum kode di blok iterator dan mengimplementasikan antarmuka enumerator, tetapi metode implementasi lainnya dimungkinkan. Jika kelas enumerator dihasilkan oleh pengkompilasi, kelas tersebut akan ditumpuk, secara langsung atau tidak langsung, di kelas yang berisi anggota fungsi, kelas tersebut akan memiliki aksesibilitas privat, dan akan memiliki nama yang dicadangkan untuk penggunaan kompilator (§6.4.3).

Objek enumerator dapat mengimplementasikan lebih banyak antarmuka daripada yang ditentukan di atas.

Subklaus berikut menggambarkan perilaku yang diperlukan dari anggota untuk memajukan enumerator, memperoleh nilai saat ini dari enumerator, dan membuang sumber daya yang digunakan oleh enumerator. Ini didefinisikan dalam anggota berikut untuk enumerator sinkron dan asinkron, masing-masing:

  • Untuk memajukan enumerator: MoveNext dan MoveNextAsync.
  • Untuk mengambil nilai saat ini: Current.
  • Untuk membuang sumber daya: Dispose dan DisposeAsync.

Objek enumerator tidak mendukung metode .IEnumerator.Reset Memanggil metode ini menyebabkan System.NotSupportedException dilempar.

Blok iterator sinkron dan asinkron berbeda dalam hal anggota iterator asinkron mengembalikan jenis tugas dan bisa di-"await".

15.15.5.2 Majukan penghitung

Metode MoveNext dan MoveNextAsync objek enumerator merangkum kode blok iterator. Memanggil metode MoveNext atau MoveNextAsync menjalankan kode di blok iterator dan mengatur properti Current pada objek enumerator secara tepat.

MoveNext mengembalikan nilai yang bool maknanya dijelaskan di bawah ini. MoveNextAsync mengembalikan ValueTask<bool> (§15.14.3). Nilai hasil tugas yang dikembalikan dari MoveNextAsync memiliki arti yang sama dengan nilai hasil dari MoveNext. Dalam deskripsi berikut, tindakan yang dijelaskan untuk MoveNext diterapkan MoveNextAsync dengan perbedaan berikut: Jika dinyatakan bahwa MoveNext mengembalikan true atau false, MoveNextAsync mengatur tugasnya ke status selesai , dan mengatur nilai hasil tugas ke nilai yang sesuai true atau false nilai.

Tindakan tepat yang dilakukan oleh MoveNext atau MoveNextAsync tergantung pada status objek enumerator saat dipanggil:

  • Jika status objek enumerator adalah sebelumnya, panggil MoveNext:
    • Mengubah status menjadi berjalan.
    • Menginisialisasi parameter (termasuk this) blok iterator ke nilai argumen dan nilai instans yang disimpan saat objek enumerator diinisialisasi.
    • Menjalankan blok iterasi dari awal hingga pelaksanaannya terganggu (seperti yang dijelaskan di bawah).
  • Jika status objek enumerator berjalan, hasil pemanggilan MoveNext tidak ditentukan.
  • Jika status objek enumerator ditangguhkan, panggil MoveNext:
    • Mengubah status menjadi berjalan.
    • Memulihkan nilai semua variabel dan parameter lokal (termasuk this) ke nilai yang disimpan saat eksekusi blok iterator terakhir ditangguhkan.

      Catatan: Konten objek apa pun yang dirujuk oleh variabel ini dapat berubah sejak panggilan sebelumnya ke MoveNext. catatan akhir

    • Memulai kembali eksekusi blok iterator tepat setelah pernyataan yield return yang menyebabkan penangguhan eksekusi dan berlanjut hingga eksekusi terganggu (seperti yang dijelaskan di bawah).
  • Jika status objek enumerator adalah after, pemanggilan MoveNext akan mengembalikan false.

Ketika MoveNext menjalankan blok iterator, eksekusi dapat diinterupsi dengan empat cara: Dengan yield return perintah, oleh yield break perintah, dengan mencapai akhir blok iterator, dan dengan pengecualian yang dilemparkan dan dipropagasi keluar dari blok iterator.

  • Ketika sebuah pernyataan ditemui (yield return):
    • Ekspresi yang diberikan dalam pernyataan dievaluasi, dikonversi secara implisit ke jenis hasil, dan ditetapkan ke Current properti objek enumerator.
    • Penangguhan eksekusi bagian tubuh iterator. Nilai semua variabel dan parameter lokal (termasuk this) disimpan, seperti lokasi pernyataan ini yield return . Jika pernyataan berada dalam satu atau beberapa blok yield return, blok "finally" yang terkait tidak dijalankan saat ini.
    • Status objek enumerator diubah menjadi ditangguhkan.
    • Metode MoveNext kembali true ke pemanggilnya, menunjukkan bahwa iterasi berhasil dimajukan ke nilai berikutnya.
  • Ketika sebuah pernyataan ditemui (yield break):
    • yield break Jika pernyataan berada dalam satu atau beberapa try blok, blok terkait finally dijalankan.
    • Status objek enumerator diubah menjadi setelahnya.
    • Metode MoveNext kembali false ke pemanggilnya, menunjukkan bahwa iterasi selesai.
  • Ketika ujung dari badan iterator ditemui:
    • Status objek enumerator diubah menjadi setelahnya.
    • Metode MoveNext kembali false ke pemanggilnya, menunjukkan bahwa iterasi selesai.
  • Ketika sebuah pengecualian dilemparkan dan menyebar keluar dari blok iterator:
    • Blok yang sesuai finally dalam badan iterator telah dijalankan oleh propagasi pengecualian.
    • Status objek enumerator diubah menjadi setelahnya.
    • Penyebaran pengecualian berlanjut ke metode pemanggil MoveNext.

15.15.5.3 Mengambil nilai saat ini

Properti objek Current enumerator dipengaruhi oleh yield return pernyataan di blok iterator.

Catatan: Properti Current adalah properti sinkron untuk objek iterator sinkron dan asinkron. catatan akhir

Ketika objek enumerator dalam status ditangguhkan , nilainya Current adalah nilai yang ditetapkan oleh panggilan sebelumnya ke MoveNext. Ketika objek enumerator berada di status sebelum, berjalan, atau setelah , hasil akses Current tidak ditentukan.

Untuk iterator dengan jenis hasil selain object, hasil mengakses Current melalui implementasi objek IEnumerable enumerator sesuai dengan mengakses Current melalui implementasi objek IEnumerator<T> enumerator dan mentransmisikan hasilnya ke object.

15.15.5.4 Membuang sumber daya

Metode Dispose atau DisposeAsync digunakan untuk membersihkan iterasi dengan membawa objek enumerator ke status setelah .

  • Jika status objek enumerator adalah sebelum, memanggil Dispose mengubah status menjadi sesudah.
  • Jika status objek enumerator berjalan, hasil pemanggilan Dispose tidak ditentukan.
  • Jika status objek enumerator ditangguhkan, panggil Dispose:
    • Mengubah status menjadi berjalan.
    • Menjalankan blok 'finally' seolah-olah pernyataan terakhir yang dijalankan adalah pernyataan yield return. Jika hal ini menyebabkan pengecualian dilemparkan dan disebarkan keluar dari tubuh iterator, status objek enumerator diatur ke setelah dan pengecualian diteruskan ke pemanggil metode Dispose.
    • Mengubah status menjadi setelah itu.
  • Jika status objek enumerator adalah setelah, memanggil Dispose tidak berpengaruh.

15.15.6 Objek enumerable

15.15.6.1 Umum

Ketika anggota fungsi atau fungsi lokal yang mengembalikan jenis antarmuka enumerable diimplementasikan menggunakan blok iterator, memanggil anggota fungsi tidak segera menjalankan kode di blok iterator. Sebagai gantinya, objek yang dapat dijumlahkan dibuat dan dikembalikan.

Objek enumerable GetEnumerator atau metode GetAsyncEnumerator mengembalikan objek enumerator yang merangkum kode yang ditentukan dalam blok iterator, dan eksekusi kode dalam blok iterator terjadi ketika metode objek enumerator MoveNext atau MoveNextAsync dipanggil. Objek yang dapat dijumlahkan memiliki karakteristik berikut:

  • Ini mengimplementasikan IEnumerable dan IEnumerable<T> atau IAsyncEnumerable<T>, di mana T adalah jenis hasil iterator.
  • Ini diinisialisasi dengan salinan nilai argumen (jika ada) dan nilai instans yang diteruskan ke anggota fungsi.

Objek enumerabel biasanya merupakan instans kelas enumerabel yang dihasilkan kompilator yang merangkum kode di blok iterator dan mengimplementasikan antarmuka yang dapat dihitung, tetapi metode implementasi lainnya dimungkinkan. Jika kelas enumerable dihasilkan oleh pengkompilasi, kelas tersebut akan ditumpuk, secara langsung atau tidak langsung, di kelas yang berisi anggota fungsi, kelas tersebut akan memiliki aksesibilitas privat, dan akan memiliki nama yang dicadangkan untuk penggunaan kompilator (§6.4.3).

Objek yang dapat dijumlahkan dapat mengimplementasikan lebih banyak antarmuka daripada yang ditentukan di atas.

Catatan: Misalnya, objek enumerable juga dapat mengimplementasikan IEnumerator dan IEnumerator<T>, memungkinkannya untuk berfungsi sebagai enumerable dan enumerator. Biasanya, pengimplementasian seperti itu akan mengembalikan instansinya sendiri (untuk menghemat alokasi) dari panggilan pertama ke GetEnumerator. Pemanggilan berikutnya dari GetEnumerator, jika ada, akan mengembalikan instans kelas baru, biasanya dari kelas yang sama, sehingga panggilan ke instans enumerator yang berbeda tidak akan mempengaruhi satu sama lain. Ini tidak dapat mengembalikan instance yang sama bahkan jika enumerator sebelumnya telah melewati titik akhir urutan, karena semua panggilan di masa mendatang ke enumerator yang telah habis akan melempar pengecualian. catatan akhir

15.15.6.2 Metode GetEnumerator atau GetAsyncEnumerator

Objek enumerable menyediakan implementasi GetEnumerator metode dari antarmuka IEnumerable dan IEnumerable<T>. Kedua GetEnumerator metode berbagi implementasi umum yang memperoleh dan mengembalikan objek enumerator yang tersedia. Objek enumerator diinisialisasi dengan nilai argumen dan nilai instans yang disimpan ketika objek enumerable diinisialisasi, tetapi jika tidak, objek enumerator berfungsi seperti yang dijelaskan dalam §15.15.5.

Objek enumerasi asinkron menyediakan implementasi metode GetAsyncEnumerator dari antarmuka IAsyncEnumerable<T>. Metode ini mengembalikan objek enumerator asinkron yang tersedia. Objek enumerator diinisialisasi dengan nilai argumen dan nilai instans yang disimpan ketika objek enumerable diinisialisasi, tetapi jika tidak, objek enumerator berfungsi seperti yang dijelaskan dalam §15.15.5.