23 Atribut

23.1 Umum

Sebagian besar bahasa C# memungkinkan programmer menentukan informasi deklaratif tentang entitas yang ditentukan dalam program. Misalnya, aksesibilitas metode di kelas ditentukan dengan mendekorasinya dengan method_modifier, public, protectedinternal, dan private.

C# memungkinkan programmer untuk menciptakan jenis informasi deklaratif baru, yang disebut atributs. Programmer kemudian dapat melampirkan atribut ke berbagai entitas program, dan mengambil informasi atribut di lingkungan run-time.

Catatan: Misalnya, kerangka kerja mungkin menentukan HelpAttribute atribut yang dapat ditempatkan pada elemen program tertentu (seperti kelas dan metode) untuk menyediakan pemetaan dari elemen program tersebut ke dokumentasi mereka. catatan akhir

Atribut didefinisikan melalui deklarasi kelas atribut (§23.2), yang dapat memiliki parameter posisi dan bernama (§23.2.3). Atribut dilampirkan ke entitas dalam program C# menggunakan spesifikasi atribut (§23.3), dan dapat diambil pada run-time sebagai instans atribut (§23.4).

23.2 Kelas atribut

23.2.1 Umum

Kelas yang berasal dari kelas System.Attributeabstrak , baik secara langsung maupun tidak langsung, adalah kelas atribut. Deklarasi kelas atribut mendefinisikan jenis atribut baru yang dapat ditempatkan pada entitas program. Menurut konvensi, kelas atribut dinamai dengan akhiran Attribute. Penggunaan atribut dapat mencakup atau menghilangkan akhiran ini.

Deklarasi kelas generik tidak boleh digunakan System.Attribute sebagai kelas dasar langsung atau tidak langsung.

Contoh:

public class B : Attribute {}
public class C<T> : B {} // Error – generic cannot be an attribute

contoh akhir

23.2.2 Penggunaan atribut

Atribut AttributeUsage (§23.5.2) digunakan untuk menjelaskan bagaimana kelas atribut dapat digunakan.

AttributeUsage memiliki parameter posisi (§23.2.3) yang memungkinkan kelas atribut untuk menentukan jenis entitas program tempatnya dapat digunakan.

Contoh: Contoh berikut mendefinisikan kelas atribut bernama SimpleAttribute yang dapat ditempatkan pada class_declarationdan interface_declarations saja, dan menunjukkan beberapa penggunaan Simple atribut.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)]
public class SimpleAttribute : Attribute
{ 
    ... 
}

[Simple] class Class1 {...}
[Simple] interface Interface1 {...}

Meskipun atribut ini didefinisikan dengan nama SimpleAttribute, ketika atribut ini digunakan, Attribute akhiran dapat dihilangkan, menghasilkan nama Simplependek . Dengan demikian, contoh di atas secara semantik setara dengan yang berikut ini

[SimpleAttribute] class Class1 {...}
[SimpleAttribute] interface Interface1 {...}

contoh akhir

AttributeUsage memiliki parameter bernama (§23.2.3), yang disebut AllowMultiple, yang menunjukkan apakah atribut dapat ditentukan lebih dari sekali untuk entitas tertentu. Jika AllowMultiple untuk kelas atribut benar, kelas atribut tersebut adalah kelas atribut multi-penggunaan, dan dapat ditentukan lebih dari sekali pada entitas. Jika AllowMultiple untuk kelas atribut salah atau tidak ditentukan, kelas atribut tersebut adalah kelas atribut sekali pakai, dan dapat ditentukan paling banyak sekali pada entitas.

Contoh: Contoh berikut mendefinisikan kelas atribut multi-penggunaan bernama AuthorAttribute dan menunjukkan deklarasi kelas dengan dua penggunaan Author atribut:

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class AuthorAttribute : Attribute
{
    public string Name { get; }
    public AuthorAttribute(string name) => Name = name;
}

[Author("Brian Kernighan"), Author("Dennis Ritchie")]
class Class1 
{
    ...
}

contoh akhir

AttributeUsage memiliki parameter bernama lain (§23.2.3), yang disebut Inherited, yang menunjukkan apakah atribut, ketika ditentukan pada kelas dasar, juga diwarisi oleh kelas yang berasal dari kelas dasar tersebut. Jika Inherited untuk kelas atribut benar, maka atribut tersebut diwariskan. Jika Inherited untuk kelas atribut salah, atribut tersebut tidak diwariskan. Jika tidak ditentukan, nilai defaultnya adalah true.

Kelas X atribut yang tidak memiliki atribut yang AttributeUsage melekat padanya, seperti dalam

class X : Attribute { ... }

setara dengan yang berikut ini:

[AttributeUsage(
   AttributeTargets.All,
   AllowMultiple = false,
   Inherited = true)
]
class X : Attribute { ... }

23.2.3 Parameter posisi dan bernama

Kelas atribut dapat memiliki parameterposisi dan parameterbernama s. Setiap konstruktor instans publik untuk kelas atribut menentukan urutan parameter posisi yang valid untuk kelas atribut tersebut. Setiap bidang baca-tulis publik non-statis dan properti baca-tulis atau baca-init publik non-statis untuk kelas atribut menentukan parameter bernama untuk kelas atribut. Agar properti menentukan parameter bernama, properti tersebut harus memiliki aksesor get publik dan set publik atau init accessor.

Contoh: Contoh berikut mendefinisikan kelas atribut bernama HelpAttribute yang memiliki satu parameter posisi, url, dan satu parameter bernama, Topic. Meskipun tidak statis dan publik, properti Url tidak menentukan parameter bernama, karena bukan baca-tulis atau baca-init. Dua penggunaan atribut ini juga ditampilkan:

[AttributeUsage(AttributeTargets.Class)]
public class HelpAttribute : Attribute
{
    public HelpAttribute(string url) // url is a positional parameter
    { 
        ...
    }

    // Topic is a named parameter
    public string Topic
    { 
        get;
        set;
    }

    public string Url { get; }
}

[Help("http://www.mycompany.com/xxx/Class1.htm")]
class Class1
{
}

[Help("http://www.mycompany.com/xxx/Misc.htm", Topic ="Class2")]
class Class2
{
}

contoh akhir

23.2.4 Jenis parameter atribut

Jenis parameter posisi dan bernama untuk kelas atribut terbatas pada jenis parameter atributs, yaitu:

  • Salah satu jenis berikut: bool, , byte, char, double, float, intlong, sbyte, , short, string, uint, ulong, ushort.
  • Jenis object.
  • Jenis System.Type.
  • Jenis enum.
  • Array dimensi tunggal dari jenis di atas.
  • Argumen konstruktor atau bidang publik yang tidak memiliki salah satu jenis ini, tidak boleh digunakan sebagai parameter posisi atau bernama dalam spesifikasi atribut.

23.3 Spesifikasi atribut

Aplikasi atribut yang ditentukan sebelumnya ke entitas program disebut spesifikasi atribut. Atribut adalah bagian dari informasi deklaratif tambahan yang ditentukan untuk entitas program. Atribut dapat ditentukan pada cakupan global (untuk menentukan atribut pada rakitan atau modul yang berisi) dan untuk type_declaration(§14,8), class_member_declarations (§15,3), interface_member_declarations (§19 4, struct_member_declaration(§16,3), enum_member_declarations (§20,2), accessor_declarations (§15,7,3), event_accessor_declarations (§15,8), local_function_declarations (§13,6,4), elemen parameter_lists (§15,6,2), elemen type_parameter_lists (§15.2), elemen type_parameter_list s (§15.2) 2,3), lambda_expression(§12.22.1), dan elemen explicit_anonymous_function_parameterdan implicit_anonymous_function_parameter(§12.22.1).

Atribut ditentukan dalam bagian atributs. Bagian atribut terdiri dari sepasang kurung siku, yang mengelilingi daftar yang dipisahkan koma dari satu atau beberapa atribut. Urutan di mana atribut ditentukan dalam daftar seperti itu, dan urutan di mana bagian yang dilampirkan ke entitas program yang sama diatur, tidak signifikan. Misalnya, spesifikasi [A][B]atribut , , [B][A][A, B], dan [B, A] setara.

global_attributes
    : global_attribute_section+
    ;

global_attribute_section
    : '[' global_attribute_target_specifier attribute_list ']'
    ;

global_attribute_target_specifier
    : global_attribute_target ':'
    ;

global_attribute_target
    : identifier
    ;

attributes
    : attribute_section+
    ;

attribute_section
    : '[' attribute_target_specifier? attribute_list ']'
    ;

attribute_target_specifier
    : attribute_target ':'
    ;

attribute_target
    : identifier
    | keyword
    ;

attribute_list
    : attribute (',' attribute)* ','?
    ;

attribute
    : attribute_name attribute_arguments?
    ;

attribute_name
    : type_name
    ;

attribute_arguments
    : '(' ')'
    | '(' positional_argument_list (',' named_argument_list)? ')'
    | '(' named_argument_list ')'
    ;

