Menu

Reflection dan Attribute di C#: typeof, GetType dan Custom Attribute

Cara kerja reflection di C#: object Type dari typeof dan GetType, membaca dan mengisi property, memanggil method lewat namanya, membuat instance, serta attribute: yang bawaan seperti Obsolete, membuat attribute sendiri, dan membacanya saat program berjalan. Ditambah biaya yang dibawa reflection.

Halaman ini dilengkapi editor yang bisa dijalankan. Edit, jalankan, dan lihat output-nya seketika.

Reflection memungkinkan program memeriksa tipe saat program berjalan: property apa saja yang dimiliki sebuah class, berapa nilainya, method apa yang ada, attribute apa yang terpasang. Attribute adalah separuh lainnya: penanda deklaratif dalam kurung siku, seperti [Obsolete] atau [JsonPropertyName("id")], yang tidak berarti apa-apa sampai compiler atau kode tertentu membacanya dengan reflection. Serializer, ORM, library validasi, framework testing, dan routing ASP.NET semuanya bekerja dengan cara ini.

Sebagian besar kode aplikasi jarang butuh reflection secara langsung, tetapi memahami cara kerjanya menjelaskan banyak perilaku framework.

Object Type: typeof dan GetType

Semuanya dimulai dari System.Type. Ada dua cara untuk mendapatkannya:

Output:

Employee
Manager
Employee
False
True
True
System.Int32
False
Name

Perbedaan yang perlu diingat: typeof(X) menyebut tipe yang sudah kamu ketahui saat menulis kode, sedangkan obj.GetType() menanyakan sebuah object tipe aslinya. Perbandingan GetType() == typeof(Employee) adalah pencocokan persis yang gagal untuk class turunan, dan biasanya bukan itu yang kamu inginkan; is dan IsAssignableFrom memperhitungkan pewarisan. nameof terlihat mirip, tetapi sama sekali bukan reflection: compiler menggantinya dengan konstanta string.

GetType() pada referensi null melempar NullReferenceException, karena tidak ada object yang bisa ditanya. Pada nullable value type yang sudah mengalami boxing, method ini mengembalikan tipe dasarnya: ((int?)5).GetType() adalah System.Int32.

Membaca dan mengisi property

GetProperties() menampilkan property public sebuah tipe sebagai object PropertyInfo, yang masing-masing bisa membaca dan menulis nilai pada instance tertentu:

Output:

Name   String   = Mug
Price  Decimal  = 8.50
Stock  Int32    = 12
7.90
True
SUP-77

Ada tiga hal yang ditunjukkan contoh ini:

  • GetValue mengembalikan object, jadi value type kembali dalam bentuk boxed dan kamu perlu melakukan cast untuk memakainya.
  • GetProperty dengan nama yang tidak ada mengembalikan null, dan pemanggilan berikutnya padanya melempar NullReferenceException. Periksa sebelum dipakai.
  • BindingFlags.NonPublic | BindingFlags.Instance bisa menjangkau member private. Itu sah di tooling dan tes, tetapi melewati enkapsulasi dan rusak diam-diam saat class itu kamu refactor.

Perulangan ini pada dasarnya adalah cara kerja exporter CSV atau serializer object ke JSON: telusuri property-nya, baca setiap nilai, lalu format.

Memanggil method dan membuat object lewat nama

GetMethod mencari sebuah method, dan Invoke memanggilnya dengan array argumen. Activator.CreateInstance membuat object dari sebuah Type, dan begitulah sistem plugin dan container DI membangun tipe yang dipilih saat program berjalan:

Output:

60.00
Decimal WithTax(1 parameters)
String Describe(0 parameters)
True

DeclaredOnly membatasi daftar hanya pada member yang dideklarasikan di class itu sendiri; tanpanya, GetMethods juga mengembalikan ToString, Equals, GetHashCode, dan GetType dari object. Type.GetType("Name") butuh nama lengkap beserta namespace-nya, dan untuk tipe di assembly lain juga nama assembly-nya ("MyApp.Plugins.Csv, MyApp.Plugins").

Jika method yang dipanggil melempar exception, Invoke membungkusnya dalam TargetInvocationException; exception aslinya ada di InnerException.

Attribute: penanda yang dibaca compiler dan framework

Attribute ditulis dalam kurung siku sebelum hal yang dijelaskannya. Framework mendefinisikan banyak attribute; berikut beberapa yang mengubah apa yang dilakukan compiler:

public class OrderService
{
    [Obsolete("Use PlaceOrderAsync instead.")]
    public void PlaceOrder(Order order) { }
    // Every call site: warning CS0618: 'OrderService.PlaceOrder(Order)' is obsolete: 'Use PlaceOrderAsync instead.'
    // [Obsolete("...", true)] makes it error CS0619 instead.

    [Conditional("DEBUG")]
    public void Trace(string message) => Console.WriteLine(message);
    // Calls to Trace are removed entirely from builds without the DEBUG symbol.
}

[Flags] enum Channels { None = 0, Email = 1, Sms = 2 }   // changes how ToString formats combinations
[Serializable] class Snapshot { }                     // marks a type for legacy binary serialization

Attribute lain dibaca oleh library saat program berjalan: [JsonPropertyName] dan [JsonIgnore] oleh System.Text.Json, [Required] dan [MaxLength] oleh validasi model ASP.NET Core dan Entity Framework, [HttpGet("orders/{id}")] oleh routing ASP.NET, [Fact] dan [Test] oleh test runner. Attribute itu sendiri tidak melakukan apa-apa; kode yang mencarinya yang bekerja.

Nama ObsoleteAttribute disingkat menjadi [Obsolete] saat dipasang: menurut konvensi, setiap nama class attribute diakhiri Attribute, dan C# mengizinkanmu menghilangkan akhiran itu.

