Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
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 berupanull
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 metodeF
abstrak . KelasB
memperkenalkan metodeG
tambahan , tetapi karena tidak memberikan implementasi ,F
B
juga harus dinyatakan abstrak. KelasC
mengambil alihF
dan memberikan implementasi aktual. Karena tidak ada anggota abstrak diC
,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
atauabstract
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 protected
atauprotected 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 bentukT.I
, atau -
namespace_or_type-name adalah
T
dalam typeof_expression (§12.8.18) dengan bentuktypeof(T)
.
primary_expression (§12,8) diizinkan untuk mereferensikan kelas statis jika
-
primary_expression adalah
E
dalam member_access (§12.8.7) denganE.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 dariB
, danB
dikatakan berasal dariA
. KarenaA
tidak secara eksplisit menentukan kelas dasar langsung, kelas dasar langsungnya adalahobject
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 adalahB<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.Delegate
System.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 dariZ
dianggap sebagaiobject
, dan karena itu (sesuai aturan §7.8)Z
tidak dianggap memiliki anggotaY
.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 adalahC<int[]>
, ,B<IComparable<int[]>>
,A
danobject
.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 padaB
(kelas yang langsung melingkupinya), yang secara sirkuler bergantung padaA
.contoh akhir
Sebuah kelas tidak bergantung pada kelas-kelas yang ditempatkan di dalamnya.
Contoh: Dalam kode berikut
class A { class B : A {} }
B
tergantung padaA
(karenaA
merupakan kelas dasar langsung dan kelas yang segera tertutup), tetapiA
tidak bergantung padaB
(karenaB
bukan kelas dasar atau kelasA
penutup ). 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 kelasA
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
adalahIA
,IB
, danIC
.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
atauT : BaseClass
), tetapi gunakanT?
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 untukT
. Dengan demikian, jenis formulirT??
yang dibuat secara rekursif danNullable<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
atauSystem.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 jenisS
, makaS
bergantung padaT
. - Jika parameter
S
jenis bergantung pada parameterT
jenis danT
bergantung pada parameterU
jenis, makaS
bergantung 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 sehinggaS
akan dipaksa untuk menjadi jenis yang sama denganT
, menghilangkan kebutuhan untuk dua parameter jenis. - Jika
S
memiliki batasan jenis nilai makaT
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 jenisU
danU
memiliki kendala class_typeA
danT
memiliki kendala class_typeB
maka harus ada konversi identitas atau konversi referensi implisit dariA
keB
atau konversi referensi implisit dariB
keA
.
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 jenisOuter.Inner
berlapis makaCₓ
adalah jenisOuterₓ.Innerₓ
berlapis . - Jika
C
Cₓ
adalah jenis konstruksiG<A¹, ..., Aⁿ>
dengan argumen jenisA¹, ..., Aⁿ
, makaCₓ
adalah jenis konstruksiG<A¹ₓ, ..., Aⁿₓ>
. - Jika
C
adalah jenisE[]
array, makaCₓ
adalah jenisEₓ[]
array . - Jika
C
dinamis makaCₓ
adalahobject
. - Bila tidak, maka
Cₓ
adalahC
.
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
berisiSystem.ValueType
. - Untuk setiap batasan
T
yang merupakan jenis enumerasi,R
berisiSystem.Enum
. - Untuk setiap batasan dari
T
yang merupakan tipe delegasi,R
mengandung penghapusan dinamisnya. - Untuk setiap batasan
T
yang merupakan jenis array,R
berisiSystem.Array
. - Untuk setiap batasan
T
yang merupakan jenis kelas,R
berisi penghapusan dinamisnya.
Kemudian
- Jika
T
memiliki batasan jenis nilai, kelas dasarnya yang efektif adalahSystem.ValueType
. - Jika tidak, jika
R
kosong maka kelas dasar yang efektif adalahobject
. - Selain itu, kelas dasar yang efektif
T
adalah jenis yang paling banyak mencakup (§10.5.3) dari kumpulanR
. Jika set tidak memiliki jenis yang tercakup, kelas dasar efektif dariT
adalahobject
. 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 dix
karenaT
dibatasi untuk selalu mengimplementasikanIPrintable
.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
, ,out
danref
.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
danout
.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 generikGen
adalah "array dua dimensi dariT
", sehingga jenis anggotaa
dalam jenis yang dibangun di atasnya adalah "array dua dimensi dari array berdimensi tunggal dariint
", atauint[,][]
.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 dariB
, danB
berasal dariA
, makaC
mewarisi anggota yang dinyatakan dalamB
serta anggota yang dinyatakan dalamA
.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 publikint
G(string s)
yang tidak diwariskan, yang diperoleh dengan mengganti argumen tipeint
untuk parameter tipeT
.D<int>
juga memiliki anggota yang diwariskan dari deklarasi kelasB
. Anggota yang diwariskan ini ditentukan dengan terlebih dahulu menentukan jenisB<int[]>
D<int>
kelas dasar dengan menggantikanint
T
dalam spesifikasiB<T[]>
kelas dasar . Kemudian, sebagai argumen jenis keB
,int[]
diganti denganU
dalampublic U F(long index)
, menghasilkan anggotapublic 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) formulirE.M
,E
harus menunjukkan jenis yang memiliki anggotaM
. Ini adalah kesalahan waktu kompilasi jikaE
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.M
E
harus menggambarkan sebuah instans dari jenis yang memiliki anggotaM
. 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. MetodeG
ini menunjukkan bahwa dalam anggota fungsi statis, merupakan kesalahan saat waktu kompilasi untuk mengakses anggota instans melalui simple_name. Metode iniMain
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 kelasA
, dan kelasA
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
, atauprivate
), dan, seperti anggota struct lainnya, default ke aksesibilitas yang dinyatakanprivate
.
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 metodeM
yang ditentukan dalamBase
.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 instansNested
, dan meneruskan this-nya sendiri ke konstruktorNested
untuk menyediakan akses berikutnya ke anggota instansC
.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 kelasNested
tertanam. DalamNested
, metodeG
memanggil metodeF
statis yang ditentukan dalamC
, danF
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 metodeF
yang dilindungi yang ditentukan dalamDerived
kelas dasar,Base
, dengan memanggil melalui instansDerived
.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:
- Untuk memungkinkan implementasi yang mendasarinya menggunakan pengidentifikasi biasa sebagai nama metode untuk mendapatkan atau mengatur akses ke fitur bahasa C#.
- Untuk memungkinkan bahasa lain beroperasi menggunakan pengidentifikasi biasa sebagai nama metode untuk mendapatkan atau mengatur akses ke fitur bahasa C#.
- 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-sajaP
, sehingga menjaga tanda tangan untuk metodeget_P
danset_P
.A
classB
diturunkan dariA
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 operatornew
, dan karena operatornew
tidak diizinkan dalam constant_expression, satu-satunya nilai yang mungkin untuk konstanta dari reference_type selainstring
adalahnull
. 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
danreadonly
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 mengevaluasiB.Z
, dan akhirnya mengevaluasiA.X
, menghasilkan nilai10
,11
, dan12
.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
danB
dinyatakan dalam program terpisah, akan mungkin untukA.X
bergantung padaB.Z
, tetapiB.Z
kemudian tidak dapat secara bersamaan bergantung padaA.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
, danBlue
tidak dapat dideklarasikan sebagai const karena nilai mereka tidak dapat dihitung pada waktu kompilasi. Namun, mendeklarasikannya sebagai gantinyastatic 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
danProgram2
menunjukkan dua program yang dikompilasi secara terpisah. KarenaProgram1.Utils.X
dinyatakan sebagaistatic readonly
field, output nilai oleh perintahConsole.WriteLine
tidak diketahui pada waktu kompilasi, melainkan diperoleh pada waktu eksekusi. Dengan demikian, jika nilaiX
diubah danProgram1
dikompresi ulang,Console.WriteLine
pernyataan akan menghasilkan nilai baru meskipunProgram2
tidak dikompresi ulang. Namun, telahX
menjadi konstanta, nilaiX
akan diperoleh pada saatProgram2
itu dikompilasi, dan akan tetap tidak terpengaruh oleh perubahan diProgram1
sampaiProgram2
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
, atauSystem.UIntPtr
. -
enum_type yang memiliki tipe enum_base berupa
byte
,sbyte
,short
,ushort
,int
, atauuint
.
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 metodeThread2
. Metode ini menyimpan nilai ke dalam bidang non-volatil yang disebutresult
, lalu disimpantrue
di bidangfinished
volatil . Thread utama menunggu bidangfinished
diatur ketrue
, lalu membaca bidangresult
. Karenafinished
telah dinyatakanvolatile
, utas utama harus membaca nilai143
dari bidangresult
. Jika bidangfinished
belum dinyatakanvolatile
, maka akan diperbolehkan bagi penyimpanan keresult
untuk terlihat oleh utas utama setelah penyimpanan kefinished
, dan karenanya utas utama dapat membaca nilai 0 dari bidangresult
. Mendeklarasikanfinished
sebagaivolatile
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
dani
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 kei
dans
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
danb
, program ini valid. Ini menghasilkan outputa = 1, b = 2
karena bidang statis
a
danb
diinisialisasi ke0
(nilai default untukint
) sebelum penginisialisasiannya dijalankan. Ketika penginisialisasi untuka
dijalankan, nilainyab
adalah nol, sehinggaa
diinisialisasi ke1
. Ketika inisialisasi untukb
dijalankan, nilai dari a sudah1
, dan oleh karena itub
diinisialisasi menjadi2
.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 penginisialisasiY
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 statisB
) harus berjalan sebelum konstruktor statis dan penginisialisasi fieldA
.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
, ,virtual
danoverride
. - Deklarasi mencakup paling banyak salah satu pengubah berikut:
new
danoverride
. - Jika deklarasi menyertakan pengubah
abstract
, maka deklarasi tidak menyertakan salah satu pengubah berikut:static
, ,virtual
sealed
, atauextern
. - Jika deklarasi menyertakan pengubah
private
, maka deklarasi tidak menyertakan salah satu pengubah berikut:virtual
, ,override
atauabstract
. - Jika deklarasi menyertakan pengubah
sealed
, maka deklarasi juga menyertakan pengubahoverride
. - Jika deklarasi menyertakan
partial
pengubah, maka itu tidak termasuk salah satu pengubah berikut:new
, ,public
,protected
,internal
,private
virtual
, ,sealed
override
, ,abstract
, atauextern
.
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
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
, , out
dan 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
, ref
atau 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 manaS
adalah jenis nilai - ekspresi formulir
default(S)
di manaS
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
, ,s
o
dant
merupakan parameter nilai opsional dana
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:
- Parameter nilai (§15.6.2.2).
- Parameter masukan (§15.6.2.3.2).
- Parameter keluaran (§15.6.2.3.4).
- Parameter referensi (§15.6.2.3.3).
- Parameter array (§15.6.2.4).
Catatan: Seperti yang dijelaskan dalam §7.6,
in
out
ref
adalah bagian dari tanda tangan metode, tetapiparams
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
dalamMain
,x
mewakilii
dany
mewakilij
. Dengan demikian, pemanggilan memiliki efek bertukar nilaii
danj
.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
diG
meneruskan referensi kes
untuka
danb
. Dengan demikian, untuk pemanggilan itu, namas
, ,a
danb
semuanya merujuk ke lokasi penyimpanan yang sama, dan ketiga penugasan semuanya memodifikasi bidangs
instans .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
danname
variabel mungkin tidak ditetapkan sebelum diteruskan keSplitPath
, 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[]
danstring[][]
dapat digunakan sebagai jenis array parameter, tetapi jenisnyastring[,]
tidak bisa. contoh akhir
Catatan: Tidak dimungkinkan untuk menggabungkan pengubah
params
dengan pengubahin
, ,out
atauref
. 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 arrayarr
sebagai parameter nilai. Pemanggilan kedua fungsi F secara otomatis membuat array berisi empat elemenint[]
dengan nilai elemen yang diberikan dan meneruskan instans array tersebut sebagai parameter nilai. Demikian juga, pemanggilanF
ketiga membuat elemenint[]
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 denganF(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
F
pertama dan terakhir , bentukF
normal berlaku karena konversi implisit ada dari jenis argumen ke jenis parameter (keduanya berjenisobject[]
). Dengan demikian, resolusi kelebihan beban memilih bentukF
normal , dan argumen diteruskan sebagai parameter nilai reguler. Dalam pemanggilan kedua dan ketiga, bentukF
normal tidak berlaku karena tidak ada konversi implisit dari jenis argumen ke jenis parameter (jenisobject
tidak dapat dikonversi secara implisit ke jenisobject[]
). Namun, bentuk terluas dariF
dapat diterapkan, sehingga dipilih melalui resolusi kelebihan beban. Akibatnya, satu elemenobject[]
dibuat oleh pemanggilan, dan elemen tunggal array diinisialisasi dengan nilai argumen yang diberikan (yang itu sendiri adalah referensi keobject[]
).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
, ,N
danA
, untuk memilih metodeM
tertentu dari set metode yang dideklarasikan dan diwarisi olehC
. 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 dariM
sehubungan denganR
akan dipanggil.
- Jika
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 pengenalanM
, maka ini adalah implementasi turunan paling akhir sehubungan denganM
R
. - Jika
R
berisi penimpaanM
, maka ini adalah implementasi yang paling terderivasi sehubungan denganM
danR
. - Jika tidak, implementasi yang paling turunan dari
M
sehubungan denganR
adalah sama dengan implementasi yang paling turunan dariM
sehubungan dengan kelas dasar langsung dariR
.
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-virtualF
dan metode virtualG
.B
Kelas ini memperkenalkan metode non-virtual baru , sehingga menyembunyikan metodeF
yang diwariskan , dan juga meng-override metode yang diwariskanF
. Contoh menghasilkan output:A.F B.F B.G B.G
Perhatikan bahwa pernyataan
a.G()
memanggilB.G
, bukanA.G
. Ini karena jenis run-time instans (yaituB
), bukan jenis waktu kompilasi instans (yaituA
), 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
danD
berisi dua metode virtual dengan tanda tangan yang sama: satu diperkenalkan olehA
dan satu lagi diperkenalkan olehC
. Metode yang diperkenalkan denganC
menyembunyikan metode yang diwariskan dariA
. Dengan demikian, deklarasi penimpaan dalamD
mengambil alih metode yang diperkenalkan olehC
, dan tidak mungkin untukD
mengambil alih metode yang diperkenalkan olehA
. 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()
dalamB
memanggil metode PrintFields yang dideklarasikan dalamA
. base_access menonaktifkan mekanisme pemanggilan virtual dan hanya memperlakukan metode dasar sebagai metode non-virtual
. Jika pemanggilanB
ditulis((A)this).PrintFields()
, itu akan memanggil secara rekursif metodePrintFields
yang dideklarasikan dalamB
, bukan yang dideklarasikan dalamA
, karenaPrintFields
bersifat virtual dan jenis run-time dari((A)this)
adalahB
.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 dalamB
tidak termasuk pengubahoverride
dan oleh karena itu tidak meng-override metode dalamF
. Sebaliknya, metodeF
dalamB
menyembunyikan metode dalamA
, 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 dalamB
menyembunyikan metode virtualF
yang diwarisi dariA
. KarenaF
yang baru diB
memiliki akses privat, cakupannya hanya mencakup badan kelasB
dan tidak meluas keC
. Oleh karena itu, deklarasiF
dalamC
diizinkan untuk menggantikanF
yang diwarisi dariA
.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: metodeF
yang memiliki modifiersealed
dan metodeG
tanpa modifier.B
menggunakan pengubahsealed
untuk mencegahC
agar tidak lebih lanjut menimpaF
.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. MetodePaint
ini abstrak karena tidak ada implementasi default yang bermakna. KelasEllipse
danBox
adalah implementasi konkretShape
. Karena kelas-kelas ini non-abstrak, mereka diharuskan untuk mengganti metodePaint
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, kelasB
mengambil alih metode ini dengan metode abstrak, dan kelasC
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 danDllImport
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 M
parsial , 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 padastring[]
, danToInt32
metode tersedia distring
, 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. MetodeG
danH
benar karena semua jalur eksekusi yang mungkin berakhir dengan pernyataan pengembalian yang menentukan nilai pengembalian. MetodeI
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
, atauref
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
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_modifier
readonly
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 berupaprivate protected
,protected internal
,internal
,protected
, atauprivate
. - Jika properti atau pengindeks memiliki aksesibilitas yang dideklarasikan sebagai
protected internal
, maka aksesibilitas yang dideklarasikan oleh accessor_modifier dapat berupaprivate protected
,protected private
,internal
,protected
, atauprivate
. - Jika properti atau pengindeks memiliki aksesibilitas yang dinyatakan sebagai
internal
atauprotected
, maka aksesibilitas yang dinyatakan oleh accessor_modifier haruslahprivate protected
atauprivate
. - Jika properti atau pengindeks memiliki aksesibilitas yang dideklarasikan sebagai
private protected
, maka aksesibilitas yang dinyatakan oleh accessor_modifier harusprivate
. - Jika properti atau pengindeks memiliki aksesibilitas yang dinyatakan sebagai
private
, maka tidak ada accessor_modifier yang dapat digunakan.
- Jika properti atau pengindeks memiliki aksesibilitas yang dideklarasikan sebagai
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 olehref
, 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 mengembalikanstring
yang disimpan di dalam bidang privatcaption
. 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 diprivate
bidang, dan aksesor set memodifikasi bidang tersebut dan kemudian melakukan tindakan tambahan yangprivate
diperlukan untuk memperbarui sepenuhnya keadaan objek. Mengingat kelas diButton
atas, berikut ini adalah contoh penggunaanCaption
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
diB
menyembunyikan propertiP
diA
baik dalam hal membaca maupun menulis. Oleh karena itu, dalam pernyataanB 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-sajaP
diB
menyembunyikan properti tulis-sajaP
diA
. Namun, perlu dicatat bahwa sebuah cast dapat digunakan untuk mengakses propertiP
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 duaint
bidang,x
dany
, untuk menyimpan lokasinya. Lokasi diekspos secara publik baik sebagai propertiX
danY
maupun sebagai propertiLocation
jenisPoint
. Jika, dalam versiLabel
yang akan datang, menjadi lebih praktis untuk menyimpan lokasi sebagai internalPoint
, 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
dany
adalah atributpublic readonly
, tidak mungkin untuk membuat perubahan seperti itu pada kelasLabel
.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
, danError
, 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 propertiOut
, seperti dalamConsole.Out.WriteLine("hello, world");
Dasar
TextWriter
dari perangkat keluaran telah dibuat. Namun, jika aplikasi tidak membuat referensi keIn
properti danError
, 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 propertiB.Text
, bahkan dalam konteks di mana hanya aksesor yang ditetapkan yang dipanggil. Sebaliknya, propertiB.Count
tidak dapat diakses oleh kelasM
, sehingga propertiA.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 private
accessor_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, danZ
merupakan properti baca-tulis abstrak. KarenaZ
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
, danZ
menggantikan deklarasi properti. Setiap deklarasi properti sama persis dengan pengubah aksesibilitas, jenis, dan nama properti yang diwariskan yang sesuai. Aksesor get dariX
dan aksesor set dariY
menggunakan kata kunci 'base' untuk mengakses aksesor yang diwariskan. DeklarasiZ
menggantikan kedua pengakses abstrak—dengan demikian, tidak ada anggota fungsi yang belum diimplementasikanabstract
diB
, danB
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 instansButton
dan melampirkan penangan kejadian ke kejadianClick
.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 dalamButton
kelas . Seperti yang ditunjukkan dalam contoh, bidang tersebut dapat diperiksa, dimodifikasi, dan digunakan dalam ekspresi pemanggilan delegate. MetodeOnClick
pada kelasButton
"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
, anggotaClick
hanya dapat digunakan pada sisi kiri operator+=
dan–=
, seperti dalamb.Click += new EventHandler(...);
yang menambahkan delegasi ke daftar pemanggilan peristiwa
Click
, danClick –= 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 keEv
di sisi kiri operator+=
dan–=
menyebabkan aksesor penambahan dan penghapusan dipanggil. Semua referensi lain keEv
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. MetodeAddEventHandler
mengaitkan nilai delegasi dengan kunci, metodeGetEventHandler
mengembalikan delegasi yang saat ini terkait dengan kunci, dan metodeRemoveEventHandler
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,
parameter_list menentukan parameter pengindeks. Daftar parameter pengindeks sesuai dengan metode (§15.6.2), kecuali bahwa setidaknya satu parameter harus ditentukan, dan bahwa this
pengubah parameter , , ref
dan 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
, atauref
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 kelasbool[]
yang setara (karena setiap nilainya hanya membutuhkan satu bit ketimbangbyte
yang membutuhkan satubyte
), tetapi mengizinkan operasi yang sama dengan .Kelas berikut
CountPrimes
menggunakanBitArray
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 untukbool[]
.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 bernamavalue
. - 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 manaP
adalah nama properti. Dalam deklarasi penganuliran pengindeks, pengindeks yang diwarisi diakses dengan menggunakan sintaksbase[E]
, di manaE
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 pengubahstatic
. - 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 jenisT
atauT?
dan dapat mengembalikan jenis apa pun. - Unary
++
atau--
operator harus mengambil satu parameter dari jenisT
atauT?
dan harus mengembalikan jenis yang sama atau jenis yang diturunkan darinya. - Operator unary
true
ataufalse
harus menggunakan satu parameter jenisT
atauT?
dan harus mengembalikan jenisbool
.
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
atauT?
, dan dapat mengembalikan jenis apa pun. - Operator biner
<<
atau>>
(§12,11) harus mengambil dua parameter, yang pertama harus memiliki jenisT
atauT?
dan yang kedua harus memiliki jenisint
atauint?
, 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 S
T
target hanya jika semua hal berikut ini benar:
S₀
danT₀
merupakan jenis yang berbeda.Baik
S₀
atauT₀
merupakan jenis instans kelas atau struktur yang berisi deklarasi operator.Baik
S₀
maupunT₀
bukan interface_type.Tidak termasuk konversi yang ditentukan pengguna, konversi tidak ada dari
S
keT
atau dariT
keS
.
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
danint
danstring
, masing-masing dianggap sebagai jenis unik tanpa hubungan. Namun, operator ketiga adalah kesalahan karenaC<T>
merupakan kelas dasar dariD<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 dariC
keint
dan dariint
keC
, tetapi tidak dariint
kebool
. 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 untukT
, 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 tipeT
, semua konversi yang ditentukan pengguna (implisit atau eksplisit) dariS
keT
diabaikan. - Jika konversi eksplisit yang sudah ditetapkan sebelumnya (§10.3) ada dari jenis
S
ke jenisT
, konversi eksplisit yang ditentukan pengguna dariS
keT
diabaikan. Selanjutnya:- Jika salah satu
S
atauT
merupakan jenis antarmuka, konversi implisit yang ditentukan pengguna dariS
keT
diabaikan. - Jika tidak, konversi implisit yang ditentukan pengguna dari
S
keT
masih dipertimbangkan.
- Jika salah satu
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
kebyte
adalah implisit karena tidak pernah melemparkan pengecualian atau kehilangan informasi, tetapi konversi daribyte
keDigit
adalah eksplisit karenaDigit
hanya dapat mewakili subset dari nilai yangbyte
mungkin 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 instansB
, output berikut dihasilkan:x = 1, y = 0
Nilai
x
adalah 1 karena penginisialisasi variabel dijalankan sebelum konstruktor instans kelas dasar dipanggil. Namun, nilaiy
adalah 0 (nilaiint
default ) karena penugasan untuky
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. Contohclass 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
danthis
). 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
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 (
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
A
konstruktor statis dipicu oleh panggilan keA.F
, dan eksekusiB
konstruktor statis dipicu oleh panggilan keB.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 untukB.Y
, sebelum konstruktor statis kelasB
.Y
penginisialisasi menyebabkan konstruktorA
static
untuk dijalankan karena nilaiA.X
dirujuk. Konstruktor statis padaA
pada gilirannya melanjutkan menghitung nilaiX
, dan dalam melakukannya mengambil nilai defaultY
, yaitu nol.A.X
dengan demikian diinisialisasi menjadi 1. Proses menjalankanA
penginisialisasi bidang statis dan konstruktor statis kemudian selesai, kembali ke perhitungan nilai awalY
, 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
milikFinalize
.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 tugasMyTaskMethodBuilder<T>
dan jenis penungguAwaiter<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 didefinisikaninternal
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», bernamabuilder
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 diStart()
atau setelahStart()
mengembalikan untuk memajukan mesin status.
- Pembangun akan memanggil
- Setelah
Start()
kembali, metodeasync
memanggilbuilder.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
danIsCompleted
false, mesin status memanggilbuilder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine)
.-
AwaitUnsafeOnCompleted()
harus memanggilawaiter.UnsafeOnCompleted(action)
denganAction
yang memanggilstateMachine.MoveNext()
ketika awaiter selesai.
-
- Jika tidak, state machine memanggil
builder.AwaitOnCompleted(ref awaiter, ref stateMachine)
.-
AwaitOnCompleted()
harus memanggilawaiter.OnCompleted(action)
denganAction
yang memanggilstateMachine.MoveNext()
ketika awaiter selesai.
-
-
SetStateMachine(IAsyncStateMachine)
dapat dipanggil oleh implementasiIAsyncStateMachine
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)
, makastateMachine
akan memanggilbuilder.SetStateMachine(stateMachine)
pada instans penyusun yang terkait denganstateMachine
.
- Jika penyusun memanggil
Catatan: Untuk
SetResult(T result)
dan«TaskType»<T> Task { get; }
, parameter dan argumen masing-masing harus dapat dikonversi identitas keT
. 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
atauIEnumerable
adalahobject
. - Jenis hasil iterator yang mengembalikan
IEnumerator<T>
, ,IAsyncEnumerator<T>
IEnumerable<T>
, atauIAsyncEnumerable<T>
adalahT
.
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
danIEnumerator<T>
, atauSystem.IAsyncDisposable
,IAsyncEnumerator<T>
di manaT
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
danMoveNextAsync
. - Untuk mengambil nilai saat ini:
Current
. - Untuk membuang sumber daya:
Dispose
danDisposeAsync
.
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 iniyield return
. Jika pernyataan berada dalam satu atau beberapa blokyield return
, blok "finally" yang terkait tidak dijalankan saat ini. - Status objek enumerator diubah menjadi ditangguhkan.
- Metode
MoveNext
kembalitrue
ke pemanggilnya, menunjukkan bahwa iterasi berhasil dimajukan ke nilai berikutnya.
- Ekspresi yang diberikan dalam pernyataan dievaluasi, dikonversi secara implisit ke jenis hasil, dan ditetapkan ke
- Ketika sebuah pernyataan ditemui (
yield break
):-
yield break
Jika pernyataan berada dalam satu atau beberapatry
blok, blok terkaitfinally
dijalankan. - Status objek enumerator diubah menjadi setelahnya.
- Metode
MoveNext
kembalifalse
ke pemanggilnya, menunjukkan bahwa iterasi selesai.
-
- Ketika ujung dari badan iterator ditemui:
- Status objek enumerator diubah menjadi setelahnya.
- Metode
MoveNext
kembalifalse
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
.
- Blok yang sesuai
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 metodeDispose
. - 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
danIEnumerable<T>
atauIAsyncEnumerable<T>
, di manaT
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
danIEnumerator<T>
, memungkinkannya untuk berfungsi sebagai enumerable dan enumerator. Biasanya, pengimplementasian seperti itu akan mengembalikan instansinya sendiri (untuk menghemat alokasi) dari panggilan pertama keGetEnumerator
. Pemanggilan berikutnya dariGetEnumerator
, 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.
ECMA C# draft specification