positional_argument_list
    : positional_argument (',' positional_argument)*
    ;

positional_argument
    : argument_name? attribute_argument_expression
    ;

named_argument_list
    : named_argument (','  named_argument)*
    ;

named_argument
    : identifier '=' attribute_argument_expression
    ;

attribute_argument_expression
    : non_assignment_expression
    ;

Untuk global_attribute_target produksi, dan dalam teks di bawah ini, pengidentifikasi harus memiliki ejaan yang sama dengan atau , di mana kesetaraan assembly yang ditentukan dalam module. Untuk attribute_target produksi, dan dalam teks di bawah ini, pengidentifikasi harus memiliki ejaan yang tidak sama dengan assembly atau module, menggunakan definisi kesetaraan yang sama seperti di atas.

Atribut terdiri dari attribute_name dan daftar opsional argumen posisi dan bernama. Argumen posisi (jika ada) mendahului argumen bernama. Argumen posisi terdiri dari attribute_argument_expression; argumen bernama terdiri dari nama, diikuti dengan tanda sama dengan, diikuti oleh attribute_argument_expression, yang, bersama-sama, dibatasi oleh aturan yang sama dengan penugasan sederhana. Urutan argumen bernama tidak signifikan.

Catatan: Untuk kenyamanan, koma berikutnya diizinkan dalam global_attribute_section dan attribute_section, sama seperti yang diizinkan dalam array_initializer (§17,7). catatan akhir

attribute_name mengidentifikasi kelas atribut.

Saat atribut ditempatkan di tingkat global, global_attribute_target_specifier diperlukan. Saat global_attribute_target sama dengan:

  • assembly — targetnya adalah rakitan yang berisi
  • module — targetnya adalah modul yang berisi

Tidak ada nilai lain untuk global_attribute_target yang diizinkan.

Nama attribute_target yang distandarkan adalah , , , event, fieldmethod, param, property, dan return.typetypevar Nama target ini hanya akan digunakan dalam konteks berikut:

  • event — sebuah peristiwa.
  • field — bidang. Peristiwa seperti bidang (yaitu, satu tanpa pengakses) (§15.8.2) dan properti yang diimplementasikan secara otomatis (§15.7.4) juga dapat memiliki atribut dengan target ini.
  • method — konstruktor; finalizer; Metode; Operator; fungsi lokal, properti dapatkan, atur, dan init accessor; pengindeks mendapatkan, mengatur, dan memulai pengakses; peristiwa menambahkan dan menghapus aksesor; dan ekspresi lambda. Peristiwa seperti bidang (yaitu, satu tanpa aksesor) juga dapat memiliki atribut dengan target ini.
  • param — properti set dan init accessor, indexer set dan init accessors, event add and remove accessor, dan parameter dalam konstruktor, metode, fuksi lokal, dan operator.
  • property — properti dan pengindeks.
  • return — delegasi, metode, fungsi lokal, operator, properti dapatkan aksesor, pengindeks mendapatkan aksesor, dan ekspresi lambda.
  • type — delegasi, kelas, struktur, enum, dan antarmuka.
  • typevar — parameter jenis.

Konteks tertentu mengizinkan spesifikasi atribut pada lebih dari satu target. Program dapat secara eksplisit menentukan target dengan menyertakan attribute_target_specifier. Tanpa attribute_target_specifier default diterapkan, tetapi attribute_target_specifier dapat digunakan untuk mengafirmasi atau mengambil alih default. Konteks diselesaikan sebagai berikut:

  • Untuk atribut pada deklarasi delegasi, target default adalah delegasi. Jika tidak, jika attribute_target sama dengan:
    • type — target adalah delegasi
    • return — target adalah nilai pengembalian
  • Untuk atribut pada deklarasi metode, target default adalah metode . Jika tidak, jika attribute_target sama dengan:
    • method — targetnya adalah metode
    • return — target adalah nilai pengembalian
  • Untuk atribut pada deklarasi fungsi lokal, target default adalah fungsi lokal. Jika tidak, jika attribute_target sama dengan:
    • method — targetnya adalah fungsi lokal
    • return — target adalah nilai pengembalian
  • Untuk atribut pada deklarasi operator, target default adalah operator. Jika tidak, jika attribute_target sama dengan:
    • method — targetnya adalah operator
    • return — target adalah nilai pengembalian
  • Untuk atribut pada deklarasi dapatkan aksesor untuk deklarasi properti atau pengindeks, target default adalah metode terkait. Jika tidak, jika attribute_target sama dengan:
    • method — target adalah metode terkait
    • return — target adalah nilai pengembalian
  • Untuk atribut yang ditentukan pada set atau init accessor untuk properti atau deklarasi pengindeks, target default adalah metode terkait. Jika tidak, jika attribute_target sama dengan:
    • method — target adalah metode terkait
    • param — target adalah parameter implisit kesepian
  • Untuk atribut pada deklarasi properti yang diimplementasikan secara otomatis, target default adalah properti . Jika tidak, jika attribute_target sama dengan:
    • field — target adalah bidang backing yang dihasilkan kompilator untuk properti
  • Untuk atribut yang ditentukan pada deklarasi peristiwa yang menghilangkan event_accessor_declarations target default adalah deklarasi peristiwa. Jika tidak, jika attribute_target sama dengan:
    • event — targetnya adalah deklarasi peristiwa
    • field — target adalah bidang
    • method — targetnya adalah metode
  • Dalam kasus deklarasi peristiwa yang tidak menghilangkan event_accessor_declarations target default adalah metode .
    • method — target adalah metode terkait
    • param — targetnya adalah parameter lone
  • Untuk atribut pada lambda_expression target default adalah metode . Jika tidak, jika attribute_target sama dengan:
    • method — targetnya adalah metode
    • return — target adalah nilai pengembalian

Dalam semua konteks lainnya, penyertaan attribute_target_specifier diizinkan tetapi tidak perlu.

Contoh: deklarasi kelas dapat mencakup atau menghilangkan penentu type:

[type: Author("Brian Kernighan")]
class Class1 {}

[Author("Dennis Ritchie")]
class Class2 {}

contoh akhir.

Implementasi dapat menerima attribute_targetlain, tujuan implementasi yang ditentukan. Implementasi yang tidak mengenali attribute_target tersebut akan mengeluarkan peringatan dan mengabaikan attribute_section yang berisi.

Menurut konvensi, kelas atribut dinamai dengan akhiran Attribute. Attribute_name dapat menyertakan atau menghilangkan akhiran ini. Secara khusus, attribute_name diselesaikan sebagai berikut:

  • Jika pengidentifikasi paling kanan dari attribute_name adalah pengidentifikasi verbatim (§6.4.3), maka attribute_name diselesaikan sebagai type_name (§7,8). Jika hasilnya bukan jenis yang berasal dari System.Attribute, kesalahan waktu kompilasi terjadi.
  • Sebaliknya
    • attribute_name diselesaikan sebagai type_name (§7,8) kecuali kesalahan apa pun ditekan. Jika resolusi ini berhasil dan menghasilkan jenis yang berasal dari System.Attribute maka jenisnya adalah hasil dari langkah ini.
    • Karakter Attribute ditambahkan ke pengidentifikasi paling kanan dalam attribute_name dan string token yang dihasilkan diselesaikan sebagai type_name (§7,8) kecuali kesalahan apa pun ditekan. Jika resolusi ini berhasil dan menghasilkan jenis yang berasal dari System.Attribute maka jenisnya adalah hasil dari langkah ini.

Jika tepat salah satu dari dua langkah di atas menghasilkan jenis yang berasal dari System.Attribute, maka jenis tersebut adalah hasil dari attribute_name. Jika tidak, kesalahan waktu kompilasi terjadi.

Contoh: Jika kelas atribut ditemukan dengan dan tanpa akhiran ini, ambiguitas ada, dan hasil kesalahan waktu kompilasi. Jika attribute_name dieja sedih sehingga pengidentifikasi paling kanan adalah pengidentifikasi verbatim (§6.4.3), maka hanya atribut tanpa akhiran yang cocok, sehingga memungkinkan ambiguitas seperti itu diselesaikan. Contoh

[AttributeUsage(AttributeTargets.All)]
public class Example : Attribute
{}

[AttributeUsage(AttributeTargets.All)]
public class ExampleAttribute : Attribute
{}

[Example]               // Error: ambiguity
class Class1 {}

[ExampleAttribute]      // Refers to ExampleAttribute
class Class2 {}

[@Example]              // Refers to Example
class Class3 {}

[@ExampleAttribute]     // Refers to ExampleAttribute
class Class4 {}

menunjukkan dua kelas atribut bernama Example dan ExampleAttribute. Atributnya [Example] ambigu, karena dapat merujuk ke Example atau ExampleAttribute. Menggunakan pengidentifikasi verbatim memungkinkan niat yang tepat untuk ditentukan dalam kasus langka seperti itu. Atribut [ExampleAttribute] tidak ambigu (meskipun akan jika ada kelas atribut bernama ExampleAttributeAttribute!). Jika deklarasi untuk kelas Example dihapus, kedua atribut merujuk ke kelas atribut bernama ExampleAttribute, sebagai berikut:

[AttributeUsage(AttributeTargets.All)]
public class ExampleAttribute : Attribute
{}

[Example]            // Refers to ExampleAttribute
class Class1 {}

[ExampleAttribute]   // Refers to ExampleAttribute
class Class2 {}

[@Example]           // Error: no attribute named “Example”
class Class3 {}

contoh akhir

Ini adalah kesalahan waktu kompilasi untuk menggunakan kelas atribut sekali pakai lebih dari sekali pada entitas yang sama.

Contoh: Contoh

[AttributeUsage(AttributeTargets.Class)]
public class HelpStringAttribute : Attribute
{
    public HelpStringAttribute(string value)
    {
        Value = value;
    }

    public string Value { get; }
}
[HelpString("Description of Class1")]
[HelpString("Another description of Class1")]   // multiple uses not allowed
public class Class1 {}

menghasilkan kesalahan waktu kompilasi HelpStringkarena mencoba menggunakan Class1, yang merupakan kelas atribut sekali pakai, lebih dari sekali pada deklarasi .

contoh akhir

Ekspresi E adalah attribute_argument_expression jika semua pernyataan berikut ini benar:

  • Jenisnya E adalah jenis parameter atribut (§23.2.4).
  • Pada waktu kompilasi E , nilai dapat diatasi ke salah satu hal berikut:
    • Nilai konstanta.
    • Objek System.Type yang diperoleh menggunakan typeof_expression (§12.8.18) yang menentukan jenis non-generik, jenis konstruksi tertutup (§8.4.3), atau jenis generik yang tidak terikat (§8.4.4), tetapi bukan jenis terbuka (§8.4.3).
    • Array dimensi tunggal attribute_argument_expression.

Contoh:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Field)]
public class TestAttribute : Attribute
{
    public int P1 { get; set; }

    public Type P2 { get; set; }

    public object P3 { get; set; }
}

[Test(P1 = 1234, P3 = new int[]{1, 3, 5}, P2 = typeof(float))]
class MyClass {}

class C<T> {
    [Test(P2 = typeof(T))] // Error – T not a closed type.
    int x1;

    [Test(P2 = typeof(C<T>))] // Error – C<;T>; not a closed type.
    int x2;

    [Test(P2 = typeof(C<int>))] // Ok
    int x3;

    [Test(P2 = typeof(C<>))] // Ok
    int x4;
}

contoh akhir

Atribut jenis yang dideklarasikan dalam beberapa bagian ditentukan dengan menggabungkan, dalam urutan yang tidak ditentukan, atribut masing-masing bagiannya. Jika atribut yang sama ditempatkan pada beberapa bagian, atribut tersebut setara dengan menentukan atribut tersebut beberapa kali pada jenisnya.

Contoh: Dua bagian:

[Attr1, Attr2("hello")]
partial class A {}

[Attr3, Attr2("goodbye")]
partial class A {}

setara dengan deklarasi tunggal berikut:

[Attr1, Attr2("hello"), Attr3, Attr2("goodbye")]
class A {}

contoh akhir

Atribut pada parameter jenis digabungkan dengan cara yang sama.

23.4 Instans atribut

23.4.1 Umum

Instans atribut adalah instans yang mewakili atribut pada run-time. Atribut didefinisikan dengan kelas atribut, argumen posisi, dan argumen bernama. Instans atribut adalah instans kelas atribut yang diinisialisasi dengan argumen posisi dan bernama.

Pengambilan instans atribut melibatkan pemrosesan waktu kompilasi dan run-time, seperti yang dijelaskan dalam subklasul berikut.

23.4.2 Kompilasi atribut

Kompilasi atribut dengan kelas Tatribut , positional_argument_listP, , dan ditentukan pada entitas N program dikompilasi ke dalam rakitan E melalui langkah-langkah berikut:

  • Ikuti langkah-langkah pemrosesan waktu kompilasi untuk mengkompilasi object_creation_expression formulir baru T(P). Langkah-langkah ini mengakibatkan kesalahan waktu kompilasi, atau menentukan konstruktor C instans pada T yang dapat dipanggil pada run-time.
  • Jika C tidak memiliki aksesibilitas publik, maka terjadi kesalahan waktu kompilasi.
  • Untuk setiap named_argumentArg di N:
    • Biarkan Name menjadi pengidentifikasi
    • Name harus mengidentifikasi bidang publik baca-tulis non-statis atau properti baca-tulis atau baca-init non-statis publik pada T. Jika T tidak memiliki bidang atau properti seperti itu, maka terjadi kesalahan waktu kompilasi.
  • Jika salah satu nilai dalam positional_argument_listP atau salah satu nilai dalam named_argument_listN berjenis System.String dan nilainya tidak terbentuk dengan baik seperti yang didefinisikan oleh Standar Unicode, nilai yang didefinisikan implementasinya sama dengan nilai run-time yang diambil (§23.4.3).

    Catatan: Sebagai contoh, string yang berisi unit kode UTF-16 pengganti tinggi yang tidak segera diikuti oleh unit kode pengganti rendah tidak terbentuk dengan baik. catatan akhir

  • Simpan informasi berikut (untuk instansiasi run-time atribut) dalam output perakitan oleh pengkompilasi sebagai hasil dari mengkompilasi program yang berisi atribut: kelas Tatribut , konstruktor C instans pada T, , P, dan entitas Nprogram terkait , dengan nilai diselesaikan sepenuhnya pada waktu kompilasi.

23.4.3 Pengambilan run-time instans atribut

Menggunakan istilah yang ditentukan dalam §23.4.2, instans atribut yang diwakili oleh T, , CP, dan N, dan yang terkait dengan E dapat diambil pada run-time dari perakitan A menggunakan langkah-langkah berikut:

  • Ikuti langkah-langkah pemrosesan run-time untuk menjalankan object_creation_expression formulir new T(P), menggunakan konstruktor C instans dan nilai seperti yang ditentukan pada waktu kompilasi. Langkah-langkah ini menghasilkan pengecualian, atau menghasilkan instans OT.
  • Untuk setiap named_argumentArg dalam N, secara berurutan:
    • Biarkan Name menjadi pengidentifikasi Jika Name tidak mengidentifikasi bidang baca-tulis publik non-statis atau properti baca-tulis atau baca-init publik non-statis pada O, maka pengecualian akan dilemparkan.
    • Mari kita Value jadikan hasil mengevaluasi attribute_argument_expression .Arg
    • Jika Name mengidentifikasi bidang pada O, maka atur bidang ini ke Value.
    • Jika tidak, Nama mengidentifikasi properti pada O. Atur properti ini ke Nilai.
    • Hasilnya adalah O, instans kelas T atribut yang telah diinisialisasi dengan positional_argument_listP dan named_argument_listN.

Catatan: Format untuk menyimpan T, , C, PN(dan mengaitkannya dengan E) di A dan mekanisme untuk menentukan E dan mengambil T, , CP, N dari A (dan karenanya bagaimana instans atribut diperoleh pada runtime) berada di luar cakupan spesifikasi ini. catatan akhir

23.5 Atribut cadangan

23.5.1 Umum

Sejumlah atribut memengaruhi bahasa dalam beberapa cara. Atribut ini meliputi:

  • System.AttributeUsageAttribute (§23.5.2), yang digunakan untuk menggambarkan cara kelas atribut dapat digunakan.
  • System.Diagnostics.ConditionalAttribute (§23.5.3), adalah kelas atribut multi-penggunaan yang digunakan untuk menentukan metode kondisional dan kelas atribut kondisional. Atribut ini menunjukkan kondisi dengan menguji simbol kompilasi bersyarah.
  • System.ObsoleteAttribute (§23.5.4), yang digunakan untuk menandai anggota sebagai usang.
  • System.Runtime.CompilerServices.AsyncMethodBuilderAttribute (§23.5.5), yang digunakan untuk membuat pembangun tugas untuk metode asinkron.
  • System.Runtime.CompilerServices.CallerLineNumberAttribute (§23.5.6.2), System.Runtime.CompilerServices.CallerFilePathAttribute (§23.5.6.3), System.Runtime.CompilerServices.CallerMemberNameAttribute (§23.5.6.4), dan System.Runtime.CompilerServices.CallerArgumentExpressionAttribute (§23.5.6.5), yang digunakan untuk memberikan informasi tentang konteks panggilan ke parameter opsional.
  • System.Runtime.CompilerServices.EnumeratorCancellationAttribute (§23.5.8), yang digunakan untuk menentukan parameter untuk token pembatalan dalam iterator asinkron.
  • System.Runtime.CompilerServices.ModuleInitializer (§23.5.9), yang digunakan untuk menandai metode sebagai penginisialisasi modul.
  • System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute dan System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute, yang digunakan untuk mendeklarasikan handler ekspresi string terinterpolasi kustom (§23.5.9.1) dan untuk memanggil salah satu konstruktornya, masing-masing.

Atribut analisis statis nullable (§23.5.7) dapat meningkatkan kebenaran peringatan yang dihasilkan untuk nullabilities dan status null (§8.9.5).

Lingkungan eksekusi dapat menyediakan atribut tambahan yang ditentukan implementasi yang memengaruhi eksekusi program C#.

23.5.2 AtributUsage atribut

Atribut AttributeUsage digunakan untuk menjelaskan cara kelas atribut dapat digunakan.

Kelas yang didekorasi dengan AttributeUsage atribut harus berasal dari System.Attribute, baik secara langsung maupun tidak langsung. Jika tidak, terjadi kesalahan waktu kompilasi.

Catatan: Untuk contoh penggunaan atribut ini, lihat §23.2.2. catatan akhir

23.5.3 Atribut Bersyarah

23.5.3.1 Umum

Atribut Conditional memungkinkan definisi metode kondisional, fungsi lokal kondisional, dan kelas atribut kondisionales.

23.5.3.2 Metode bersyarah

Metode yang didekorasi dengan Conditional atribut adalah metode bersyarkat. Setiap metode bersyarat dengan demikian dikaitkan dengan simbol kompilasi bersyarat yang dideklarasikan dalam atributnya Conditional .

Contoh:

class Eg
{
    [Conditional("ALPHA")]
    [Conditional("BETA")]
    public static void M()
    {
        // ...
    }
}

menyatakan Eg.M sebagai metode bersyarat yang terkait dengan dua simbol kompilasi bersyarat ALPHA dan BETA.

contoh akhir

Panggilan ke metode bersyarkat disertakan jika satu atau beberapa simbol kompilasi bersyar terkait didefinisikan pada titik panggilan, jika tidak, panggilan dihilangkan.

Metode bersyukur tunduk pada pembatasan berikut:

  • Metode bersyarah harus menjadi metode dalam class_declaration atau struct_declaration. Kesalahan waktu kompilasi terjadi jika Conditional atribut ditentukan pada metode dalam deklarasi antarmuka.
  • Metode bersyarah tidak boleh menjadi pengakses properti, pengindeks, atau peristiwa.
  • Metode bersyarah harus memiliki jenis pengembalian .void
  • Metode kondisional tidak boleh ditandai dengan pengubah override . Namun, metode kondisional dapat ditandai dengan pengubah virtual . Penimpaan metode tersebut secara implisit kondisional, dan tidak boleh ditandai secara eksplisit dengan Conditional atribut .
  • Metode kondisional tidak boleh menjadi implementasi metode antarmuka. Jika tidak, terjadi kesalahan waktu kompilasi.
  • Parameter metode bersyarah tidak boleh berupa parameter output.

Catatan: Atribut dengan AttributeUsage (§23.2.2) termasuk AttributeTargets.Method biasanya dapat diterapkan ke pengakses properti, pengindeks, dan peristiwa. Pembatasan di atas melarang penggunaan Conditional atribut ini. catatan akhir

Selain itu, kesalahan waktu kompilasi terjadi jika delegasi dibuat dari metode bersyarah.

Contoh: Contoh

#define DEBUG
using System;
using System.Diagnostics;

class Class1
{
    [Conditional("DEBUG")]
    public static void M()
    {
        Console.WriteLine("Executed Class1.M");
    }
}

class Class2
{
    public static void Test()
    {
        Class1.M();
    }
}

menyatakan sebagai metode bersyarat Class1.M . Class2Metode memanggil Test metode ini. Karena simbol DEBUG kompilasi bersyarat didefinisikan, jika Class2.Test dipanggil, simbol tersebut akan memanggil M. Jika simbol DEBUG belum ditentukan, maka Class2.Test tidak akan memanggil Class1.M.

contoh akhir

Penting untuk dipahami bahwa penyertaan atau pengecualian panggilan ke metode kondisional dikendalikan oleh simbol kompilasi kondisional pada saat panggilan.

Contoh: Dalam kode berikut

// File Class1.cs:
using System;
using System.Diagnostics;
class Class1
{
    [Conditional("DEBUG")]
    public static void F()
    {
        Console.WriteLine("Executed Class1.F");
    }
}

// File Class2.cs:
#define DEBUG
class Class2
{
    public static void G()
    {
        Class1.F(); // F is called
    }
}

// File Class3.cs:
#undef DEBUG
class Class3
{
    public static void H()
    {
        Class1.F(); // F is not called
    }
}

kelas Class2 dan Class3 masing-masing berisi panggilan ke metode Class1.Fkondisional , yang bersyukur berdasarkan apakah didefinisikan atau tidak DEBUG . Karena simbol ini didefinisikan dalam konteks Class2 tetapi tidak Class3, panggilan ke F dalam Class2 disertakan, sementara panggilan ke F dihilangkan Class3 .

contoh akhir

Penggunaan metode kondisional dalam rantai warisan dapat membingungkan. Panggilan yang dilakukan ke metode bersyarah melalui base, dari formulir base.M, tunduk pada aturan panggilan metode kondisi normal.

Contoh: Dalam kode berikut

// File Class1.cs
using System;
using System.Diagnostics;
class Class1
{
    [Conditional("DEBUG")]
    public virtual void M() => Console.WriteLine("Class1.M executed");
}

// File Class2.cs
class Class2 : Class1
{
    public override void M()
    {
        Console.WriteLine("Class2.M executed");
        base.M(); // base.M is not called!
    }
}

// File Class3.cs
#define DEBUG
class Class3
{
    public static void Main()
    {
        Class2 c = new Class2();
        c.M(); // M is called
    }
}

Class2 termasuk panggilan ke yang M ditentukan di kelas dasarnya. Panggilan ini dihilangkan karena metode dasar bersyukur berdasarkan keberadaan simbol DEBUG, yang tidak terdefinisi. Dengan demikian, metode menulis ke konsol "Class2.M executed" saja. Penggunaan pp_declarationyang berdasar dapat menghilangkan masalah tersebut.

contoh akhir

23.5.3.3 Fungsi lokal bersyar

Fungsi lokal statis dapat dibuat bersyarah dalam arti yang sama dengan metode bersyarah (§23.5.3.2).

Kesalahan waktu kompilasi terjadi jika fungsi lokal non-statis dibuat bersyarah.

23.5.3.4 Kelas atribut kondisional

Kelas atribut (§23.2) yang didekorasi dengan satu atau beberapa Conditional atribut adalah kelas atribut kondisional. Kelas atribut bersyarat dengan demikian dikaitkan dengan simbol kompilasi bersyarat yang dideklarasikan dalam atributnya Conditional .

Contoh:

[Conditional("ALPHA")]
[Conditional("BETA")]
public class TestAttribute : Attribute {}

menyatakan TestAttribute sebagai kelas atribut bersyarat yang terkait dengan simbol kompilasi bersyarat ALPHA dan BETA.

contoh akhir

Spesifikasi atribut (§23,3) dari atribut bersyarkat disertakan jika satu atau beberapa simbol kompilasi bersyarkat terkait didefinisikan pada titik spesifikasi, jika tidak, spesifikasi atribut dihilangkan.

Penting untuk dicatat bahwa penyertaan atau pengecualian spesifikasi atribut dari kelas atribut bersyarkat dikontrol oleh simbol kompilasi bersyarkat pada titik spesifikasi.

Contoh: Dalam contoh

// File Test.cs:
using System;
using System.Diagnostics;
[Conditional("DEBUG")]
public class TestAttribute : Attribute {}

// File Class1.cs:
#define DEBUG
[Test] // TestAttribute is specified
class Class1 {}

// File Class2.cs:
#undef DEBUG
[Test] // TestAttribute is not specified
class Class2 {}

kelas Class1 dan Class2 masing-masing didekorasi dengan atribut Test, yang bersyukur berdasarkan apakah ditentukan atau tidak DEBUG . Karena simbol ini didefinisikan dalam konteks Class1 tetapi tidak Class2, spesifikasi atribut Uji pada Class1 disertakan, sementara spesifikasi Test atribut pada dihilangkan Class2 .

contoh akhir

23.5.4 Atribut Usang

Atribut Obsolete digunakan untuk menandai jenis dan anggota jenis yang seharusnya tidak lagi digunakan.

Jika program menggunakan jenis atau anggota yang dihiasi dengan atribut Obsolete, pengkompilasi akan mengeluarkan peringatan atau kesalahan. Secara khusus, kompilator harus mengeluarkan peringatan jika tidak ada parameter kesalahan yang disediakan, atau jika parameter kesalahan disediakan dan memiliki nilai false. Kompilator akan mengeluarkan kesalahan jika parameter kesalahan ditentukan dan memiliki nilai true.

Contoh: Dalam kode berikut

[Obsolete("This class is obsolete; use class B instead")]
class A
{
    public void F() {}
}

class B
{
    public void F() {}
}

class Test
{
    static void Main()
    {
        A a = new A(); // Warning
        a.F();
    }
}