Mendeklarasikan dan membaca custom attribute

Custom attribute adalah class yang diturunkan dari Attribute. [AttributeUsage] menyatakan di mana attribute itu boleh dipasang. Parameter constructor menjadi argumen posisional, dan property public yang bisa diisi menjadi argumen bernama:

Output:

Username must be at most 20 characters
Email is required
Keep the city code short
0

Itu adalah versi mini dari apa yang dilakukan validasi model ASP.NET Core dengan System.ComponentModel.DataAnnotations. Argumen attribute harus berupa konstanta saat kompilasi (angka, string, typeof(...), nilai enum, atau array dari semua itu), karena argumen tersebut disimpan di metadata assembly. GetCustomAttribute<T>() adalah extension method di System.Reflection; ada juga IsDefined(typeof(T)) jika kamu hanya perlu tahu apakah sebuah attribute terpasang.

Biaya reflection

Reflection menukar kecepatan dan keamanan dengan fleksibilitas:

  • Kecepatan. Mencari member lewat namanya lalu memanggilnya lewat Invoke atau GetValue jauh lebih lambat daripada pemanggilan langsung, dan value type ikut mengalami boxing. Untuk pemakaian berulang, cari PropertyInfo atau MethodInfo sekali lalu simpan, atau ubah menjadi delegate dengan Delegate.CreateDelegate atau MethodInfo.CreateDelegate lalu panggil delegate itu.
  • Keamanan. Nama yang salah ketik atau signature yang berubah tetap bisa dikompilasi, lalu gagal saat program berjalan. Sebisa mungkin, pakai nameof(Product.Price) alih-alih string "Price", agar penggantian nama ketahuan.
  • Trimming dan AOT. Aplikasi dengan trimming dan Native AOT menghapus member yang terlihat tidak dipakai, dan reflection menyembunyikan pemakaian itu dari analisisnya. Library modern (System.Text.Json, GeneratedRegex, logging) mulai beralih ke source generator, yang melakukan pekerjaan yang sama saat kompilasi.

Pakai reflection untuk bagian program yang memang tidak tahu tipenya sejak awal: plugin, tooling generic, serializer, helper untuk tes. Jika tipenya sudah diketahui, kode biasa, generic, atau interface lebih cepat dan diperiksa oleh compiler.

Kesalahan umum

  • GetType() == typeof(Base) untuk menguji base class. Cara ini gagal untuk tipe turunan. Pakai is atau IsAssignableFrom.
  • Tidak memeriksa null. GetProperty, GetMethod, dan Type.GetType mengembalikan null jika tidak ada yang cocok.
  • Reflection di dalam perulangan yang sering dijalankan tanpa cache. Simpan MemberInfo di cache atau compile sebuah delegate.
  • Menangkap exception yang salah dari Invoke. Exception aslinya adalah InnerException dari TargetInvocationException.
  • String ajaib untuk nama member. Pakai nameof.

Pertanyaan yang sering diajukan

Apa itu reflection di C#?

Reflection adalah kemampuan program untuk memeriksa tipe saat program berjalan: melihat daftar property dan method sebuah class, membaca dan mengisi nilai lewat namanya, memanggil method, membuat instance, dan membaca attribute. Fitur ini ada di System.Reflection dan dimulai dari sebuah object Type. Serializer, ORM, container dependency injection, dan framework testing dibangun di atasnya.

Apa perbedaan typeof dan GetType di C#?

typeof(Customer) ditentukan saat kompilasi dari sebuah nama tipe dan tidak butuh object. obj.GetType() dipanggil pada sebuah instance saat program berjalan dan mengembalikan tipe asli object itu, yang bisa lebih turunan daripada tipe yang dideklarasikan untuk variabelnya: untuk Animal a = new Dog();, a.GetType() adalah Dog. GetType() pada referensi null melempar NullReferenceException.

Bagaimana cara mengambil nilai property lewat namanya di C#?

obj.GetType().GetProperty("Price") mengembalikan PropertyInfo (atau null jika tidak ada property public dengan nama itu), dan .GetValue(obj) membacanya sebagai object. .SetValue(obj, value) menulisnya. Simpan PropertyInfo di cache jika kamu melakukannya di dalam perulangan, karena pencariannya adalah bagian yang mahal.

Bagaimana cara membuat custom attribute di C#?

Deklarasikan class yang diturunkan dari System.Attribute, beri nama dengan akhiran Attribute, dan tandai di mana attribute itu boleh dipakai dengan [AttributeUsage]: [AttributeUsage(AttributeTargets.Property)] class MaxLengthAttribute : Attribute { public int Length { get; } public MaxLengthAttribute(int length) { Length = length; } }. Pasang sebagai [MaxLength(50)] dan baca dengan property.GetCustomAttribute<MaxLengthAttribute>().

Apa fungsi attribute Obsolete di C#?

[Obsolete("Use PlaceOrderAsync instead")] pada sebuah member membuat compiler mengeluarkan peringatan CS0618, dengan pesanmu, di setiap tempat pemanggilannya. [Obsolete("...", true)] mengubah peringatan itu menjadi error CS0619. Dengan cara inilah library memensiunkan sebuah API tanpa merusak kode pemanggil dalam semalam.

Apakah reflection di C# lambat?

Dibandingkan pemanggilan langsung, ya: mencari member lewat namanya lalu memanggilnya lewat MethodInfo.Invoke atau PropertyInfo.GetValue biasanya puluhan sampai ratusan kali lebih lambat, dan value type ikut mengalami boxing. Ini tidak masalah untuk start-up, konfigurasi, dan pemakaian sesekali. Untuk bagian kode yang sering dijalankan, simpan MemberInfo di cache, buat delegate sekali, atau pakai generic atau source generator.

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai