Memahami Reflection di C#: Cara Kerja, Penggunaan, dan Contoh Implementasi
17 Agustus 2026
•
12 menit baca
•
3 pembaca

Memahami bagaimana C# membaca metadata, menemukan tipe, dan berinteraksi dengan kode secara dinamis saat runtime.
Bagian 1: Pendahuluan
Ketika sebuah program C# dikompilasi, compiler sudah mengetahui hampir semua hal tentang tipe-tipe yang dipakai di dalamnya. Nama class, method apa saja yang tersedia, parameter apa yang dibutuhkan, semuanya sudah "diketahui" sejak proses compile time. Karena itulah IntelliSense bisa menyarankan method dengan tepat, dan compiler bisa langsung memberi error kalau ada pemanggilan method yang salah.
Tapi ada situasi di mana informasi tentang suatu tipe baru diketahui setelah aplikasi benar-benar berjalan. Misalnya, sebuah aplikasi memuat file DLL dari luar dan perlu tahu class apa saja yang ada di dalamnya. Atau sebuah framework perlu membaca struktur sebuah object untuk mengubahnya menjadi JSON, tanpa tahu sebelumnya object itu akan berbentuk seperti apa. Dalam kasus-kasus semacam ini, compiler tidak bisa membantu karena informasinya memang belum ada saat kode ditulis.
Reflection hadir untuk menjawab kebutuhan tersebut. Reflection memungkinkan program memperoleh informasi mengenai suatu tipe ketika aplikasi sedang berjalan, mulai dari nama class, property, method, hingga constructor yang dimiliki suatu object.
Analoginya, Reflection seperti membuka "identitas" sebuah class saat aplikasi sedang berjalan. Alih-alih hanya bisa memakai apa yang sudah diketahui sejak awal, program jadi bisa bertanya langsung ke suatu tipe: class ini punya property apa saja, method apa saja, dan bagaimana cara memanggilnya, semuanya dilakukan secara dinamis.
Bagian 2: Apa Itu Reflection?
Reflection adalah mekanisme di .NET yang memungkinkan sebuah program membaca metadata dari assembly, type, dan member (property, method, field, constructor) saat aplikasi sedang berjalan (runtime), bukan saat compile time.
Dengan Reflection, program bisa melakukan hal-hal berikut secara dinamis:
- Inspect assembly — melihat assembly apa saja yang sedang dimuat, dan class apa saja yang ada di dalamnya.
- Inspect type — melihat detail sebuah tipe: nama, namespace, base class, interface yang diimplementasikan, apakah generic, dan sebagainya.
- Inspect member — melihat property, method, field, dan constructor yang dimiliki suatu tipe, lengkap dengan modifier-nya (public, private, static, virtual, dll).
- Runtime inspection — semua proses "membaca" ini terjadi ketika program sudah berjalan, bukan ketika kode sedang ditulis atau dikompilasi.
Semua kemampuan ini tersedia lewat namespace berikut:
C#
using System.Reflection;
Namespace ini berisi class-class seperti Type, PropertyInfo, MethodInfo, FieldInfo, ConstructorInfo, dan Assembly, yang masing-masing merepresentasikan bagian dari metadata sebuah tipe. Sebagai gambaran singkat, Type merepresentasikan sebuah class atau struct, PropertyInfo merepresentasikan satu property, MethodInfo merepresentasikan satu method, dan seterusnya. Class-class inilah yang nantinya dipakai untuk "membongkar" struktur sebuah tipe secara dinamis.
Yang membuat Reflection berbeda dari pemanggilan kode biasa adalah sifatnya yang tidak butuh tipe konkret diketahui sejak awal. Kode biasa memanggil method lewat nama method secara langsung, sedangkan Reflection memanggil method lewat representasi object (MethodInfo) yang didapat secara dinamis, bisa saja berdasarkan nama yang diketik user, dibaca dari file konfigurasi, atau ditemukan dari hasil scan sebuah assembly.
Bagian 3: Mengapa Reflection Dibutuhkan?
Reflection jarang dipakai untuk kebutuhan sehari-hari seperti menampilkan data atau memproses transaksi. Kegunaannya baru terasa jelas ketika sebuah program harus bekerja dengan tipe yang tidak diketahui sebelumnya, atau harus melakukan sesuatu secara generik terhadap banyak tipe berbeda tanpa menulis kode khusus untuk masing-masing tipe tersebut.
Berikut beberapa contoh nyata di mana Reflection benar-benar dibutuhkan:
- Plugin System — aplikasi yang mendukung plugin biasanya tidak tahu class apa saja yang akan dimuat dari file DLL eksternal. Reflection dipakai untuk memindai assembly tersebut, menemukan class yang mengimplementasikan interface tertentu, lalu membuat object dan menjalankannya secara dinamis.
- Serializer — untuk mengubah object menjadi JSON atau XML, serializer perlu tahu property apa saja yang dimiliki object tersebut. Karena serializer dirancang untuk bekerja dengan tipe apa pun, ia mengandalkan Reflection untuk membaca daftar property secara dinamis, bukan menulis kode konversi khusus untuk tiap class.
- ORM (Object-Relational Mapping) — ORM seperti Entity Framework perlu memetakan property sebuah class ke kolom tabel database. Pemetaan ini dilakukan lewat Reflection, karena ORM harus bisa bekerja dengan model class apa pun yang didefinisikan pengguna.
- Dependency Injection Container — container DI perlu tahu constructor apa yang dimiliki sebuah class dan parameter apa saja yang dibutuhkan, supaya bisa membuat object tersebut secara otomatis beserta seluruh dependency-nya. Proses pembacaan constructor ini dilakukan lewat Reflection.
- Unit Testing Framework — framework seperti xUnit atau NUnit perlu menemukan method mana saja yang ditandai sebagai test method, lalu menjalankannya satu per satu. Reflection dipakai untuk memindai class dan menemukan method-method tersebut.
- MVC Framework — ASP.NET Core MVC memakai Reflection untuk menemukan Controller dan Action method berdasarkan konvensi penamaan atau Attribute routing.
- Model Binder — proses mengubah data request HTTP menjadi object model juga memanfaatkan Reflection untuk mengisi property object berdasarkan nama field yang dikirim.
- JSON Serializer dan Object Mapper — library seperti
System.Text.Json, Newtonsoft.Json, atau AutoMapper memakai Reflection secara intensif untuk membaca dan mengisi property secara dinamis.
Pola yang sama muncul di semua contoh di atas: ada kebutuhan untuk bekerja dengan tipe yang sifatnya generik atau belum diketahui sebelumnya, dan menulis kode khusus untuk setiap kemungkinan tipe jelas tidak realistis. Di sinilah Reflection berperan, memungkinkan satu potong kode bekerja untuk banyak tipe berbeda tanpa harus mengenal tipe-tipe tersebut sejak awal. Karena alasan itu, banyak framework inti di .NET, mulai dari ASP.NET Core, Entity Framework Core, hingga berbagai testing framework, dibangun di atas fondasi Reflection.
Bagian 4: Memahami Metadata
Sebelum membahas lebih jauh tentang API Reflection, ada satu hal penting yang perlu dipahami terlebih dahulu: metadata. Saat sebuah project C# dikompilasi, hasilnya bukan hanya kode yang dapat dijalankan dalam bentuk IL (Intermediate Language). Assembly yang dihasilkan juga menyimpan berbagai informasi yang menjelaskan struktur kode tersebut. Informasi inilah yang disebut metadata.
Metadata dapat dianggap sebagai deskripsi tentang isi sebuah program. Informasi yang disimpan antara lain:
-
Namespace — menunjukkan pengelompokan tempat sebuah class berada.
-
Class — nama class, apakah
abstract,sealed,generic, dan sebagainya. -
Method — nama method, return type, parameter, serta modifier seperti
public,private,static, atauvirtual. -
Property — nama property, tipe data, serta apakah memiliki
getterdan/atausetter. -
Field — variabel yang dideklarasikan secara langsung di dalam class.
-
Constructor — constructor yang tersedia beserta parameternya.
-
Attribute — informasi tambahan yang ditempelkan pada class, method, property, atau member lainnya.
Sebagai contoh, terdapat class sederhana berikut:
C#
public class Person{public string Name { get; set; }public int Age { get; set; }public void Greet(){Console.WriteLine($"Hello, my name is {Name}");}}
Setelah kode tersebut dikompilasi, assembly tidak hanya berisi instruksi yang diperlukan untuk menjalankan Greet(). Assembly juga menyimpan informasi mengenai struktur Person, misalnya secara sederhana dapat dibayangkan seperti berikut:
Plain Text
Class: PersonProperty: Name (string)Property: Age (int)Method: Greet() -> void
Informasi seperti inilah yang disebut metadata.
Reflection kemudian menyediakan cara bagi program untuk membaca informasi tersebut secara langsung. Jadi, daripada harus melihat source code atau dokumentasi untuk mengetahui isi sebuah class, program dapat memeriksa informasi tersebut saat runtime melalui Reflection.
Metadata ini tersimpan di dalam assembly, seperti file .dll atau .exe. Karena itu, source code tidak harus tersedia agar struktur tipe di dalam assembly dapat diperiksa. Hal ini menjadi salah satu alasan Reflection berguna dalam berbagai skenario, termasuk plugin system. Sebuah aplikasi dapat memuat DLL dari luar, kemudian memeriksa class, method, property, attribute, dan informasi lainnya yang terdapat di dalam DLL tersebut tanpa perlu memiliki source code-nya.
Bagian 5: Mengenal Type
Titik masuk utama untuk melakukan Reflection di C# adalah class System.Type. Setiap tipe di .NET, baik itu class, struct, interface, enum, maupun delegate, direpresentasikan oleh sebuah object Type saat runtime. Object Type inilah yang menjadi "pintu" untuk mengakses seluruh metadata yang dibahas di bagian sebelumnya, mulai dari nama, namespace, property, method, hingga constructor. Ada dua cara umum untuk mendapatkan object Type dari suatu tipe.
Cara pertama, menggunakan operator typeof:
C#
Type type = typeof(Person);
typeof dipakai ketika nama tipenya sudah diketahui secara langsung di dalam kode, dievaluasi saat compile time. Cara ini paling umum dipakai ketika sedang menulis kode dan sudah tahu persis class mana yang ingin diperiksa.
Cara kedua, menggunakan method GetType():
C#
var person = new Person();Type type = person.GetType();
GetType() dipanggil dari sebuah instance object, dan akan mengembalikan tipe sebenarnya dari object tersebut saat runtime. Perbedaan ini terlihat jelas ketika ada inheritance:
C#
public class Animal { }public class Dog : Animal { }Animal animal = new Dog();Type typeOfAnimal = typeof(Animal); // selalu AnimalType typeFromInstance = animal.GetType(); // Dog, karena itu tipe aslinya
Meskipun variabel animal bertipe Animal, GetType() tetap mengembalikan Dog, karena GetType() melihat tipe object yang sesungguhnya dibuat, bukan tipe dari variabel yang menampungnya. Sebaliknya, typeof(Animal) akan selalu mengembalikan Animal, apa pun object yang sebenarnya dipegang oleh variabel tersebut, karena typeof bekerja berdasarkan nama tipe yang ditulis langsung di kode.
Sebagai patokan sederhana: pakai typeof ketika nama class sudah diketahui saat menulis kode, dan pakai GetType() ketika hanya punya instance object dan ingin tahu tipe aslinya secara dinamis, terutama saat berurusan dengan polymorphism atau ketika tipe object baru diketahui saat runtime.
Setelah mendapatkan object Type, barulah seluruh informasi metadata bisa diakses lewat property dan method yang dimiliki Type, yang akan dibahas satu per satu di bagian-bagian berikutnya.
Bagian 6: Mendapatkan Informasi Class
Setelah mendapatkan object Type, berbagai informasi dasar tentang sebuah class dapat diperiksa melalui property dan method yang tersedia di Type. Informasi ini berguna dalam berbagai situasi, misalnya ketika aplikasi perlu menampilkan informasi tipe secara dinamis, melakukan logging, debugging, atau memeriksa struktur class tanpa mengetahui detailnya terlebih dahulu.
Beberapa informasi yang paling sering digunakan antara lain:
-
Nama class —
Namememberikan nama class tanpa namespace. -
Namespace —
Namespacememberikan namespace tempat class berada. -
Base class —
BaseTypememberikanTypedari class induknya. -
Interface —
GetInterfaces()memberikan daftar interface yang diimplementasikan oleh class. -
Visibility —
IsPublic,IsNotPublic, dan property sejenisnya menunjukkan tingkat akses sebuah class. -
Generic —
IsGenericTypemenunjukkan apakah sebuah tipe merupakan generic type. -
Nested type —
IsNestedmenunjukkan apakah sebuah tipe didefinisikan di dalam tipe lain.
Informasi Dasar Class
Sebagai contoh, terdapat class Employee yang mewarisi Person:
C#
public class Employee : Person{public string Department { get; set; }}Type type = typeof(Employee);Console.WriteLine(type.Name);Console.WriteLine(type.Namespace);Console.WriteLine(type.BaseType);
Hasilnya kurang lebih:
Plain Text
EmployeeMyApp.ModelsMyApp.Models.Person
Name memberikan nama class tanpa namespace, sedangkan Namespace menunjukkan lokasi logis class tersebut. Sementara itu, BaseType memberikan object Type yang merepresentasikan class induknya. Informasi sederhana seperti ini sudah cukup untuk mengetahui hubungan dasar sebuah class dengan tipe lainnya.
Memeriksa Interface
Reflection juga dapat digunakan untuk mengetahui interface yang diimplementasikan oleh sebuah class.
Misalnya:
C#
public class Employee : Person, IComparable<Employee>{public string Department { get; set; }public int CompareTo(Employee other) => 0;}Type type = typeof(Employee);Type[] interfaces = type.GetInterfaces();foreach (var i in interfaces){Console.WriteLine(i.Name);}
Salah satu hasilnya:
Plain Text
IComparable`1
Bentuk nama seperti IComparable1 mungkin terlihat tidak biasa. Tanda **backtick (`)** diikuti angka menunjukkan jumlah generic parameter yang dimiliki tipe tersebut.
Dalam contoh ini:
Plain Text
IComparable`1↑1 generic parameter
Jadi, IComparable1menunjukkan bahwa interface tersebut memiliki satu generic parameter, yaituEmployee.
Format seperti ini merupakan bagian dari cara .NET memberi nama generic type secara internal dan akan kembali ditemukan ketika Reflection digunakan untuk membaca tipe generic.
Memeriksa Generic dan Nested Type
Type juga menyediakan beberapa property untuk mengetahui karakteristik khusus sebuah tipe. Misalnya, IsGenericType dapat digunakan untuk mengetahui apakah sebuah tipe merupakan generic type:
C#
Console.WriteLine(typeof(List<int>).IsGenericType);// True
Sedangkan IsNested dapat digunakan untuk mengetahui apakah sebuah tipe didefinisikan di dalam tipe lain:
C#
Console.WriteLine(typeof(Employee).IsNested);// False
Property seperti ini biasanya berguna ketika sebuah program perlu memperlakukan tipe berdasarkan karakteristiknya. Misalnya, sebuah tool Reflection dapat membedakan antara class biasa, generic class, dan nested class sebelum melakukan pemeriksaan lebih lanjut.
Dengan beberapa informasi dasar tersebut, program sudah dapat mengenali struktur permukaan sebuah class hanya dengan bermodalkan object Type. Nama, namespace, base class, interface, dan berbagai karakteristik lainnya dapat diperiksa tanpa harus mengetahui struktur class tersebut sebelumnya.
Bagian 7: Membaca Property
Salah satu penggunaan Reflection yang paling umum adalah membaca property yang dimiliki sebuah class. Kemampuan ini menjadi dasar bagi berbagai library, seperti serializer dan object mapper. Library tersebut perlu mengetahui property yang tersedia pada sebuah object, meskipun saat library dibuat belum tentu diketahui class apa yang nantinya akan digunakan.
Untuk membaca property, Type menyediakan method GetProperties(). Method ini mengembalikan kumpulan object PropertyInfo, dengan satu PropertyInfo untuk setiap property yang ditemukan.
Sebagai contoh, terdapat class Person:
C#
public class Person{public string Name { get; set; }public int Age { get; set; }public string Email { get; set; }}
Seluruh property dapat dibaca menggunakan Reflection:
C#
Type type = typeof(Person);PropertyInfo[] properties = type.GetProperties();foreach (var prop in properties){Console.WriteLine($"{prop.Name} : {prop.PropertyType.Name}");}
Hasilnya:
Plain Text
Name : StringAge : Int32Email : String
Di sini terdapat dua konsep penting:
-
Typemerepresentasikan class yang sedang diperiksa. -
PropertyInfomerepresentasikan satu property yang dimiliki class tersebut.
Jadi, jika Person memiliki tiga property, GetProperties() akan menghasilkan tiga object PropertyInfo.
Informasi yang Dimiliki PropertyInfo
Setiap PropertyInfo menyediakan berbagai informasi tentang property yang diwakilinya. Beberapa yang paling sering digunakan adalah:
-
Name— nama property. -
PropertyType— tipe data property dalam bentuk objectType. -
CanRead— menunjukkan apakah property memiliki getter yang dapat digunakan. -
CanWrite— menunjukkan apakah property memiliki setter yang dapat digunakan.
Contohnya:
C#
public class Product{public string Name { get; set; }public decimal Price { get; }}
Property tersebut dapat diperiksa seperti ini:
C#
Type type = typeof(Product);foreach (var prop in type.GetProperties()){Console.WriteLine($"{prop.Name} - " +$"CanRead: {prop.CanRead}, " +$"CanWrite: {prop.CanWrite}");}
Hasilnya:
Plain Text
Name - CanRead: True, CanWrite: TruePrice - CanRead: True, CanWrite: False
Name memiliki getter dan setter, sehingga CanRead dan CanWrite bernilai True.
Sementara itu, Price hanya memiliki getter:
C#
public decimal Price { get; }
Karena tidak memiliki setter, CanWrite bernilai False. Property seperti ini biasa disebut read-only property.
Bagaimana dengan Accessor yang Private?
Perlu diperhatikan bahwa GetProperties() secara default hanya mengambil property yang dapat diakses secara public.
Misalnya:
C#
public class Product{public string Name { get; set; }public decimal Price { get; }public string InternalCode { private get; set; }}
Jika menggunakan:
C#
PropertyInfo[] properties = typeof(Product).GetProperties();
InternalCode tidak akan muncul seperti property public pada umumnya karena memiliki accessor private.
Untuk membaca member yang bersifat non-public, Reflection menyediakan mekanisme tambahan melalui BindingFlags. Pembahasan mengenai hal tersebut akan dilakukan pada bagian BindingFlags.
Dari PropertyInfo Menuju Nilai Property
Sampai tahap ini, Reflection baru digunakan untuk mengenali property dan membaca informasinya. Misalnya, program sudah dapat mengetahui bahwa sebuah class memiliki:
Plain Text
Name → stringAge → intEmail → string
Namun, informasi tersebut belum menunjukkan nilai yang sedang tersimpan di dalam object.
Langkah berikutnya adalah menggunakan PropertyInfo untuk membaca atau mengubah nilai property secara dinamis, tanpa harus menulis akses langsung seperti:
C#
person.Nameperson.Age
Kemampuan inilah yang menjadi dasar dari pembahasan berikutnya tentang mengakses property secara dinamis.
Bagian 8: Membaca Field
Selain property, sebuah class juga dapat memiliki field. Field adalah variabel yang dideklarasikan secara langsung di dalam class, tanpa menggunakan getter atau setter seperti pada property.
Contohnya:
C#
public class Counter{public int Value;public static int Total;private int _internalId = 0;}
Reflection menyediakan method GetFields() untuk membaca field. Method ini mengembalikan kumpulan object FieldInfo, dengan satu FieldInfo untuk setiap field yang ditemukan.
C#
Type type = typeof(Counter);FieldInfo[] fields = type.GetFields();foreach (var field in fields){Console.WriteLine($"{field.Name} : {field.FieldType.Name}");}
Hasilnya:
Plain Text
Value : Int32Total : Int32
Value dan Total muncul karena keduanya merupakan field public. Sementara itu, _internalId tidak muncul karena bersifat private.
Instance Field dan Static Field
Perlu diperhatikan bahwa GetFields() secara default tidak hanya mencari instance field. Static field juga ikut ditemukan.
Itulah sebabnya Total tetap muncul meskipun dideklarasikan dengan keyword static:
C#
public static int Total;
Untuk membedakan field biasa dengan static field, FieldInfo menyediakan property IsStatic:
C#
foreach (var field in fields){Console.WriteLine($"{field.Name} - IsStatic: {field.IsStatic}");}
Hasilnya:
Plain Text
Value - IsStatic: FalseTotal - IsStatic: True
Value merupakan instance field, sehingga IsStatic bernilai False.
Sedangkan Total merupakan static field, sehingga IsStatic bernilai True.
Membaca Field Non-Public
Sama seperti GetProperties(), GetFields() secara default hanya mengambil field yang bersifat public. Jika field private seperti _internalId juga perlu ditemukan, pencarian dapat diperluas menggunakan BindingFlags:
C#
FieldInfo[] allFields = type.GetFields(BindingFlags.Public |BindingFlags.NonPublic |BindingFlags.Instance |BindingFlags.Static);foreach (var field in allFields){Console.WriteLine(field.Name);}
Hasilnya:
Plain Text
Value_internalIdTotal
Pada contoh tersebut, BindingFlags digunakan untuk menentukan field seperti apa yang ingin dicari.
-
Public— menyertakan field public. -
NonPublic— menyertakan field non-public, sepertiprivate. -
Instance— menyertakan field milik object. -
Static— menyertakan field static.
Dengan menggabungkan flag tersebut, pencarian mencakup seluruh field, baik public maupun non-public, serta baik instance maupun static.
Pembahasan lebih lengkap mengenai BindingFlags dan berbagai kombinasinya akan dibahas pada bagian BindingFlags.
Property vs Field
Dalam kode C# modern, property lebih sering digunakan untuk mengekspos data dibandingkan field secara langsung.
Misalnya:
C#
public string Name { get; set; }
lebih umum digunakan daripada:
C#
public string Name;
Salah satu alasannya adalah property menyediakan kontrol melalui getter dan setter, sedangkan field hanya merupakan tempat penyimpanan data secara langsung.
Meskipun demikian, kemampuan membaca field tetap penting ketika menggunakan Reflection. Field dapat ditemukan pada class lama, struct, library pihak ketiga, maupun class yang menyimpan sebagian state internalnya dalam field.
Terutama dalam skenario seperti debugging atau tool yang perlu memeriksa kondisi internal sebuah object, Reflection dapat digunakan untuk menemukan bahkan field yang bersifat private.
Sampai tahap ini, Reflection sudah dapat digunakan untuk mengenali dua jenis member utama dalam sebuah class: property melalui PropertyInfo dan field melalui FieldInfo. Bagian berikutnya akan membahas jenis member lain yang tidak kalah penting, yaitu method.
Bagian 9: Membaca Method
Selain property dan field, Reflection juga dapat digunakan untuk membaca method yang dimiliki sebuah class. Untuk itu, Type menyediakan method GetMethods(), yang mengembalikan kumpulan object MethodInfo. Setiap MethodInfo merepresentasikan satu method yang ditemukan.
Kemampuan ini menjadi bagian penting dalam berbagai framework. Misalnya, testing framework dapat menggunakan Reflection untuk menemukan method yang perlu dijalankan sebagai test. Framework MVC juga dapat menggunakannya untuk menemukan method yang berperan sebagai action pada sebuah Controller.
Sebagai contoh, terdapat class Calculator:
C#
public class Calculator{public int Add(int a, int b) => a + b;public int Subtract(int a, int b) => a - b;public static Calculator CreateDefault() => new Calculator();}
Seluruh method yang dapat ditemukan dapat dibaca menggunakan GetMethods():
C#
Type type = typeof(Calculator);MethodInfo[] methods = type.GetMethods();foreach (var method in methods){Console.WriteLine(method.Name);}
Hasilnya dapat terlihat seperti:
Plain Text
AddSubtractCreateDefaultGetTypeToStringEqualsGetHashCode
Method yang Diwarisi dari object
Ada hal penting dari hasil tersebut. GetMethods() tidak hanya mengembalikan method yang ditulis langsung di dalam Calculator.
Method seperti:
Plain Text
GetTypeToStringEqualsGetHashCode
berasal dari System.Object.
Setiap class di C# pada akhirnya memiliki object sebagai base class, baik secara langsung maupun melalui class induknya. Karena itu, method yang diwariskan juga dapat ditemukan oleh Reflection.
Jika hanya ingin melihat method yang didefinisikan langsung oleh Calculator, DeclaringType dapat digunakan sebagai filter:
C#
foreach (var method in type.GetMethods()){if (method.DeclaringType != typeof(Calculator))continue;Console.WriteLine(method.Name);}
Hasilnya:
Plain Text
AddSubtractCreateDefault
DeclaringType menunjukkan tipe tempat sebuah member pertama kali dideklarasikan. Karena ToString() dideklarasikan di object, DeclaringType untuk method tersebut adalah System.Object, bukan Calculator.
Membaca Detail Method
MethodInfo tidak hanya menyediakan nama method. Berbagai informasi lain juga dapat diperiksa, seperti return type, parameter, dan modifier.
Contohnya:
C#
foreach (var method in type.GetMethods()){if (method.DeclaringType != typeof(Calculator))continue;var parameters = method.GetParameters();var paramList = string.Join(", ",parameters.Select(p =>$"{p.ParameterType.Name} {p.Name}"));Console.WriteLine($"{method.ReturnType.Name} " +$"{method.Name}({paramList}) - " +$"Static: {method.IsStatic}, " +$"Virtual: {method.IsVirtual}");}
Hasilnya:
Plain Text
Int32 Add(Int32 a, Int32 b) - Static: False, Virtual: FalseInt32 Subtract(Int32 a, Int32 b) - Static: False, Virtual: FalseCalculator CreateDefault() - Static: True, Virtual: False
Dari contoh tersebut, beberapa informasi penting dapat diperoleh:
-
ReturnType— menunjukkan tipe data yang dikembalikan method. -
GetParameters()— mengembalikan kumpulanParameterInfoyang berisi informasi setiap parameter, termasuk nama dan tipenya. -
IsStatic— menunjukkan apakah method merupakan static method. -
IsVirtual— menunjukkan apakah method merupakan virtual method. -
DeclaringType— menunjukkan tipe tempat method tersebut dideklarasikan.
Sebagai contoh, dari:
Plain Text
Int32 Add(Int32 a, Int32 b)
dapat diketahui bahwa:
-
Nama method:
Add -
Return type:
Int32 -
Parameter pertama:
Int32 a -
Parameter kedua:
Int32 b -
Method bukan static
-
Method bukan virtual
Dengan demikian, MethodInfo dapat memberikan gambaran yang cukup lengkap tentang sebuah method tanpa perlu mengetahui source code-nya.
Sampai tahap ini, Reflection sudah dapat digunakan untuk membaca struktur class, property, field, dan method. Langkah berikutnya adalah menggunakan informasi tersebut bukan hanya untuk membaca struktur, tetapi juga memanggil method secara dinamis.
Bagian 10: Membaca Constructor
Selain method, property, dan field, constructor sebuah class juga dapat dibaca menggunakan Reflection. Kemampuan ini penting dalam berbagai situasi ketika sebuah program perlu membuat object secara otomatis. Salah satu contohnya adalah Dependency Injection (DI) container, yang perlu mengetahui constructor yang tersedia serta dependency apa saja yang dibutuhkan sebelum dapat membuat sebuah object.
Untuk membaca constructor, Type menyediakan method GetConstructors(). Method ini mengembalikan kumpulan object ConstructorInfo, dengan satu object untuk setiap constructor yang ditemukan.
Sebagai contoh, class Person memiliki dua constructor:
C#
public class Person{public string Name { get; set; }public int Age { get; set; }public Person(){}public Person(string name, int age){Name = name;Age = age;}}
Seluruh constructor tersebut dapat dibaca menggunakan Reflection:
C#
Type type = typeof(Person);ConstructorInfo[] constructors = type.GetConstructors();foreach (var ctor in constructors){var parameters = ctor.GetParameters();var paramList = string.Join(", ",parameters.Select(p =>$"{p.ParameterType.Name} {p.Name}"));Console.WriteLine($"Person({paramList})");}
Hasilnya:
Plain Text
Person()Person(String name, Int32 age)
Dari hasil tersebut dapat terlihat bahwa Person memiliki dua constructor:
Plain Text
Person()Person(String name, Int32 age)
ConstructorInfo juga menyediakan method GetParameters(), sama seperti MethodInfo. Melalui method tersebut, parameter yang dibutuhkan oleh setiap constructor dapat diperiksa.
Misalnya:
Plain Text
Person(String name, Int32 age)
berarti constructor tersebut membutuhkan dua parameter:
-
namedengan tipeString -
agedengan tipeInt32
Informasi ini penting karena sebuah program dapat mengetahui apa yang dibutuhkan untuk membuat object, bahkan sebelum constructor tersebut dipanggil.
Constructor dan Dependency Injection
Konsep ini dapat dilihat pada Dependency Injection container.
Misalnya terdapat class:
C#
public class OrderService{private readonly IOrderRepository _repository;public OrderService(IOrderRepository repository){_repository = repository;}}
Jika sebuah DI container perlu membuat OrderService, container harus mengetahui bahwa constructor tersebut membutuhkan IOrderRepository.
Secara sederhana, prosesnya dapat dibayangkan seperti:
Plain Text
OrderService│▼Cari constructor│▼Butuh IOrderRepository│▼Cari object IOrderRepository│▼Panggil constructor│▼OrderService berhasil dibuat
Reflection dapat digunakan untuk mendapatkan informasi constructor dan parameter yang dibutuhkan. Setelah itu, container dapat menentukan dependency mana yang harus disiapkan dan kemudian memanggil constructor yang sesuai.
Tentu saja, DI container nyata memiliki banyak aturan dan mekanisme tambahan. Namun, kemampuan membaca constructor dan parameternya merupakan salah satu fondasi yang memungkinkan proses tersebut dilakukan secara dinamis.
Sampai tahap ini, Reflection sudah dapat digunakan untuk membaca berbagai bagian penting dari sebuah class:
-
Type→ informasi tentang tipe -
PropertyInfo→ informasi tentang property -
FieldInfo→ informasi tentang field -
MethodInfo→ informasi tentang method -
ConstructorInfo→ informasi tentang constructor
Langkah berikutnya adalah menggunakan informasi constructor tersebut untuk benar-benar membuat object secara dinamis.
Bagian 11: Membuat Object Secara Dinamis
Setelah dapat membaca constructor, langkah berikutnya adalah menggunakan informasi tersebut untuk membuat object secara dinamis.
Pada kode biasa, object dibuat dengan menuliskan tipe dan constructor secara langsung:
C#
var person = new Person();
Namun, Reflection memungkinkan object dibuat tanpa menuliskan new Person() secara langsung. Tipe yang akan dibuat dapat ditentukan melalui object Type saat program sedang berjalan.
Kemampuan seperti ini menjadi salah satu dasar dari plugin system dan Dependency Injection container, karena tipe object yang perlu dibuat terkadang baru diketahui saat runtime.
Menggunakan Activator.CreateInstance()
.NET menyediakan class Activator untuk membantu membuat object secara dinamis. Salah satu method yang paling umum digunakan adalah Activator.CreateInstance().
Contoh paling sederhana adalah membuat object dari constructor tanpa parameter:
C#
public class Person{public string Name { get; set; } = "Unknown";public int Age { get; set; }}Type type = typeof(Person);var person = Activator.CreateInstance(type);Console.WriteLine(person.GetType().Name);// Person
Pada contoh tersebut, type berisi Type yang merepresentasikan Person.
Kemudian:
C#
Activator.CreateInstance(type);
akan membuat object berdasarkan tipe tersebut.
Secara konsep, prosesnya mirip dengan:
C#
new Person();
Perbedaannya adalah pada Reflection, tipe yang akan dibuat dapat berasal dari sebuah variabel Type, sehingga tidak harus diketahui secara langsung ketika kode ditulis.
Return Type
Ada satu hal yang perlu diperhatikan: Activator.CreateInstance() mengembalikan object.
Karena itu, jika object tersebut ingin digunakan sebagai Person, hasilnya perlu di-cast:
C#
var person = (Person)Activator.CreateInstance(type);Console.WriteLine(person.Name);// Unknown
Atau, jika tipe konkretnya memang belum diketahui saat compile time, object tersebut tetap dapat diperlakukan sebagai object dan diperiksa menggunakan Reflection.
Membuat Object dengan Constructor Berparameter
Activator.CreateInstance() juga dapat digunakan untuk constructor yang membutuhkan parameter.
Misalnya:
C#
public class Person{public string Name { get; set; }public int Age { get; set; }public Person(string name, int age){Name = name;Age = age;}}
Object dapat dibuat seperti berikut:
C#
Type type = typeof(Person);var person = (Person)Activator.CreateInstance(type,"Budi",25);Console.WriteLine($"{person.Name}, {person.Age} tahun");// Budi, 25 tahun
Dalam contoh ini, "Budi" dan 25 diberikan sebagai argumen constructor.
Secara konsep, Reflection melakukan sesuatu yang setara dengan:
C#
new Person("Budi", 25);
Bedanya, tipe Person tidak dituliskan langsung pada saat object dibuat. Tipe tersebut berasal dari variabel type.
Activator.CreateInstance() kemudian mencari constructor yang sesuai dengan argumen yang diberikan. Jika constructor yang cocok tidak ditemukan, proses pembuatan object akan gagal dan dapat menghasilkan MissingMethodException.
Menggunakan ConstructorInfo.Invoke()
Ada pendekatan lain yang lebih eksplisit, yaitu menggunakan ConstructorInfo.
Pada bagian sebelumnya, constructor dapat ditemukan menggunakan GetConstructor():
C#
Type type = typeof(Person);ConstructorInfo ctor = type.GetConstructor(new[] { typeof(string), typeof(int) });
Object kemudian dapat dibuat dengan memanggil Invoke():
C#
var person = (Person)ctor.Invoke(new object[] { "Sinta", 30 });Console.WriteLine($"{person.Name}, {person.Age} tahun");// Sinta, 30 tahun
Pendekatan ini memberikan kontrol yang lebih besar karena constructor yang digunakan sudah ditentukan secara eksplisit.
Alurnya menjadi:
Plain Text
Type│▼Cari constructor│▼ConstructorInfo│▼Siapkan parameter│▼Invoke()│▼Object
Pendekatan seperti ini berguna ketika program perlu memeriksa constructor terlebih dahulu, kemudian menentukan apakah constructor tersebut dapat digunakan sebelum akhirnya membuat object.
Hal tersebut merupakan salah satu konsep dasar yang digunakan oleh Dependency Injection container. Container dapat mencari constructor, memeriksa parameter yang dibutuhkan, mencari dependency yang sesuai, kemudian memanggil constructor tersebut untuk menghasilkan object.
Dengan demikian, Reflection tidak hanya memungkinkan program membaca struktur class, tetapi juga mulai memungkinkan program berinteraksi dengan class tersebut secara dinamis.
Pada bagian berikutnya, kemampuan ini akan dilanjutkan ke tahap yang lebih spesifik, yaitu membaca dan mengubah nilai property secara dinamis.
Bagian 12: Mengakses Property Secara Dinamis
Setelah dapat membaca daftar property dan membuat object secara dinamis, langkah berikutnya adalah membaca dan mengubah nilai property tanpa menuliskan nama property secara langsung di dalam kode.
Misalnya, akses property secara normal dilakukan seperti ini:
C#
person.Nameperson.Age = 29;
Dengan Reflection, akses tersebut dapat dilakukan berdasarkan informasi yang ditemukan saat runtime. Kemampuan ini menjadi salah satu konsep dasar yang digunakan oleh serializer dan object mapper.
Setiap PropertyInfo yang diperoleh melalui GetProperties() menyediakan dua method utama:
-
GetValue()— membaca nilai property. -
SetValue()— mengubah nilai property.
Membaca Nilai Property dengan GetValue()
Sebagai contoh, terdapat class Person:
C#
public class Person{public string Name { get; set; }public int Age { get; set; }}
Sebuah object kemudian dibuat:
C#
var person = new Person{Name = "Dimas",Age = 28};
Property Name dapat ditemukan menggunakan Reflection:
C#
Type type = typeof(Person);PropertyInfo nameProperty = type.GetProperty("Name");
Setelah PropertyInfo diperoleh, nilai property dapat dibaca menggunakan GetValue():
C#
object value = nameProperty.GetValue(person);Console.WriteLine(value);// Dimas
GetValue() membutuhkan instance object yang nilainya ingin dibaca. Dalam contoh tersebut, instance-nya adalah person.
Hasil GetValue() bertipe object, karena pada saat kode ditulis belum tentu diketahui tipe property yang sedang dibaca.
Jika tipe aslinya diperlukan, hasilnya dapat di-cast:
C#
string name = (string)nameProperty.GetValue(person);
Dengan cara ini, Reflection dapat membaca property tanpa menuliskan person.Name secara langsung.
Mengubah Nilai Property dengan SetValue()
Selain membaca, nilai property juga dapat diubah menggunakan SetValue().
Misalnya, property Age ingin diubah:
C#
PropertyInfo ageProperty = type.GetProperty("Age");ageProperty.SetValue(person, 29);Console.WriteLine(person.Age);// 29
SetValue() menerima dua informasi utama:
-
Object yang property-nya ingin diubah.
-
Nilai baru yang ingin diberikan.
Secara konsep, kode tersebut melakukan hal yang sama dengan:
C#
person.Age = 29;
Perbedaannya, Reflection menentukan property berdasarkan PropertyInfo, bukan berdasarkan akses property yang ditulis langsung di source code.
Studi Kasus: Menyalin Property dengan Nama yang Sama
Kemampuan GetValue() dan SetValue() menjadi lebih menarik ketika digunakan untuk menghubungkan dua object yang berbeda.
Misalnya terdapat dua class:
C#
public class Person{public string Name { get; set; }public int Age { get; set; }}public class PersonDto{public string Name { get; set; }public int Age { get; set; }}
Object dari kedua class tersebut dapat diproses tanpa menuliskan property satu per satu:
C#
var source = new Person{Name = "Wulan",Age = 26};var target = new PersonDto();Type sourceType = source.GetType();Type targetType = target.GetType();foreach (var sourceProp in sourceType.GetProperties()){var targetProp = targetType.GetProperty(sourceProp.Name);if (targetProp != null && targetProp.CanWrite){var value = sourceProp.GetValue(source);targetProp.SetValue(target, value);}}
Setelah proses tersebut selesai:
C#
Console.WriteLine($"{target.Name}, {target.Age} tahun");// Wulan, 26 tahun
Yang terjadi dalam proses tersebut secara sederhana adalah:
Plain Text
Person.Name││ GetValue()▼"Wulan"││ SetValue()▼PersonDto.Name
Hal yang sama dilakukan untuk Age.
Menariknya, kode tersebut tidak secara langsung menyebut:
C#
target.Name = source.Name;target.Age = source.Age;
Sebaliknya, program mencari property berdasarkan nama, membaca nilainya, kemudian menyalinnya ke property dengan nama yang sama pada object tujuan.
Sebelum menyalin nilai, kode juga memeriksa:
C#
targetProp != null
untuk memastikan property dengan nama tersebut memang tersedia pada object tujuan, dan:
C#
targetProp.CanWrite
untuk memastikan property tersebut dapat diberi nilai.
Pola sederhana seperti ini merupakan salah satu dasar dari object mapping. Library seperti AutoMapper dapat membangun proses yang jauh lebih lengkap di atas konsep serupa, termasuk menangani perbedaan tipe, nested object, collection, konfigurasi mapping, dan berbagai aturan lainnya.
Jadi, pada tahap ini Reflection tidak lagi hanya digunakan untuk membaca struktur class, tetapi sudah dapat digunakan untuk membaca dan memodifikasi data di dalam object secara dinamis.
Bagian 13: Mengakses Field
Seperti property, field juga dapat dibaca dan diubah secara dinamis menggunakan Reflection. Setiap FieldInfo yang diperoleh melalui GetFields() menyediakan method GetValue() dan SetValue(). Cara kerjanya hampir sama dengan PropertyInfo.
Perbedaannya adalah FieldInfo digunakan untuk mengakses field, sedangkan PropertyInfo digunakan untuk mengakses property.
Sebagai contoh, terdapat class Counter:
C#
public class Counter{public int Value;private int _internalId = 100;}
Membaca Nilai Field
Sebuah field dapat ditemukan menggunakan GetField():
C#
var counter = new Counter{Value = 10};Type type = typeof(Counter);FieldInfo valueField = type.GetField("Value");object value = valueField.GetValue(counter);Console.WriteLine(value);// 10
GetValue() membutuhkan instance object yang field-nya ingin dibaca. Dalam contoh tersebut, instance-nya adalah counter.
Hasilnya bertipe object, sehingga dapat di-cast jika diperlukan:
C#
int value = (int)valueField.GetValue(counter);
Mengubah Nilai Field
SetValue() digunakan untuk memberikan nilai baru pada field:
C#
valueField.SetValue(counter, 20);Console.WriteLine(counter.Value);// 20
Secara konsep, Reflection melakukan sesuatu yang setara dengan:
C#
counter.Value = 20;
Bedanya, field yang diakses ditentukan secara dinamis melalui FieldInfo.
Pola dasarnya cukup mirip dengan property:
Plain Text
FieldInfo│├── GetValue() → membaca nilai│└── SetValue() → mengubah nilai
Mengakses Field Private
Secara default, GetField() hanya mencari field yang dapat diakses secara public.
Pada contoh sebelumnya, _internalId merupakan field private:
C#
private int _internalId = 100;
Jika field tersebut perlu ditemukan, BindingFlags dapat digunakan:
C#
FieldInfo idField = type.GetField("_internalId",BindingFlags.NonPublic | BindingFlags.Instance);
Setelah FieldInfo diperoleh, nilainya dapat dibaca maupun diubah:
C#
object idValue = idField.GetValue(counter);Console.WriteLine(idValue);// 100idField.SetValue(counter, 200);Console.WriteLine(idField.GetValue(counter));// 200
Dengan Reflection, field private yang biasanya tidak dapat diakses secara langsung dari luar class dapat ditemukan dan dimodifikasi.
Mengapa Field Private Perlu Diperlakukan dengan Hati-hati?
Kemampuan ini memang berguna, tetapi penggunaannya perlu dipertimbangkan dengan baik. Field private biasanya dibuat private karena merupakan bagian dari detail internal sebuah class. Class tersebut mungkin memiliki aturan tertentu mengenai bagaimana nilai field tersebut boleh berubah.
Misalnya, sebuah class menjaga kondisi tertentu:
C#
public class BankAccount{private decimal _balance;public void Deposit(decimal amount){if (amount <= 0)throw new ArgumentException();_balance += amount;}}
Secara normal, perubahan _balance harus melalui Deposit() agar aturan yang ada di dalam class tetap dijalankan.
Reflection secara teknis dapat mengubah _balance secara langsung. Namun, jika dilakukan sembarangan, kondisi internal object dapat menjadi tidak valid karena aturan yang seharusnya dijalankan mungkin dilewati.
Karena itu, akses terhadap field private biasanya lebih masuk akal untuk kebutuhan khusus seperti unit testing, debugging, atau tooling, bukan sebagai cara umum untuk menjalankan logika bisnis aplikasi.
Kapan Akses Field Berguna?
Dalam aplikasi modern, property biasanya lebih sering digunakan untuk mengekspos data dibandingkan public field. Karena itu, penggunaan FieldInfo untuk membaca atau mengubah data tidak sesering PropertyInfo.
Meski demikian, kemampuan ini tetap berguna ketika berhadapan dengan:
-
class lama yang menggunakan public field;
-
struct atau tipe sederhana tertentu;
-
library yang struktur internalnya perlu diperiksa;
-
unit testing yang perlu memeriksa state internal;
-
debugging dan tooling yang perlu melihat kondisi object secara lebih mendalam.
Dengan demikian, FieldInfo melengkapi kemampuan Reflection dalam berinteraksi dengan data di dalam sebuah object. Setelah memahami property dan field, bagian berikutnya dapat beralih dari data ke perilaku, yaitu bagaimana Reflection digunakan untuk memanggil method secara dinamis.
Bagian 14: Memanggil Method Secara Dinamis
Setelah dapat membaca daftar method melalui GetMethods(), langkah berikutnya adalah menjalankan method tersebut secara dinamis.
Pada kode biasa, method dipanggil secara langsung:
C#
calculator.Add(5, 3);
Dengan Reflection, method dapat ditemukan terlebih dahulu melalui MethodInfo, kemudian dijalankan tanpa harus menuliskan nama method secara langsung pada saat pemanggilan.
Kemampuan ini menjadi salah satu dasar dari berbagai framework. Testing framework, misalnya, dapat menemukan method yang ditandai sebagai test lalu menjalankannya. Plugin system juga dapat menemukan dan menjalankan method tertentu dari plugin yang baru dimuat saat runtime. Untuk menjalankan method melalui Reflection, MethodInfo menyediakan method Invoke().
Memanggil Method Tanpa Parameter
Sebagai contoh, terdapat class Calculator:
C#
public class Calculator{public int Add(int a, int b) => a + b;public void PrintHello(){Console.WriteLine("Hello!");}}
Method PrintHello() dapat ditemukan menggunakan Reflection:
C#
var calculator = new Calculator();Type type = typeof(Calculator);MethodInfo printMethod = type.GetMethod("PrintHello");
Setelah MethodInfo diperoleh, method tersebut dapat dijalankan menggunakan Invoke():
C#
printMethod.Invoke(calculator, null);
Hasilnya:
Plain Text
Hello!
Invoke() menerima dua informasi utama:
-
Instance object tempat method akan dijalankan.
-
Argumen method yang diberikan dalam bentuk array.
Karena PrintHello() tidak memiliki parameter, argumen kedua dapat diberikan sebagai null.
Secara konsep, kode tersebut melakukan hal yang sama dengan:
C#
calculator.PrintHello();
Namun, pada Reflection, method yang dijalankan ditemukan secara dinamis melalui MethodInfo.
Memanggil Method dengan Parameter
Reflection juga dapat digunakan untuk method yang membutuhkan parameter.
Method Add() membutuhkan dua parameter:
C#
public int Add(int a, int b) => a + b;
Method tersebut dapat ditemukan dan dijalankan seperti ini:
C#
MethodInfo addMethod = type.GetMethod("Add");object result = addMethod.Invoke(calculator,new object[] { 5, 3 });Console.WriteLine(result);// 8
Array:
C#
new object[] { 5, 3 }
berisi nilai yang akan diberikan kepada parameter method berdasarkan urutannya.
Sehingga:
Plain Text
parameter a ← 5parameter b ← 3
Secara konsep, proses tersebut setara dengan:
C#
calculator.Add(5, 3);
Perbedaannya, Reflection menentukan method dan argumennya secara dinamis.
Return Value
Seperti GetValue(), hasil dari Invoke() bertipe object.
Jika hasil tersebut ingin digunakan sebagai int, misalnya, hasilnya dapat di-cast:
C#
int sum = (int)addMethod.Invoke(calculator,new object[] { 5, 3 });Console.WriteLine(sum + 10);// 18
Hal ini diperlukan karena Invoke() tidak mengetahui secara statis tipe return value dari method yang sedang dijalankan.
Memanggil Static Method
Static method tidak membutuhkan instance object.
Misalnya:
C#
public class MathHelper{public static int Square(int x) => x * x;}
Method tersebut dapat dipanggil melalui Reflection:
C#
MethodInfo squareMethod = typeof(MathHelper).GetMethod("Square");object result = squareMethod.Invoke(null,new object[] { 6 });Console.WriteLine(result);// 36
Perhatikan bahwa argumen pertama Invoke() adalah null.
Hal ini karena static method tidak terikat pada instance tertentu. Tidak ada object MathHelper yang perlu diberikan.
Secara konsep:
Plain Text
Instance method:method.Invoke(object, arguments)Static method:method.Invoke(null, arguments)
Bagaimana Jika Parameter Tidak Sesuai?
Karena Reflection bekerja secara dinamis, kesalahan parameter baru dapat diketahui ketika program dijalankan.
Misalnya method membutuhkan:
C#
Add(int a, int b)
tetapi argumen yang diberikan jumlahnya tidak sesuai atau memiliki tipe yang tidak dapat digunakan.
Pemanggilan Invoke() dapat menghasilkan exception, seperti TargetParameterCountException atau ArgumentException, tergantung jenis kesalahannya.
Karena itu, pemanggilan method melalui Reflection perlu menangani kemungkinan error, terutama ketika nilai parameter berasal dari sumber yang tidak sepenuhnya dapat dipercaya, seperti input pengguna atau file konfigurasi.
Dari Membaca Method hingga Menjalankannya
Jika bagian sebelumnya hanya menggunakan Reflection untuk mengetahui method apa yang tersedia, pada tahap ini Reflection sudah dapat digunakan untuk menjalankan method tersebut.
Alurnya secara sederhana:
Plain Text
Type│▼GetMethod()│▼MethodInfo│▼Invoke()│▼Method dijalankan
Kombinasi inilah yang membuat Reflection sangat berguna dalam framework. Testing framework dapat menemukan method test berdasarkan attribute atau aturan tertentu, kemudian memanggilnya dengan Invoke().
MVC framework dapat menemukan action method pada Controller, menentukan method mana yang sesuai dengan request, kemudian menjalankannya dengan parameter yang sudah disiapkan.
Dengan demikian, Reflection memungkinkan sebuah program tidak hanya mengetahui struktur kode saat runtime, tetapi juga berinteraksi dan menjalankan bagian dari kode tersebut secara dinamis.
Bagian 15: Reflection dan Attribute
Pada artikel sebelumnya tentang Attribute, sudah dibahas bahwa Attribute dapat ditempelkan pada class, method, property, atau member lainnya sebagai informasi tambahan.
Contohnya:
C#
[Obsolete]
atau custom attribute yang dibuat sendiri.
Pembahasan mengenai konsep dasar Attribute, custom Attribute, dan bagaimana Attribute bekerja sebagai metadata dapat ditemukan pada artikel Memahami Attribute di C#.
Namun, menempelkan Attribute pada kode tidak secara otomatis membuat sesuatu terjadi. Pada dasarnya, Attribute hanya menjadi informasi tambahan yang tersimpan sebagai metadata.
Agar informasi tersebut bisa digunakan, diperlukan kode yang membacanya. Salah satu cara yang umum digunakan untuk membaca Attribute saat runtime adalah Reflection.
Dengan kata lain:
Attribute menyediakan metadata tambahan, sedangkan Reflection menyediakan cara untuk membaca metadata tersebut.
Membaca Attribute dengan Reflection
Untuk membaca Attribute yang menempel pada suatu tipe atau member, Reflection menyediakan method seperti GetCustomAttributes() dan GetCustomAttribute<T>().
Sebagai contoh, terdapat custom attribute bernama MinValueAttribute:
C#
[AttributeUsage(AttributeTargets.Property)]public class MinValueAttribute : Attribute{public int Min { get; }public MinValueAttribute(int min){Min = min;}}
Attribute tersebut kemudian digunakan pada property:
C#
public class Product{[MinValue(0)]public decimal Price { get; set; }public string Name { get; set; }}
Artinya, metadata pada property Price sekarang berisi informasi bahwa nilai minimumnya adalah 0.
Reflection dapat digunakan untuk membaca informasi tersebut:
C#
Type type = typeof(Product);PropertyInfo priceProperty = type.GetProperty("Price");object[] attributes =priceProperty.GetCustomAttributes(true);foreach (var attr in attributes){if (attr is MinValueAttribute minValueAttr){Console.WriteLine($"Price minimal: {minValueAttr.Min}");}}
Hasilnya:
Plain Text
Price minimal: 0
GetCustomAttributes(true) mengembalikan seluruh Attribute yang ditemukan pada priceProperty.
Hasilnya berupa object[] karena sebuah member dapat memiliki lebih dari satu Attribute.
Misalnya sebuah property dapat memiliki:
C#
[Required][MinValue(0)]public decimal Price { get; set; }
Dalam kondisi seperti ini, Reflection perlu mengembalikan kedua Attribute tersebut.
Parameter true
Parameter true pada:
C#
GetCustomAttributes(true)
menunjukkan bahwa Attribute yang diwariskan juga dapat ikut diperiksa.
Hal ini berkaitan dengan konfigurasi AttributeUsage, khususnya pengaturan Inherited.
Untuk kebutuhan sederhana yang hanya berfokus pada Attribute yang ditempelkan langsung pada sebuah member, detail pewarisan ini tidak selalu perlu diperhatikan. Namun, ketika Attribute digunakan pada class yang memiliki inheritance, perbedaan ini menjadi penting.
Mencari Attribute Tertentu
Jika hanya ingin mencari satu jenis Attribute, tidak perlu mengambil seluruh Attribute terlebih dahulu.
Generic method GetCustomAttribute<T>() dapat digunakan:
C#
var minValueAttr =priceProperty.GetCustomAttribute<MinValueAttribute>();if (minValueAttr != null){Console.WriteLine($"Price minimal: {minValueAttr.Min}");}
Cara ini lebih ringkas karena langsung mencari MinValueAttribute. Jika Attribute tersebut tidak ditemukan, hasilnya adalah null.
Studi Kasus: Validasi Berbasis Attribute
Kombinasi Attribute dan Reflection menjadi lebih menarik ketika digunakan untuk membuat mekanisme yang bersifat generik.
Misalnya terdapat aturan:
C#
[MinValue(0)]public decimal Price { get; set; }
Aturan validasi tidak ditulis langsung di dalam Product. Informasi mengenai batas minimum disimpan sebagai Attribute.
Sebuah validator kemudian dapat membaca Attribute tersebut:
C#
public class Product{[MinValue(0)]public decimal Price { get; set; }}public static class Validator{public static List<string> Validate(object obj){var errors = new List<string>();Type type = obj.GetType();foreach (var prop in type.GetProperties()){var minValueAttr =prop.GetCustomAttribute<MinValueAttribute>();if (minValueAttr != null){var value =Convert.ToDecimal(prop.GetValue(obj));if (value < minValueAttr.Min){errors.Add($"{prop.Name} tidak boleh kurang dari " +$"{minValueAttr.Min}");}}}return errors;}}
Validator tersebut dapat digunakan seperti ini:
C#
var product = new Product{Price = -5};var errors = Validator.Validate(product);foreach (var error in errors){Console.WriteLine(error);}
Hasilnya:
Plain Text
Price tidak boleh kurang dari 0
Yang menarik, Validator tidak memiliki kode khusus seperti:
C#
if (product.Price < 0){// ...}
Validator hanya mengetahui cara membaca MinValueAttribute dan memeriksa property yang memiliki Attribute tersebut.
Akibatnya, mekanisme yang sama dapat digunakan pada class lain:
C#
public class Order{[MinValue(1)]public int Quantity { get; set; }}
Tidak diperlukan perubahan pada Validator. Selama property memiliki [MinValue], aturan tersebut dapat ditemukan dan diproses secara otomatis.
Attribute sebagai Aturan, Reflection sebagai Pembaca
Pola ini dapat diringkas seperti berikut:
Plain Text
Attribute││ menyimpan informasi▼Metadata││ dibaca oleh▼Reflection││ menghasilkan informasi▼Logic / Framework
Attribute menentukan informasi atau aturan apa yang ditempelkan, sedangkan Reflection memungkinkan program menemukan dan menggunakan informasi tersebut.
Konsep ini digunakan dalam berbagai library dan framework .NET. Salah satu contohnya adalah System.ComponentModel.DataAnnotations, yang menyediakan Attribute seperti [Required], [Range], dan berbagai aturan validasi lainnya.
Jadi, kekuatan Attribute sebenarnya bukan hanya pada Attribute itu sendiri. Nilai tambahnya muncul ketika ada mekanisme yang dapat membaca metadata tersebut dan menggunakannya untuk melakukan sesuatu secara otomatis.
Dalam konteks Reflection, Attribute merupakan salah satu cara penting untuk membuat program menentukan perilaku berdasarkan metadata, tanpa harus menuliskan aturan secara khusus untuk setiap class.
Bagian 16: Membaca Assembly
Sejauh ini, Reflection digunakan untuk membaca informasi dari satu tipe tertentu, misalnya Person yang diperoleh melalui typeof(Person) atau tipe object yang diperoleh melalui GetType().
Namun, Reflection tidak hanya bekerja pada satu class. Reflection juga dapat digunakan pada tingkat yang lebih luas, yaitu assembly.
Assembly adalah hasil kompilasi aplikasi atau library .NET, yang biasanya berbentuk file .dll atau .exe. Di dalam assembly terdapat berbagai tipe seperti class, interface, enum, dan struct.
Kemampuan untuk membaca isi sebuah assembly sangat berguna ketika program perlu menemukan tipe yang sebelumnya belum diketahui. Salah satu contohnya adalah plugin system, ketika aplikasi perlu mencari seluruh plugin yang tersedia di sebuah DLL.
Mendapatkan Assembly
Sebuah assembly direpresentasikan oleh class Assembly dari namespace System.Reflection.
Untuk mendapatkan assembly tempat kode yang sedang berjalan berada, dapat digunakan:
C#
using System.Reflection;Assembly assembly = Assembly.GetExecutingAssembly();Console.WriteLine(assembly.FullName);
Hasilnya kurang lebih:
Plain Text
MyApp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
GetExecutingAssembly() mengembalikan assembly tempat kode yang sedang dijalankan berada.
Selain assembly yang sedang berjalan, Reflection juga dapat digunakan untuk memuat assembly dari file lain.
Misalnya:
C#
Assembly pluginAssembly = Assembly.LoadFrom("MyPlugin.dll");
Pada contoh tersebut, program memuat assembly dari file MyPlugin.dll.
Kemampuan memuat DLL dari luar aplikasi seperti ini menjadi salah satu dasar dari plugin system. Aplikasi utama dapat memuat library tambahan ketika runtime, kemudian menggunakan Reflection untuk mencari dan mengenali tipe yang ada di dalamnya.
Membaca Seluruh Type dalam Assembly
Setelah mendapatkan object Assembly, seluruh tipe yang ada di dalamnya dapat diperiksa menggunakan GetTypes():
C#
Assembly assembly = Assembly.GetExecutingAssembly();Type[] types = assembly.GetTypes();foreach (var type in types){Console.WriteLine(type.FullName);}
Hasilnya bergantung pada isi project. Misalnya:
Plain Text
MyApp.Models.PersonMyApp.Models.ProductMyApp.Services.Calculator
Setiap hasil dari GetTypes() berupa object Type.
Artinya, object Type tersebut dapat diperlakukan sama seperti Type yang diperoleh melalui:
C#
typeof(Person)
Teknik yang sudah dibahas sebelumnya tetap dapat digunakan, misalnya:
Plain Text
Assembly│└── GetTypes()│▼Type│├── GetProperties()├── GetFields()├── GetMethods()├── GetConstructors()└── GetCustomAttributes()
Dengan demikian, Reflection dapat digunakan untuk bergerak dari level assembly hingga ke detail member di dalam sebuah class.
Hubungan Assembly, Module, dan Type
Secara konsep, struktur metadata .NET dapat dibayangkan seperti berikut:
Plain Text
Assembly└── Module└── Type├── Method├── Property├── Field└── Constructor
Assembly merupakan unit yang lebih besar, biasanya berupa file .dll atau .exe.
Di dalam assembly terdapat satu atau lebih module. Untuk kebanyakan aplikasi .NET modern, sebuah assembly biasanya hanya memiliki satu module sehingga konsep ini jarang terlihat dalam penggunaan sehari-hari.
Di dalam module terdapat berbagai type, seperti class, interface, struct, enum, dan delegate. Kemudian setiap type dapat memiliki member seperti method, property, field, dan constructor.
Dengan memahami hierarki ini, dapat terlihat bahwa Reflection tidak hanya digunakan untuk memeriksa satu class. Program dapat mulai dari assembly, mencari type yang diperlukan, kemudian turun lebih jauh untuk memeriksa member di dalam type tersebut.
Mencari Class yang Mengimplementasikan Interface
Salah satu penggunaan yang menarik adalah mencari seluruh class yang mengimplementasikan interface tertentu.
Misalnya terdapat interface plugin:
C#
public interface IPlugin{void Execute();}
Kemudian terdapat beberapa implementasinya:
C#
public class LoggerPlugin : IPlugin{public void Execute(){Console.WriteLine("Logger plugin dijalankan");}}public class NotificationPlugin : IPlugin{public void Execute(){Console.WriteLine("Notification plugin dijalankan");}}
Program tidak perlu mengetahui nama LoggerPlugin atau NotificationPlugin sebelumnya. Program dapat memindai seluruh type dalam assembly:
C#
Assembly assembly = Assembly.GetExecutingAssembly();var pluginTypes = assembly.GetTypes().Where(t =>typeof(IPlugin).IsAssignableFrom(t) &&!t.IsInterface);
Bagian pentingnya adalah:
C#
typeof(IPlugin).IsAssignableFrom(t)
Ekspresi tersebut digunakan untuk memeriksa apakah sebuah type dapat diperlakukan sebagai IPlugin.
Sementara itu:
C#
!t.IsInterface
digunakan agar interface IPlugin itu sendiri tidak ikut dianggap sebagai plugin.
Setelah type yang sesuai ditemukan, object dapat dibuat secara dinamis menggunakan Activator.CreateInstance():
C#
foreach (var pluginType in pluginTypes){var plugin = (IPlugin)Activator.CreateInstance(pluginType);plugin.Execute();}
Hasilnya:
Plain Text
Logger plugin dijalankanNotification plugin dijalankan
Yang menarik dari contoh tersebut adalah aplikasi tidak perlu menuliskan:
C#
new LoggerPlugin();new NotificationPlugin();
Nama dan jumlah plugin bahkan dapat berubah tanpa perlu mengubah kode pencarian tersebut. Selama sebuah class mengimplementasikan IPlugin dan berada di assembly yang dipindai, class tersebut dapat ditemukan secara otomatis.
Dari Reflection Menuju Plugin System
Contoh tersebut menggambarkan bagaimana beberapa kemampuan Reflection yang telah dibahas dapat digabungkan:
Plain Text
Assembly│▼GetTypes()│▼Cari type yang mengimplementasikan IPlugin│▼Activator.CreateInstance()│▼IPlugin│▼Execute()
Inilah salah satu pola dasar plugin system.
Aplikasi utama cukup menentukan kontrak, misalnya:
C#
public interface IPlugin{void Execute();}
Implementasi plugin dapat berada di tempat lain. Saat aplikasi berjalan, assembly plugin dimuat, seluruh type diperiksa, implementasi IPlugin ditemukan, kemudian object dibuat dan digunakan.
Sistem plugin nyata tentu biasanya memiliki mekanisme tambahan seperti validasi dependency, versioning, isolation, error handling, dan cara menentukan lokasi DLL. Namun, konsep dasarnya dapat dibangun dari kemampuan Reflection yang sudah dibahas: membaca assembly, menemukan type, membuat object, dan menjalankan method secara dinamis.
Bagian 17: Studi Kasus Nyata
Bagian ini menggabungkan seluruh teknik yang sudah dibahas sebelumnya, membaca property, membuat object, memanggil method, membaca Attribute, hingga memindai assembly, menjadi beberapa implementasi sederhana yang mendekati kondisi nyata. Tujuannya bukan menghasilkan library siap pakai, melainkan menunjukkan bagaimana potongan-potongan teknik Reflection yang sudah dipelajari saling terhubung menjadi sesuatu yang berguna.
Contoh 1: Mini Serializer
Serializer sederhana ini mengubah sebuah object menjadi teks berformat JSON, dengan membaca seluruh property-nya secara dinamis lewat Reflection, tanpa mengenal struktur class tersebut sebelumnya.
C#
public class Person{public string Name { get; set; }public int Age { get; set; }}public static class MiniSerializer{public static string Serialize(object obj){Type type = obj.GetType();var properties = type.GetProperties();var parts = properties.Select(prop =>{var value = prop.GetValue(obj);var formattedValue = value is string ? $"\"{value}\"" : value?.ToString() ?? "null";return $"\"{prop.Name}\": {formattedValue}";});return "{ " + string.Join(", ", parts) + " }";}}var person = new Person { Name = "Bima", Age = 24 };string json = MiniSerializer.Serialize(person);Console.WriteLine(json);// Output:// { "Name": "Bima", "Age": 24 }
Method Serialize() di atas membaca seluruh property lewat GetProperties(), mengambil nilainya lewat GetValue(), lalu merangkainya menjadi format JSON secara manual. Karena logikanya murni berdasarkan Reflection, method ini bisa dipakai untuk class apa pun tanpa perlu modifikasi, persis seperti cara kerja System.Text.Json di balik layar, meski tentu jauh lebih sederhana dan tanpa penanganan kasus khusus seperti nested object atau array.
Contoh 2: Mini Dependency Injection
Container DI sederhana ini membuat object secara otomatis dengan menemukan constructor yang tersedia, lalu menyediakan dependency yang dibutuhkan lewat lookup ke container itu sendiri.
C#
public interface ILogger{void Log(string message);}public class ConsoleLogger : ILogger{public void Log(string message) => Console.WriteLine($"[LOG] {message}");}public class OrderService{private readonly ILogger _logger;public OrderService(ILogger logger){_logger = logger;}public void PlaceOrder() => _logger.Log("Order berhasil dibuat");}public class MiniContainer{private readonly Dictionary<Type, Type> _registrations = new();public void Register<TInterface, TImplementation>(){_registrations[typeof(TInterface)] = typeof(TImplementation);}public T Resolve<T>(){return (T)Resolve(typeof(T));}private object Resolve(Type type){var actualType = _registrations.ContainsKey(type) ? _registrations[type] : type;var constructor = actualType.GetConstructors().First();var parameters = constructor.GetParameters();var arguments = parameters.Select(p => Resolve(p.ParameterType)).ToArray();return Activator.CreateInstance(actualType, arguments);}}var container = new MiniContainer();container.Register<ILogger, ConsoleLogger>();var orderService = container.Resolve<OrderService>();orderService.PlaceOrder();// Output:// [LOG] Order berhasil dibuat
Resolve() bekerja secara rekursif: setiap kali menemukan parameter constructor, ia mencoba me-resolve tipe parameter tersebut terlebih dulu (memakai _registrations untuk memetakan interface ke implementasinya), sebelum akhirnya memanggil constructor lewat Activator.CreateInstance() dengan seluruh dependency yang sudah disiapkan. Pola rekursif semacam ini adalah inti dari cara kerja DI container sesungguhnya seperti yang dipakai Microsoft.Extensions.DependencyInjection.
Contoh 3: Auto Mapper Sederhana
Auto mapper ini menyalin nilai antar dua object berbeda berdasarkan nama property yang sama, memperluas contoh yang sudah dibahas singkat pada Bagian 12.
C#
public class Product{public string Name { get; set; }public decimal Price { get; set; }public string InternalNote { get; set; }}public class ProductDto{public string Name { get; set; }public decimal Price { get; set; }}public static class MiniMapper{public static TTarget Map<TSource, TTarget>(TSource source) where TTarget : new(){var target = new TTarget();Type sourceType = typeof(TSource);Type targetType = typeof(TTarget);foreach (var sourceProp in sourceType.GetProperties()){var targetProp = targetType.GetProperty(sourceProp.Name);if (targetProp != null && targetProp.CanWrite && targetProp.PropertyType == sourceProp.PropertyType){var value = sourceProp.GetValue(source);targetProp.SetValue(target, value);}}return target;}}var product = new Product { Name = "Keyboard", Price = 350000, InternalNote = "stok gudang B" };var dto = MiniMapper.Map<Product, ProductDto>(product);Console.WriteLine($"{dto.Name} - Rp{dto.Price}");// Output:// Keyboard - Rp350000
InternalNote pada Product tidak ikut tersalin karena ProductDto tidak memiliki property dengan nama yang sama. Pengecekan targetProp.PropertyType == sourceProp.PropertyType ditambahkan untuk memastikan tipe data kedua property benar-benar cocok, menghindari error saat SetValue() dipanggil dengan tipe yang tidak sesuai.
Contoh 4: Plugin Loader
Contoh terakhir ini memperluas studi kasus yang sudah disinggung di Bagian 16, memuat seluruh implementasi sebuah interface dari assembly yang sedang berjalan, lalu menjalankannya satu per satu.
C#
public interface ICommand{string Name { get; }void Run();}public class BackupCommand : ICommand{public string Name => "Backup";public void Run() => Console.WriteLine("Menjalankan backup data...");}public class CleanupCommand : ICommand{public string Name => "Cleanup";public void Run() => Console.WriteLine("Membersihkan file sementara...");}public static class CommandLoader{public static void RunAll(){var assembly = Assembly.GetExecutingAssembly();var commandTypes = assembly.GetTypes().Where(t => typeof(ICommand).IsAssignableFrom(t) && !t.IsInterface);foreach (var type in commandTypes){var command = (ICommand)Activator.CreateInstance(type);Console.WriteLine($"Menjalankan command: {command.Name}");command.Run();}}}CommandLoader.RunAll();// Output:// Menjalankan command: Backup// Menjalankan backup data...// Menjalankan command: Cleanup// Membersihkan file sementara...
CommandLoader.RunAll() tidak mengenal BackupCommand maupun CleanupCommand secara langsung. Ia hanya tahu bahwa keduanya mengimplementasikan ICommand, ditemukan lewat pemindaian assembly, lalu dibuat dan dijalankan secara dinamis. Jika di kemudian hari ditambahkan ICommand baru, CommandLoader tidak perlu diubah sama sekali, ia akan otomatis menemukan dan menjalankannya. Inilah nilai utama dari plugin system berbasis Reflection: kode utama tidak perlu tahu apa pun tentang plugin yang akan ditambahkan di masa depan.
Bagian 18: BindingFlags
Di beberapa bagian sebelumnya, sudah muncul beberapa kali penyebutan bahwa method seperti GetProperties(), GetFields(), GetMethods(), dan sejenisnya, secara default hanya mengembalikan member yang bersifat public. Untuk mengubah perilaku pencarian ini, seperti menyertakan member private, atau membatasi hanya pada member static, Reflection menyediakan BindingFlags.
BindingFlags adalah enum yang berfungsi sebagai filter, menentukan kombinasi visibility dan cakupan member mana saja yang ingin disertakan dalam pencarian. Karena berupa flag, beberapa nilai BindingFlags bisa digabungkan sekaligus memakai operator |.
Beberapa nilai BindingFlags yang paling sering dipakai:
BindingFlags.Public— menyertakan member yang bersifat public.BindingFlags.NonPublic— menyertakan member yang bersifat private, protected, atau internal.BindingFlags.Instance— menyertakan member instance (bukan static).BindingFlags.Static— menyertakan member static.BindingFlags.DeclaredOnly— hanya menyertakan member yang dideklarasikan langsung pada tipe tersebut, tanpa ikut menyertakan member yang diwariskan dari base class.
Perlu diperhatikan, BindingFlags.Public dan BindingFlags.NonPublic saja tidak cukup untuk mendapatkan hasil. Kombinasi flag tersebut wajib disertai minimal satu dari BindingFlags.Instance atau BindingFlags.Static, karena keduanya menentukan cakupan pencarian. Tanpa salah satunya, hasil pencarian akan selalu kosong.
Contoh: mengambil seluruh member, termasuk yang private
C#
public class Account{public string Owner { get; set; }private decimal _balance = 1000000;private void LogTransaction() => Console.WriteLine("Transaksi dicatat");}Type type = typeof(Account);var allFields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);foreach (var field in allFields){Console.WriteLine(field.Name);}// Output:// _balance
Contoh: membedakan member yang dideklarasikan sendiri vs yang diwariskan
C#
public class Base{public void BaseMethod() { }}public class Derived : Base{public void DerivedMethod() { }}Type type = typeof(Derived);var declaredOnly = type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);foreach (var m in declaredOnly){Console.WriteLine(m.Name);}// Output:// DerivedMethod
Tanpa BindingFlags.DeclaredOnly, hasil di atas akan ikut menyertakan BaseMethod yang diwariskan dari Base, beserta method bawaan dari object seperti ToString() dan Equals().
Sebagai patokan, BindingFlags baru perlu dipakai secara eksplisit ketika kebutuhan pencarian keluar dari perilaku default (public, instance, termasuk yang diwariskan). Untuk kebutuhan sehari-hari seperti membaca property public sebuah DTO atau model, pemanggilan tanpa BindingFlags tambahan sudah cukup.
Bagian 19: Kelebihan Reflection
Setelah membahas cara kerja dan berbagai studi kasus, bagian ini merangkum apa yang membuat Reflection begitu berharga, meskipun pemakaiannya perlu dipertimbangkan matang-matang.
- Sangat fleksibel — Reflection memungkinkan kode bekerja dengan tipe yang tidak diketahui sejak compile time, membuka kemungkinan yang tidak bisa dicapai lewat pemanggilan kode biasa.
- Cocok untuk framework — banyak framework inti di .NET (ASP.NET Core, Entity Framework Core, testing framework) dibangun di atas Reflection, justru karena framework perlu bekerja dengan tipe apa pun yang didefinisikan penggunanya, bukan tipe yang sudah ditentukan sejak awal.
- Mendukung plugin — seperti dibahas pada studi kasus Plugin Loader, Reflection memungkinkan aplikasi memuat dan menjalankan kode dari luar tanpa perlu tahu strukturnya sejak awal, membuat sistem menjadi bisa diperluas (extensible) tanpa mengubah kode utama.
- Mendukung automation — proses yang sifatnya berulang untuk banyak tipe berbeda, seperti validasi, mapping, atau serialisasi, bisa ditulis satu kali secara generik lewat Reflection, alih-alih menulis kode khusus untuk setiap class.
- Mengurangi boilerplate code — seperti terlihat pada Mini Serializer dan Mini Mapper, satu potong logika berbasis Reflection bisa menggantikan puluhan baris kode manual yang seharusnya ditulis berulang untuk setiap class.
Kelebihan-kelebihan ini menjelaskan mengapa Reflection, meskipun jarang muncul di kode aplikasi sehari-hari, tetap menjadi fondasi penting di balik layar hampir semua framework .NET yang dipakai secara luas.
Bagian 20: Kekurangan Reflection
Di balik seluruh fleksibilitasnya, Reflection juga membawa sejumlah trade-off yang perlu dipahami sebelum memakainya secara sembarangan.
- Lebih lambat dibanding pemanggilan langsung — pemanggilan method lewat
Invoke(), atau akses property lewatGetValue()/SetValue(), melibatkan proses pencarian metadata dan pengecekan tipe secara dinamis, yang membuatnya jauh lebih lambat dibanding pemanggilanobject.Method()biasa yang sudah "diketahui" sejak compile time. - Sulit dibaca — kode yang penuh dengan
GetProperty(),GetValue(), danInvoke()cenderung lebih sulit ditelusuri alurnya dibanding kode biasa, karena nama property atau method yang dipanggil sering berupa string, bukan referensi langsung yang bisa diikuti lewat "go to definition". - Sulit di-debug — karena pemanggilannya bersifat dinamis, error yang muncul biasanya baru terlihat saat runtime (misalnya
TargetParameterCountExceptionatauMissingMethodException), bukan langsung terdeteksi compiler seperti kesalahan pada pemanggilan biasa. - Mengurangi type safety — banyak pengecekan yang biasanya dilakukan compiler (nama property benar, tipe data cocok, parameter sesuai) menjadi tanggung jawab yang harus dipastikan sendiri saat menulis kode berbasis Reflection, karena compiler tidak bisa memvalidasi string nama property atau method.
- Berpotensi membuka akses member private — seperti dibahas pada Bagian 13, Reflection memungkinkan pembacaan dan perubahan field maupun property yang seharusnya bersifat private, berpotensi melanggar enkapsulasi yang sengaja dirancang oleh pembuat class.
- Tidak cocok digunakan berlebihan — memakai Reflection untuk kasus yang sebenarnya bisa diselesaikan dengan pemanggilan langsung atau Generic hanya menambah kompleksitas dan menurunkan performa tanpa manfaat yang sepadan.
Kekurangan-kekurangan ini bukan berarti Reflection harus dihindari sepenuhnya, melainkan menjadi pengingat bahwa Reflection sebaiknya dipakai secara sadar, hanya pada kasus yang memang membutuhkan fleksibilitas runtime, bukan sebagai kebiasaan default dalam menulis kode.
Bagian 21: Reflection vs Generic
Reflection dan Generic sama-sama membantu menulis kode yang bisa bekerja dengan banyak tipe berbeda, tapi keduanya menyelesaikan masalah ini dengan pendekatan yang sangat berbeda. Memahami perbedaannya penting supaya tidak salah pilih, memakai Reflection untuk kasus yang sebenarnya cukup diselesaikan dengan Generic.
Pemahaman mengenai Generic dapat menjadi dasar sebelum membandingkannya dengan Reflection. Pembahasan lebih lengkap mengenai type safety, constraints, Factory Pattern, hingga penggunaan Generic dalam kasus nyata dapat ditemukan pada artikel Memahami C# Generics: Type Safety, Constraints, Factory Pattern, dan Penggunaan Nyata.
Perbandingan Reflection dan Generic:
| Aspek | Reflection | Generic |
|---|---|---|
| Compile Time | Tipe tidak perlu diketahui saat compile time | Tipe ditentukan (atau diinfer) saat compile time |
| Runtime | Pencarian metadata dan pemanggilan terjadi saat runtime | Tidak ada overhead pencarian tambahan saat runtime |
| Performance | Lebih lambat, karena ada proses lookup metadata | Setara dengan kode biasa, karena sudah ditentukan sejak compile time |
| Type Safety | Rendah, banyak validasi jadi tanggung jawab developer | Tinggi, compiler memvalidasi tipe sejak awal |
| Kemudahan Penggunaan | Lebih rumit, memakai string dan cast manual | Lebih sederhana, memakai sintaks <T> yang familiar |
Perbedaan paling mendasar terletak pada kapan tipe tersebut "diketahui". Generic memungkinkan sebuah class atau method bekerja dengan tipe yang fleksibel, tapi tipe tersebut tetap harus ditentukan saat kode dipakai, entah secara eksplisit (List<Person>) atau lewat inferensi compiler. Compiler tetap tahu persis tipe apa yang dipakai, sehingga seluruh validasi tetap bisa dilakukan sejak compile time.
Reflection berbeda. Tipe yang diproses benar-benar baru diketahui saat program berjalan, entah dari hasil scan assembly, input pengguna, atau file konfigurasi. Karena itu compiler tidak bisa membantu validasi apa pun, dan seluruh tanggung jawab itu berpindah ke developer.
Sebagai ilustrasi, method generic berikut ini bisa menerima tipe apa pun, tapi compiler tetap tahu persis apa yang terjadi:
C#
public T CreateDefault<T>() where T : new(){return new T();}var person = CreateDefault<Person>(); // compiler tahu ini Person
Bandingkan dengan pendekatan Reflection, di mana tipe baru diketahui lewat variabel yang nilainya bisa saja berasal dari luar:
C#
public object CreateDefault(Type type){return Activator.CreateInstance(type);}var person = (Person)CreateDefault(typeof(Person)); // compiler tidak tahu isinya Person
Sebagai patokan sederhana: pakai Generic ketika tipe yang dibutuhkan sudah bisa ditentukan saat menulis kode, meskipun fleksibel untuk berbagai tipe. Pakai Reflection hanya ketika tipe tersebut benar-benar tidak bisa diketahui sampai program benar-benar berjalan, seperti pada plugin system atau ketika membaca struktur object generik untuk serialisasi.
Bagian 22: Reflection vs Dynamic
Selain Generic, ada satu lagi fitur C# yang sering disandingkan dengan Reflection karena sama-sama terasa "fleksibel" terhadap tipe, yaitu keyword dynamic. Meski terlihat mirip, keduanya bekerja dengan cara dan tujuan yang cukup berbeda.
dynamic adalah tipe khusus di C# yang membuat compiler menunda seluruh pengecekan tipe sampai runtime. Ketika sebuah variabel bertipe dynamic, compiler tidak akan memvalidasi apakah property atau method yang dipanggil benar-benar ada, semua itu baru diperiksa saat kode benar-benar dijalankan.
C#
dynamic person = new Person { Name = "Fajar" };Console.WriteLine(person.Name); // Fajar, langsung diakses seperti biasaperson.NonExistentMethod(); // baru error saat runtime (RuntimeBinderException)
Dibandingkan dengan Reflection untuk mengakses property yang sama:
C#
Type type = typeof(Person);var property = type.GetProperty("Name");var value = property.GetValue(person);Console.WriteLine(value); // Fajar
Terlihat jelas perbedaannya dari sisi cara penulisan: dynamic tetap ditulis dengan sintaks akses biasa (person.Name), sedangkan Reflection membutuhkan pemanggilan API secara eksplisit (GetProperty, GetValue). Karena itu, kode yang memakai dynamic jauh lebih ringkas dan mudah dibaca dibanding Reflection.
Namun keduanya sebenarnya menyelesaikan masalah yang berbeda. dynamic cocok dipakai ketika struktur object sudah diketahui secara konsep (tahu bahwa person punya property Name), hanya saja compiler tidak diberi tahu tipe konkretnya, misalnya saat bekerja dengan hasil COM Interop, atau data yang bentuknya baru jelas setelah di-parsing (seperti hasil deserialisasi JSON tanpa model class). Reflection, sebaliknya, dipakai justru ketika strukturnya sendiri belum diketahui sama sekali, dan program perlu "bertanya" dulu member apa saja yang tersedia sebelum bisa memakainya, seperti pada plugin system atau serializer generik.
Sebagai patokan sederhana:
- Pakai
dynamicketika struktur object sudah cukup diketahui secara konsep, hanya butuh fleksibilitas dalam pemanggilan tanpa pengecekan tipe yang ketat. - Pakai Reflection ketika program perlu memeriksa struktur suatu tipe terlebih dulu (property, method, atau constructor apa saja yang tersedia) sebelum bisa memakainya.
Dari sisi performa, keduanya sama-sama lebih lambat dibanding pemanggilan langsung, karena keduanya melibatkan resolusi saat runtime. dynamic sedikit lebih cepat dibanding pemanggilan Reflection manual, karena .NET melakukan caching lewat mekanisme Dynamic Language Runtime (DLR) di baliknya, tapi tetap tidak secepat pemanggilan kode biasa yang sudah divalidasi compiler sejak awal.
Bagian 23: Reflection di Framework .NET
Setelah memahami cara kerja Reflection dari dasar hingga studi kasus mandiri, bagian ini menghubungkan kembali pemahaman tersebut dengan framework .NET yang sudah lebih dulu dikenal sehari-hari. Tujuannya bukan membedah implementasi internal secara mendalam, melainkan menunjukkan bahwa pola-pola yang sudah dipelajari di artikel ini benar-benar dipakai di balik layar framework tersebut.
ASP.NET Core MVC
Ketika sebuah request masuk, ASP.NET Core perlu menentukan Controller dan Action method mana yang harus dijalankan. Proses ini memanfaatkan Reflection untuk memindai seluruh Controller dalam assembly (mirip dengan pola pada studi kasus Plugin Loader di Bagian 17), membaca Attribute routing seperti [HttpGet] atau [Route] (mirip dengan pola Reflection dan Attribute di Bagian 15), lalu memanggil Action method yang sesuai lewat mekanisme yang secara konsep serupa dengan MethodInfo.Invoke() di Bagian 14.
Entity Framework Core
EF Core memetakan property sebuah class model ke kolom tabel database. Untuk melakukan ini, EF Core membaca seluruh property class model lewat Reflection, memeriksa Attribute seperti [Key] atau [Column] yang menempel padanya, lalu memakai informasi tersebut untuk membangun query SQL secara otomatis saat data dibaca atau disimpan.
xUnit dan NUnit
Testing framework seperti xUnit dan NUnit perlu menemukan method mana saja dalam sebuah class yang ditandai sebagai test method, biasanya lewat Attribute seperti [Fact] atau [Test]. Prosesnya persis seperti pola yang dibahas di Bagian 15 dan 16: memindai seluruh Type dalam assembly, membaca Attribute pada tiap method, lalu menjalankan method yang cocok satu per satu lewat Invoke().
AutoMapper
Seperti namanya, AutoMapper melakukan proses yang secara konsep identik dengan Mini Mapper pada Bagian 17: membaca property sumber dan tujuan lewat Reflection, mencocokkan nama dan tipe data, lalu menyalin nilainya secara otomatis. Bedanya, AutoMapper menambahkan banyak fitur tambahan seperti konfigurasi mapping kustom, konversi tipe otomatis, dan caching hasil Reflection untuk performa yang lebih baik.
Newtonsoft.Json dan System.Text.Json
Kedua library serialisasi JSON ini bekerja dengan prinsip yang sama seperti Mini Serializer pada Bagian 17: membaca seluruh property sebuah object lewat Reflection, lalu mengubahnya menjadi format JSON, dan sebaliknya, membuat object baru serta mengisi property-nya berdasarkan data JSON yang dibaca. Untuk performa, keduanya juga memakai caching yang lebih canggih dibanding contoh sederhana yang dibahas di artikel ini, seperti memanfaatkan Expression Tree atau source generator (khusus System.Text.Json versi terbaru) untuk mengurangi overhead Reflection saat runtime.
Dependency Injection
Microsoft.Extensions.DependencyInjection, container DI bawaan ASP.NET Core, bekerja dengan prinsip yang sama seperti Mini Container pada Bagian 17: membaca constructor sebuah class, menentukan dependency apa saja yang dibutuhkan lewat parameter constructor tersebut, lalu membuat seluruh object secara rekursif lewat Activator.CreateInstance().
Dari seluruh contoh di atas, terlihat pola yang berulang: hampir semua framework besar di .NET memakai kombinasi antara membaca metadata lewat Reflection dan Attribute sebagai penanda deklaratif. Memahami kedua konsep ini bukan hanya soal bisa menulis kode berbasis Reflection sendiri, tapi juga membantu memahami bagaimana framework yang sudah biasa dipakai sehari-hari sebenarnya bekerja di balik layar.
Bagian 24: Best Practice
Setelah memahami kelebihan, kekurangan, serta bagaimana Reflection dipakai pada framework .NET, bagian ini merangkum beberapa saran praktis agar Reflection dipakai secara tepat guna, bukan sekadar karena terasa fleksibel.
- Hindari Reflection bila pemanggilan langsung sudah cukup. Jika tipe yang dibutuhkan sudah diketahui sejak menulis kode, pemanggilan biasa (
object.Method()) selalu lebih cepat, lebih aman dari sisi tipe, dan lebih mudah dibaca dibanding Reflection. Reflection baru layak dipertimbangkan ketika tipe tersebut benar-benar tidak diketahui sampai runtime. - Cache hasil Reflection bila digunakan berulang. Proses pencarian
Type,PropertyInfo, atauMethodInfotergolong mahal jika dilakukan berulang-ulang, misalnya di dalam loop atau dipanggil setiap request. Menyimpan hasilGetProperties()atauGetMethod()dalamDictionaryatau field static, lalu memakainya kembali tanpa mencari ulang, adalah teknik umum yang dipakai library seperti AutoMapper danSystem.Text.Jsonuntuk menjaga performa tetap wajar. - Gunakan Expression Tree bila membutuhkan performa lebih tinggi. Untuk kasus yang butuh pemanggilan Reflection berulang kali dengan performa mendekati kode biasa,
System.Linq.Expressionsbisa dipakai untuk mengompilasi delegate dari metadata Reflection satu kali di awal, lalu memanggil delegate tersebut berkali-kali tanpa overhead lookup lagi. Teknik ini berada di luar cakupan artikel ini, tapi penting diketahui sebagai langkah lanjutan ketika performa menjadi masalah nyata. - Gunakan hanya pada kebutuhan runtime yang benar-benar dinamis. Reflection paling tepat dipakai untuk kasus seperti plugin system, serializer generik, validasi berbasis Attribute, atau Dependency Injection container, situasi di mana struktur tipe memang baru diketahui saat program berjalan. Untuk logika bisnis biasa yang tipenya sudah pasti sejak awal, Reflection hanya menambah kompleksitas tanpa manfaat yang sepadan.
Sebagai penutup bagian ini, keempat saran di atas pada dasarnya mengarah pada satu prinsip yang sama: Reflection adalah alat yang kuat, tapi kekuatannya sebanding dengan biaya performa dan kompleksitas yang dibawanya, sehingga sebaiknya dipakai secara sadar dan seperlunya, bukan sebagai kebiasaan default dalam menulis kode sehari-hari.
Bagian 25: Ringkasan
Artikel ini membahas Reflection di C# secara bertahap, mulai dari alasan keberadaannya (compiler tidak selalu tahu tipe apa yang akan dipakai sebelum aplikasi berjalan), konsep dasar seperti Type dan metadata, hingga cara membaca dan memanggil property, field, method, serta constructor secara dinamis.
Beberapa poin penting yang sudah dibahas:
- Reflection memungkinkan program membaca metadata suatu tipe (nama, property, method, constructor) saat runtime, lewat namespace
System.Reflection. Typeadalah titik masuk utama Reflection, didapat lewattypeof(saat tipe sudah diketahui di kode) atauGetType()(saat hanya punya instance object).Activator.CreateInstance()dipakai untuk membuat object secara dinamis, sementaraGetValue()/SetValue()danInvoke()dipakai untuk mengakses property/field dan memanggil method secara dinamis.- Reflection dan Attribute saling melengkapi: Attribute menyimpan metadata, Reflection membacanya.
BindingFlagsdibutuhkan untuk mengubah perilaku pencarian default, misalnya menyertakan member private atau static.- Dibanding Generic, Reflection dipakai ketika tipe benar-benar tidak diketahui sampai runtime. Dibanding
dynamic, Reflection dipakai ketika struktur suatu tipe perlu diperiksa dulu sebelum dipakai. - Hampir seluruh framework besar di .NET (ASP.NET Core, EF Core, xUnit, AutoMapper, System.Text.Json, Dependency Injection) memanfaatkan Reflection di balik layar.
Reflection merupakan fitur yang sangat kuat karena memungkinkan aplikasi memperoleh informasi mengenai tipe saat runtime, tetapi penggunaannya perlu dipertimbangkan karena memiliki biaya performa dan kompleksitas yang lebih tinggi dibanding pemanggilan biasa. Dipakai pada tempat yang tepat, seperti plugin system, serializer, atau validasi generik, Reflection menjadi alat yang sangat berharga. Dipakai secara berlebihan pada kasus yang sebenarnya bisa diselesaikan dengan cara biasa, Reflection justru hanya menambah beban performa dan kompleksitas yang tidak perlu.
Daftar isi
Baca juga
Artikel sebelumnya

11 Agustus 2026
•
1 jam baca
Memahami Attribute di C#: Metadata yang Membuat Kode Lebih Fleksibel dan Powerful
Attribute adalah salah satu mekanisme penting dalam ekosistem .NET untuk menyatakan metadata secara deklaratif. Dengan memahami bagaimana Attribute didefinisikan, menerima parameter, ditempatkan pada class atau method, hingga dibaca kembali menggunakan Reflection, dapat terlihat bagaimana framework seperti ASP.NET Core dan Entity Framework memanfaatkan informasi tersebut untuk menentukan perilaku aplikasi. Pembahasan dimulai dari konsep dasar Attribute sebagai class, pembuatan Custom Attribute, AttributeUsage, dan Reflection, kemudian dilanjutkan dengan contoh penggunaan dalam mini framework serta gambaran penerapannya pada ASP.NET Core.
Artikel berikutnya

24 Agustus 2026
•
2 jam baca
.NET di Balik Layar
Azure tidak mengubah cara fundamental aplikasi .NET dieksekusi. C# tetap dikompilasi menjadi IL, .NET Runtime tetap menjalankan assembly, JIT tetap menghasilkan native code, dan CPU tetap mengeksekusinya. Yang berubah adalah bagaimana infrastructure di bawah aplikasi disediakan dan dikelola oleh Azure.