kelas A dihiasi dengan Obsolete atribut . Setiap penggunaan A dalam Main menghasilkan peringatan yang mencakup pesan yang ditentukan, "Kelas ini usang; gunakan kelas B sebagai gantinya".

contoh akhir

23.5.5 Atribut AsyncMethodBuilder

Atribut ini dijelaskan dalam §15.14.1.

23.5.6 Atribut caller-info

23.5.6.1 Umum

Untuk tujuan seperti pengelogan dan pelaporan, terkadang berguna bagi anggota fungsi untuk mendapatkan informasi waktu kompilasi tertentu tentang kode panggilan. Atribut caller-info menyediakan cara untuk meneruskan informasi tersebut secara transparan.

Ketika parameter opsional diannotasi dengan salah satu atribut caller-info, menghilangkan argumen yang sesuai dalam panggilan tidak selalu menyebabkan nilai parameter default diganti. Sebaliknya, jika informasi yang ditentukan tentang konteks panggilan tersedia, informasi tersebut akan diteruskan sebagai nilai argumen.

Contoh:

public void Log(
    [CallerLineNumber] int line = -1,
    [CallerFilePath] string path = null,
    [CallerMemberName] string name = null
)
{
    Console.WriteLine((line < 0) ? "No line" : "Line "+ line);
    Console.WriteLine((path == null) ? "No file path" : path);
    Console.WriteLine((name == null) ? "No member name" : name);
}

Panggilan ke Log() tanpa argumen akan mencetak nomor baris dan jalur file panggilan, serta nama anggota tempat panggilan terjadi.

contoh akhir

Atribut caller-info dapat terjadi pada parameter opsional di mana saja, termasuk dalam deklarasi delegasi. Namun, atribut caller-info tertentu memiliki batasan pada jenis parameter yang dapat mereka atribut, sehingga akan selalu ada konversi implisit dari nilai yang diganti ke jenis parameter.

Ini adalah kesalahan untuk memiliki atribut caller-info yang sama pada parameter dari deklarasi mendefinisikan dan menerapkan bagian dari deklarasi metode parsial. Hanya atribut caller-info di bagian yang menentukan yang diterapkan, sedangkan atribut caller-info yang hanya terjadi di bagian penerapan diabaikan.

Informasi penelepon tidak memengaruhi resolusi kelebihan beban. Karena parameter opsional yang dikaitkan masih dihilangkan dari kode sumber pemanggil, resolusi kelebihan beban mengabaikan parameter tersebut dengan cara yang sama mengabaikan parameter opsional lain yang dihilangkan (§12.6.4).

Informasi penelepon hanya diganti ketika fungsi secara eksplisit dipanggil dalam kode sumber. Pemanggilan implisit seperti panggilan konstruktor induk implisit tidak memiliki lokasi sumber dan tidak akan menggantikan informasi pemanggil. Selain itu, panggilan yang terikat secara dinamis tidak akan menggantikan informasi pemanggil. Ketika parameter atribut caller-info dihilangkan dalam kasus seperti itu, nilai default parameter yang ditentukan digunakan sebagai gantinya.

Satu pengecualian adalah ekspresi kueri. Ini dianggap sebagai ekspansi syntactic, dan jika panggilan yang mereka perluas untuk menghilangkan parameter opsional dengan atribut caller-info, informasi penelepon akan diganti. Lokasi yang digunakan adalah lokasi klausa kueri tempat panggilan dihasilkan.

Jika lebih dari satu atribut caller-info ditentukan pada parameter tertentu, atribut tersebut dikenali dalam urutan berikut: CallerLineNumber, , CallerFilePathCallerMemberName, CallerArgumentExpression. Pertimbangkan deklarasi parameter berikut:

[CallerMemberName, CallerFilePath, CallerLineNumber] object p = ...

CallerLineNumber diutamakan, dan tiga atribut lainnya diabaikan. Jika CallerLineNumber dihilangkan, CallerFilePath akan diutamakan, dan CallerMemberName dan CallerArgumentExpression akan diabaikan. Urutan leksikal atribut ini tidak relevan.

23.5.6.2 Atribut CallerLineNumber

Atribut System.Runtime.CompilerServices.CallerLineNumberAttribute diizinkan pada parameter opsional ketika ada konversi implisit standar (§10.4.2) dari nilai int.MaxValue konstanta ke jenis parameter. Ini memastikan bahwa nomor baris non-negatif hingga nilai tersebut dapat diteruskan tanpa kesalahan.

Jika pemanggilan fungsi dari lokasi dalam kode sumber menghilangkan parameter opsional dengan CallerLineNumberAttribute, maka literal numerik yang mewakili nomor baris lokasi tersebut digunakan sebagai argumen untuk pemanggilan alih-alih nilai parameter default.

Jika pemanggilan mencakup beberapa baris, baris yang dipilih bergantung pada implementasi.

Nomor baris dapat dipengaruhi oleh #line arahan (§6.5.8).

23.5.6.3 Atribut CallerFilePath

Atribut System.Runtime.CompilerServices.CallerFilePathAttribute diizinkan pada parameter opsional ketika ada konversi implisit standar (§10.4.2) dari string ke jenis parameter.

Jika pemanggilan fungsi dari lokasi dalam kode sumber menghilangkan parameter opsional dengan CallerFilePathAttribute, maka string literal yang mewakili jalur file lokasi tersebut digunakan sebagai argumen untuk pemanggilan alih-alih nilai parameter default.

Format jalur file bergantung pada implementasi.

Jalur file dapat dipengaruhi oleh #line arahan (§6.5.8).

23.5.6.4 Atribut CallerMemberName

Atribut System.Runtime.CompilerServices.CallerMemberNameAttribute diizinkan pada parameter opsional ketika ada konversi implisit standar (§10.4.2) dari string ke jenis parameter.

Jika pemanggilan fungsi dari lokasi dalam isi anggota fungsi atau dalam atribut yang diterapkan ke anggota fungsi itu sendiri atau jenis pengembaliannya, parameter atau parameter jenis dalam kode sumber menghilangkan parameter opsional dengan CallerMemberNameAttribute, maka string harfiah yang mewakili nama anggota tersebut digunakan sebagai argumen untuk pemanggilan alih-alih nilai parameter default. (Dalam kasus pemanggilan fungsi dari pernyataan tingkat atas (§7.1.3), nama anggota adalah yang dihasilkan oleh implementasi.)

Untuk pemanggilan yang terjadi dalam metode generik, hanya nama metode itu sendiri yang digunakan, tanpa daftar parameter jenis.

Untuk pemanggilan yang terjadi dalam implementasi anggota antarmuka eksplisit, hanya nama metode itu sendiri yang digunakan, tanpa kualifikasi antarmuka sebelumnya.

Untuk pemanggilan yang terjadi dalam properti atau aksesor peristiwa, nama anggota yang digunakan adalah properti atau peristiwa itu sendiri.

Untuk pemanggilan yang terjadi dalam pengakses pengindeks, nama anggota yang digunakan adalah yang disediakan oleh IndexerNameAttribute (§23,6) pada anggota pengindeks, jika ada, atau nama Item default sebaliknya.

Untuk pemanggilan yang terjadi dalam penginisialisasi bidang atau peristiwa, nama anggota yang digunakan adalah nama bidang atau peristiwa yang sedang diinisialisasi.

Untuk pemanggilan yang terjadi dalam deklarasi konstruktor instans, konstruktor statis, finalizer, dan operator nama anggota yang digunakan bergantung pada implementasi.

Untuk pemanggilan yang terjadi dalam fungsi lokal atau fungsi anonim, nama metode anggota yang memanggil fungsi tersebut digunakan.

Contoh: Pertimbangkan hal berikut:

class Program
{
    static void Main()
    {
        F1();

        void F1([CallerMemberName] string? name = null)
        {
            Console.WriteLine($"F1 MemberName: |{name}|");
            F2();
        }

        static void F2([CallerMemberName] string? name = null)
        {
            Console.WriteLine($"F2 MemberName: |{name}|");
        }
    }
}

yang menghasilkan output

F1 MemberName: |Main|
F2 MemberName: |Main|

Atribut ini menyediakan nama anggota fungsi panggilan, yang untuk fungsi F1 lokal adalah metode Main. Dan meskipun F2 dipanggil oleh F1, fungsi lokal bukan anggota fungsi, sehingga pemanggil F2 yang dilaporkan juga Main. contoh akhir

23.5.6.5 Atribut CallerArgumentExpression

Atribut System.Runtime.CompilerServices.CallerArgumentExpressionAttribute diterapkan ke parameter target, dan dapat mengakibatkan pengambilan teks kode sumber argumen parameter saudara sebagai string, yang disebut di sini sebagai string yang diambil.

Kecuali ketika parameter pertama dalam metode ekstensi, parameter target harus memiliki default_argument.

Pertimbangkan deklarasi metode berikut:

using System;
using System.Runtime.CompilerServices;
#nullable enable
class Test
{
    public static void M(int val = 0, [CallerArgumentExpression("val")] string? text = null)
    {
        Console.WriteLine($"val = {val}, text = <{text}>");
    }
}

di mana parameter target adalah text dan parameter saudara adalah val, yang teks kode sumber argumen yang sesuai dapat diambil ketika M dipanggiltext.

Konstruktor atribut mengambil argumen jenis string. String itu

  • Harus berisi nama parameter saudara; jika tidak, atribut diabaikan.
  • Harus menghilangkan leading @ dari nama parameter yang memiliki awalan tersebut.

Parameter_list mungkin berisi beberapa parameter target.

Jenis parameter target harus memiliki konversi standar dari string.

Catatan: Ini berarti tidak ada konversi yang ditentukan pengguna dari string yang diizinkan, dan dalam praktiknya berarti jenis parameter tersebut harus string, , objectatau antarmuka yang diterapkan oleh string. catatan akhir

Jika argumen eksplisit diteruskan untuk parameter target, tidak ada string yang diambil, dan parameter tersebut mengambil nilai argumen tersebut. Jika tidak, teks untuk argumen yang sesuai dengan parameter saudara dikonversi ke string yang diambil, sesuai dengan aturan berikut:

  • Spasi putih awal dan akhir dihapus baik sebelum dan sesudah tanda kurung pengelompokan terluar dihapus.
  • Semua tanda kurung pengelompokan terluar dihapus baik sebelum dan sesudah spasi kosong di awal dan akhir dihapus.
  • Semua input_elementlainnya dipertahankan verbatim (termasuk spasi kosong, komentar, Unicode_Escape_Sequence, dan @ awalan pada pengidentifikasi).

String yang diambil kemudian diteruskan sebagai argumen yang sesuai dengan parameter target. Namun, jika argumen untuk parameter saudara dihilangkan, parameter target mengambil nilai default_argument .

Contoh: Mengingat deklarasi M di atas, pertimbangkan panggilan berikut ke M:

Test.M();
Test.M(123);
Test.M(123, null);
Test.M(123, "xyz");
Test.M(  1  +      2 );
Test.M(( ( (123) + 0) ) );
int local = 10;
Test.M(l\u006fcal /*...*/ + // xxx
  5);

output yang dihasilkan adalah

val = 0, text = <>
val = 123, text = <123>
val = 123, text = <>
val = 123, text = <xyz>
val = 3, text = <1  +      2>
val = 123, text = <(123) + 0>
val = 15, text = <l\u006fcal /*...*/ + // xxx
  5>

contoh akhir

23.5.7 Atribut analisis kode

23.5.7.1 Umum

Atribut dalam subklaus ini digunakan untuk memberikan informasi tambahan guna mendukung kompilator yang menyediakan diagnostik untuk ketidaknullan dan keadaan null (§8.9.5). Pengkompilasi tidak diperlukan untuk melakukan diagnostik status null. Kehadiran atau tidak adanya atribut ini tidak memengaruhi bahasa atau perilaku program. Pengkompilasi yang tidak menyediakan diagnostik status null harus membaca dan mengabaikan keberadaan atribut ini. Kompilator yang menyediakan diagnosis status null harus menggunakan arti yang ditentukan dalam subklausa ini untuk atribut mana pun yang digunakannya guna menginformasikan diagnosisnya.

Atribut analisis kode dideklarasikan dalam namespace System.Diagnostics.CodeAnalysis.

Atribut Arti
AllowNull (§23.5.7.2) Argumen yang tidak dapat diubah ke null mungkin null.
DisallowNull (§23.5.7.3) Argumen nullable tidak boleh null.
MaybeNull (§23.5.7.6) Nilai pengembalian yang tidak dapat diubah ke null mungkin null.
NotNull (§23.5.7.10) Nilai pengembalian null tidak akan pernah null.
MaybeNullWhen (§23.5.7.7) Argumen yang tidak dapat diubah ke null mungkin null saat metode mengembalikan nilai bool yang ditentukan.
NotNullWhen (§23.5.7.12) Argumen nullable tidak akan null ketika metode mengembalikan nilai yang ditentukan bool .
NotNullIfNotNull (§23.5.7.11) Nilai yang dikembalikan tidak null jika argumen untuk parameter yang ditentukan tidak null.
MemberNotNull (§23.5.7.8) Anggota yang tercantum tidak akan null saat metode kembali.
MemberNotNullWhen (§23.5.7.9) Anggota yang tercantum tidak akan null ketika metode mengembalikan nilai yang ditentukan bool .
DoesNotReturn (§23.5.7.4) Metode ini tidak pernah kembali.
DoesNotReturnIf (§23.5.7.5) Metode ini tidak pernah mengembalikan jika parameter terkait bool memiliki nilai yang ditentukan.

Subklaus berikut dalam §23.5.7 bersifat normatif kondisional.

23.5.7.2 Atribut AllowNull

Menentukan bahwa nilai null diizinkan sebagai input meskipun jenis yang sesuai melarangnya.

Contoh: Pertimbangkan properti baca/tulis berikut yang tidak pernah ditampilkan null karena memiliki nilai default yang wajar. Namun, pengguna dapat memberikan null kepada aksesor yang ditetapkan untuk mengatur properti ke nilai default tersebut.

#nullable enable
public class X
{
    [AllowNull]
    public string ScreenName
    {
        get => _screenName;
        set => _screenName = value ?? GenerateRandomScreenName();
    }
    private string _screenName = GenerateRandomScreenName();
    private static string GenerateRandomScreenName() => ...;
}

Mengingat penggunaan aksesor set properti tersebut berikut

var v = new X();
v.ScreenName = null;   // may warn without attribute AllowNull

tanpa atribut, pengompilasi dapat menghasilkan peringatan karena properti bertipe non-nullable tampaknya diatur ke nilai null. Kehadiran atribut menekan peringatan tersebut. contoh akhir

23.5.7.3 Atribut DisallowNull

Menentukan bahwa nilai null tidak diizinkan sebagai input meskipun jenis yang sesuai mengizinkannya.

Contoh: Pertimbangkan properti berikut di mana null adalah nilai default, tetapi klien hanya dapat mengaturnya ke nilai non-null.

#nullable enable
public class X
{
    [DisallowNull]
    public string? ReviewComment
    {
        get => _comment;
        set => _comment = value ?? throw new ArgumentNullException(nameof(value),
           "Cannot set to null");
    }
    private string? _comment = default;
}

Get accessor dapat mengembalikan nilai default null, sehingga pengkompilasi dapat memperingatkan bahwa itu harus diperiksa sebelum akses. Selain itu, ini memperingatkan penelepon bahwa, meskipun bisa null, penelepon tidak boleh secara eksplisit mengaturnya ke null. contoh akhir

23.5.7.4 Atribut DoesNotReturn

Menentukan bahwa metode tertentu tidak pernah kembali.

Contoh: Pertimbangkan hal berikut:

public class X
{
    [DoesNotReturn]
    private void FailFast() =>
        throw new InvalidOperationException();

    public void SetState(object? containedField)
    {
        if ((!isInitialized) || (containedField == null))
        {
            FailFast();
        }
        // null check not needed.
        _field = containedField;
    }

    private bool isInitialized = false;
    private object _field;
}

Kehadiran atribut membantu pengkompilasi dengan sejumlah cara. Pertama, kompilator dapat mengeluarkan peringatan jika ada jalur di mana metode dapat keluar tanpa melemparkan pengecualian. Kedua, kompilator dapat menekan peringatan nullable dalam kode apa pun setelah panggilan ke metode tersebut, sampai klausa tangkapan yang sesuai ditemukan. Ketiga, kode yang tidak dapat dijangkau tidak akan memengaruhi status null apa pun.

Atribut tidak mengubah keterjangkauan (§13.2) atau analisis penetapan pasti (§9,4) berdasarkan keberadaan atribut ini. Ini hanya digunakan untuk memengaruhi peringatan nullability. contoh akhir

23.5.7.5 Atribut DoesNotReturnIf

Menentukan bahwa metode tertentu tidak pernah mengembalikan jika parameter terkait bool memiliki nilai yang ditentukan.

Contoh: Pertimbangkan hal berikut:

#nullable enable
public class X
{
    private void ThrowIfNull([DoesNotReturnIf(true)] bool isNull, string argumentName)
    {
        if (isNull)
        {
            throw new ArgumentException(argumentName,
              $"argument {argumentName} cannot be null");
        }
    }

    public void SetFieldState(object containedField)
    {
        ThrowIfNull(containedField == null, nameof(containedField));
        // unreachable code when "isInitialized" is false:
        _field = containedField;
    }

    private bool isInitialized = false;
    private object _field = default!;
}

contoh akhir

23.5.7.6 Atribut MaybeNull

Menentukan bahwa nilai pengembalian yang tidak dapat diubah ke null mungkin null.

Contoh: Pertimbangkan metode generik berikut:

#nullable enable
[return: MaybeNull]
public T Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate) { ... }

Tanpa atribut pengkompilasi mungkin menghasilkan peringatan jika metode dapat mengembalikan null. Kehadiran atribut menekan peringatan tersebut. contoh akhir

23.5.7.7 Atribut MaybeNullWhen

Menentukan bahwa argumen yang tidak dapat diubah ke null mungkin null ketika metode mengembalikan nilai yang ditentukan bool . Ini mirip MaybeNull dengan atribut (§23.5.7.6), tetapi menyertakan parameter untuk nilai pengembalian yang ditentukan.

23.5.7.8 Atribut MemberNotNull

Menentukan bahwa anggota yang diberikan tidak akan ketika null metode kembali.

Contoh: Metode pembantu dapat menyertakan MemberNotNull atribut untuk mencantumkan bidang apa pun yang ditetapkan ke nilai non-null dalam metode tersebut. Kompilator yang menganalisis konstruktor untuk menentukan apakah semua bidang referensi yang tidak dapat diubah ke null telah diinisialisasi kemudian dapat menggunakan atribut ini untuk menemukan bidang mana yang telah ditetapkan oleh metode pembantu tersebut. Pertimbangkan contoh berikut:

#nullable enable
public class Container
{
    private string _uniqueIdentifier; // must be initialized.
    private string? _optionalMessage;

    public Container()
    {
        Helper();
    }

    public Container(string message)
    {
        Helper();
        _optionalMessage = message;
    }

    [MemberNotNull(nameof(_uniqueIdentifier))]
    private void Helper()
    {
        _uniqueIdentifier = DateTime.Now.Ticks.ToString();
    }
}

Beberapa nama bidang dapat diberikan sebagai argumen ke konstruktor atribut. contoh akhir

23.5.7.9 Atribut MemberNotNullWhen

Menentukan bahwa anggota yang tercantum tidak akan menjadi null ketika metode mengembalikan nilai yang ditentukan bool .

Contoh: Atribut ini seperti MemberNotNull (§23.5.7.8) kecuali yang MemberNotNullWhen mengambil bool argumen. MemberNotNullWhen ditujukan untuk digunakan dalam situasi di mana metode pembantu mengembalikan bool yang menunjukkan apakah itu menginisialisasi bidang. contoh akhir

23.5.7.10 Atribut NotNull

Menentukan bahwa nilai nullable tidak akan null pernah jika metode mengembalikan (bukan melemparkan).

Contoh: Pertimbangkan hal berikut:

#nullable enable
public static void ThrowWhenNull([NotNull] object? value,
  string valueExpression = "") =>
    _ = value ?? throw new ArgumentNullException(valueExpression);

public static void LogMessage(string? message)
{
    ThrowWhenNull(message, nameof(message));
    Console.WriteLine(message.Length);
}

Ketika jenis referensi nullable diaktifkan, metode ThrowWhenNull dikompilasi tanpa peringatan. Ketika metode itu kembali, argumen dijamin value bukan null. Namun, dapat diterima untuk memanggil ThrowWhenNull dengan referensi null. contoh akhir

23.5.7.11 Atribut NotNullIfNotNull

Menentukan bahwa nilai pengembalian bukan null jika argumen untuk parameter yang ditentukan bukan null.

Contoh: Status null dari nilai pengembalian dapat bergantung pada status null dari satu atau beberapa argumen. Untuk membantu analisis pengkompilasi ketika metode selalu mengembalikan nilai non-null ketika argumen tertentu tidak null atribut NotNullIfNotNull dapat digunakan. Pertimbangkan kode berikut:

#nullable enable
string GetTopLevelDomainFromFullUrl(string url) { ... }

url Jika argumen bukan null, null tidak dikembalikan. Ketika referensi nullable diaktifkan, tanda tangan tersebut berfungsi dengan benar, asalkan API tidak pernah menerima argumen null. Namun, jika argumen bisa null, maka nilai yang dikembalikan juga bisa null. Untuk mengekspresikan kontrak tersebut dengan benar, anotasi metode ini sebagai berikut:

#nullable enable
[return: NotNullIfNotNull("url")]
string? GetTopLevelDomainFromFullUrl(string? url) { ... }

contoh akhir

23.5.7.12 Atribut NotNullWhen

Menentukan bahwa argumen nullable tidak akan ketika null metode mengembalikan nilai yang ditentukan bool .

Contoh: Metode String.IsNullOrEmpty(String) pustaka mengembalikan true saat argumen adalah null atau string kosong. Ini adalah bentuk null-check: Penelepon tidak perlu memeriksa argumen null jika metode mengembalikan false. Untuk membuat metode seperti ini sadar nullable, buat jenis parameter sebagai jenis referensi nullable, dan tambahkan atribut NotNullWhen:

#nullable enable
bool IsNullOrEmpty([NotNullWhen(false)] string? value) { ... }

contoh akhir

23.5.8 Atribut EnumeratorCancellation

Menentukan parameter yang mewakili CancellationToken untuk iterator asinkron (§15.15). Argumen untuk parameter ini harus dikombinasikan dengan argumen yang diteruskan ke IAsyncEnumerable<T>.GetAsyncEnumerator(CancellationToken). Token gabungan ini harus dijajaki oleh IAsyncEnumerator<T>.MoveNextAsync() (§15.15.5.2). Token harus digabungkan ke dalam satu token seolah-olah oleh dan CancellationToken.CreateLinkedTokenSource propertinya Token . Token gabungan akan dibatalkan jika salah satu dari dua token sumber dibatalkan. Token gabungan dipandang sebagai argumen untuk metode iterator asinkron (§15,15) dalam isi metode tersebut.

Ini adalah kesalahan jika System.Runtime.CompilerServices.EnumeratorCancellation atribut diterapkan ke lebih dari satu parameter. Pengkompilasi dapat menghasilkan peringatan jika:

  • Atribut EnumeratorCancellation diterapkan ke parameter jenis selain CancellationToken,
  • atau jika EnumeratorCancellation atribut diterapkan ke parameter pada metode yang bukan iterator asinkron (§15.15),
  • atau jika EnumeratorCancellation atribut diterapkan ke parameter pada metode yang mengembalikan antarmuka enumerator asinkron (§15.15.2) daripada antarmuka asinkron enumerable (§15.15.3).

Iterator tidak akan memiliki akses ke CancellationToken argumen GetAsyncEnumerator ketika tidak ada atribut yang memiliki parameter ini.

Contoh: Metode GetStringsAsync() ini adalah iterator asinkron. Sebelum melakukan pekerjaan apa pun untuk mengambil nilai berikutnya, ia memeriksa token pembatalan untuk menentukan apakah perulangan harus dibatalkan. Jika pembatalan diminta, tidak ada tindakan lebih lanjut yang diambil.

public static async Task ExampleCombination()
{
    var sourceOne = new CancellationTokenSource();
    var sourceTwo = new CancellationTokenSource();
    await using (IAsyncEnumerator<string> enumerator =
        GetStringsAsync(sourceOne.Token).GetAsyncEnumerator(sourceTwo.Token))
    {
        while (await enumerator.MoveNextAsync())
        {
            string number = enumerator.Current;
            if (number == "8") sourceOne.Cancel();
            if (number == "5") sourceTwo.Cancel();
            Console.WriteLine(number);
        }
    }
}

static async IAsyncEnumerable<string> GetStringsAsync(
  [EnumeratorCancellation] CancellationToken token)
{
    for (int i = 0; i < 10; i++)
    {
        if (token.IsCancellationRequested) yield break;
        await Task.Delay(1000, token);
        yield return i.ToString();
    }
}

contoh akhir

23.5.9 Atribut ModuleInitializer

Atribut ModuleInitializer digunakan untuk menandai metode sebagai penginisialisasi modul. Metode seperti itu dipanggil selama inisialisasi modul yang berisi. Modul mungkin memiliki beberapa penginisialisasi, yang disebut dalam urutan yang ditentukan implementasi.

Tidak ada batasan pada kode apa yang diizinkan dalam penginisialisasi modul.

Penginisialisasi modul harus memiliki karakteristik berikut:

  • Method_modifierstatic.
  • Tidak ada parameter_list.
  • Return_type .void
  • Tidak ada type_parameter_list.
  • Tidak dinyatakan di dalam class_declaration memiliki type_parameter_list.
  • Dapat diakses dari modul yang berisi (yaitu, memiliki pengubah internal akses atau public).
  • Bukan fungsi lokal.

23.5.9.1 Penangan ekspresi string terinterpolasi kustom

23.5.9.1.1 Mendeklarasikan handler kustom

Pertimbangkan program berikut, yang mengimplementasikan pencatat pesan sederhana:

using System;
public class Logger
{
    public void LogMessage(string msg)
    {
        Console.WriteLine(msg);
    }
}
public class Program
{
    static void Main()
    {
        var logger = new Logger();
        int val = 255;
        logger.LogMessage($"val = {{{val,4:X}}}; 2 * val = {2 * val}.");
    }
}

Output yang dihasilkan adalah, sebagai berikut:

val = {  FF}; 2 * val = 510.

Dalam panggilan ke LogMessage, target argumen ekspresi string terinterpolasi adalah parameter msg, yang memiliki jenis string. Dengan demikian, menurut §12.8.3, handler ekspresi string terinterpolasi default dipanggil. Subklaus berikut (§23.5.9.1.1) menunjukkan cara menggunakan handler kustom.

Untuk menyediakan pemrosesan kustom ke program di atas, penangan ekspresi string terinterpolasi kustom diperlukan. Di sinilah pencatat pesan dengan handler kustom ditambahkan (yang meskipun tidak lebih dari berulah seperti handler default, ia menyediakan kait untuk penyesuaian):

using System;
using System.Text;
using System.Runtime.CompilerServices;

[InterpolatedStringHandler]
public ref struct LogInterpolatedStringHandler
{
    StringBuilder builder; // Storage for the built-up string
    public LogInterpolatedStringHandler(int literalLength, int formattedCount)
    {
        builder = new StringBuilder(literalLength);
    }
    public void AppendLiteral(string s)
    {
        builder.Append(s);
    }
    public void AppendFormatted<T>(T t)
    {
        builder.Append(t?.ToString());
    }
    public void AppendFormatted<T>(T t, string format) where T : IFormattable
    {
        builder.Append(t?.ToString(format, null));
    }
    public void AppendFormatted<T>(T t, int alignment, string format)
        where T : IFormattable
    {
        builder.Append(String.Format("{0" + "," + alignment + ":" + format + "}", t));
    }
    public override string ToString() => builder.ToString();
}

public class Logger
{
    public void LogMessage(string msg)
    {
        Console.WriteLine(msg);
    }
    public void LogMessage(LogInterpolatedStringHandler builder)
    {
        Console.WriteLine(builder.ToString());
    }
}

public class Program
{
    static void Main()
    {
        var logger = new Logger();
        int val = 255;
        logger.LogMessage($"val = {{{val,4:X}}}; 2 * val = {2 * val}.");
    }
}

Output yang dihasilkan adalah, sebagai berikut:

val = {  FF}; 2 * val = 510.

Jenis yang memiliki atribut System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute dikatakan sebagai jenis handler string terinterpolasi yang berlaku.

Untuk memenuhi syarat sebagai handler ekspresi string terinterpolasi kustom, kelas atau jenis struct harus memiliki karakteristik berikut:

  • Ditandai dengan atribut System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute.
  • Memiliki konstruktor yang dapat diakses yang dua parameter pertamanya memiliki jenis int. (Parameter lain dapat mengikuti, yang digunakan untuk meneruskan informasi ke/dari handler. Ini dibahas dalam §23.5.9.1.3. Parameter akhir opsional dapat dinyatakan untuk menghambat handler memproses string terinterpolasi. Ini dibahas dalam §23.5.9.1.2).

Ketika kode yang dihasilkan kompilator memanggil konstruktor, parameter pertama diatur ke jumlah panjang segmen ekspresi string terinterpolasi (§12.8.3) dalam ekspresi string terinterpolasi, dan parameter kedua diatur ke jumlah interpolasi. (Untuk ($"val = {{{val,4:X}}}; 2 * val = {2 * val}.", nilai-nilai ini masing-masing adalah 21 dan 2.)

  • Memiliki metode yang dapat diakses dengan tanda tangan void AppendLiteral(string s), yang dipanggil untuk memproses satu segmen literal ekspresi string terinterpolasi.
  • Memiliki satu set metode kelebihan beban yang dapat diakses yang disebut AppendFormatted, salah satunya dipanggil untuk memproses interpolasi tunggal, berdasarkan konten interpolasi tersebut. Tanda tangan mereka adalah, sebagai berikut:
    • void AppendFormatted<T>(T t), yang berkaitan dengan interpolasi yang tidak memiliki format atau keselarasan eksplisit, seperti dalam kasus {2 * val}.
    • void AppendFormatted<T>(T t, string format) where T : System.IFormattable, yang berkaitan dengan interpolasi yang memiliki format eksplisit, tetapi tidak ada keselarasan, seperti dalam kasus {val:X4}.
    • void AppendFormatted<T>(T t, int alignment, string format) where T : System.IFormattable, yang berkaitan dengan interpolasi yang memiliki format dan keselarasan eksplisit, seperti dalam kasus {val,4:X}.
  • Memiliki metode publik dengan tanda tangan override string ToString(), yang mengembalikan string bawaan.

Catatan: Ini bukan kesalahan waktu kompilasi untuk menghilangkan salah AppendFormatted satu kelebihan beban, tetapi jika handler harus kuat secara maksimal, itu harus mendukung semua format yang dikenali oleh handler default. catatan akhir

Kelebihan beban LogMessage baru mengambil handler kustom alih-alih string, dan mengambil string seperti yang diformat oleh handler tersebut. Ketika kelebihan beban tersebut ada, jika handler yang sesuai ada dan ekspresi string yang diinterpolasi bukan konstanta (§12.8.3), pengkompilasi menghasilkan kode untuk memanggil yang mengambil handler. Dalam kasus seperti itu, pengkompilasi menghasilkan kode untuk

  • memanggil konstruktor handler
  • dalam urutan leksikal dalam ekspresi string terinterpolasi
    • meneruskan setiap segmen ekspresi string terinterpolasi ke AppendLiteral
    • meneruskan setiap interpolasi ke metode yang sesuai AppendFormatted .
  • mengembalikan string akhir sebagai nilai ekspresi string terinterpolasi.
  • jalankan isi .LogMessage
23.5.9.1.2 Menghambat handler kustom

Jika konstruktor handler memiliki parameter akhir jenis bool yang merupakan parameter keluar, ketika konstruktor tersebut disebut nilai parameter tersebut diuji. Jika itu benar, perilakunya seolah-olah parameter tersebut dihilangkan. Namun, jika salah, ekspresi string yang diinterpolasi tidak diproses lebih lanjut; artinya, handler dihambat. Secara khusus, ekspresi interpolasi tidak dievaluasi, dan metode AppendLiteral dan AppendFormatted tidak dipanggil.

public LogInterpolatedStringHandler(int literalLength, int formattedCount,
    out bool processString)
{
    if (some_condition)
    {
        processString = false;
        return;
    }
    else 
    {
        processString = true;
        // continue construction
    }
}

Catatan: Interpolasi dalam ekspresi string terinterpolasi mungkin berisi efek samping (sebagai hasil dari ++, , --penugasan, dan beberapa panggilan metode). Jika handler dihambat, tidak ada efek samping dalam ekspresi string terinterpolasi yang dievaluasi. Jika handler tidak dihambat, semua efek samping dalam ekspresi string terinterpolasi dievaluasi. catatan akhir

23.5.9.1.3 Meneruskan informasi ke/dari handler kustom

Ini dapat berguna untuk meneruskan informasi lain ke, dan menerima informasi kembali dari, handler kustom. Ini dilakukan melalui atribut System.Runtime.CompilerServices.InterpolatedStringHandlerArgument. Pertimbangkan kelebihan beban baru berikut ke program pencatat pesan:

public class Logger
{
    // …
    public void LogMessage(bool flag, int count,
        [InterpolatedStringHandlerArgument("count","flag","")] 
        LogInterpolatedStringHandler builder)
    {
        // …
    }
}

public ref struct LogInterpolatedStringHandler
{
    // …
    public LogInterpolatedStringHandler(int literalLength, int formattedCount,
        int count, bool flag, Logger logger)
    {
        // …
    }
}

Atribut InterpolatedStringHandlerArgument diterapkan ke parameter handler, yang harus mengikuti deklarasi parameter yang akan diteruskan ke handler. Argumen konstruktor atribut harus berupa daftar string nol atau lebih yang dipisahkan koma yang memberi nama parameter yang akan diteruskan, bersama dengan urutannya. String kosong menunjuk instans tempat handler dipanggil. Dengan demikian, panggilan konstruktor atribut di atas yang "count","flag","" berisi memerlukan konstruktor handler yang cocok. Jika daftar argumen konstruktor atribut kosong, perilakunya seolah-olah atribut dihilangkan.

out bool Jika parameter juga dinyatakan untuk memungkinkan handler dihambat (§23.5.9.1.2) parameter tersebut akan menjadi yang terakhir.

23.6 Atribut untuk interoperatur

Untuk interoperatasi dengan bahasa lain, pengindeks dapat diimplementasikan menggunakan properti terindeks. Jika tidak ada IndexerName atribut yang ada untuk pengindeks, maka nama Item tersebut digunakan secara default. Atribut ini IndexerName memungkinkan pengembang untuk mengambil alih default ini dan menentukan nama yang berbeda.

Contoh: Secara default, nama pengindeks adalah Item. Ini dapat ditimpa, sebagai berikut:

[System.Runtime.CompilerServices.IndexerName("TheItem")]
public int this[int index]
{
    get { ... }
    set { ... }
}

Sekarang, nama pengindeks adalah TheItem.

contoh akhir