Makale Özeti

.NET ile çok uzun zamandır çalışıyor olmasına karşın Reflection'ı hiç kullanmamış, hakkında pek fikir sahibi olmayan pek çok kişi tanıyorum. Bu makalede öncelikle Reflection'ın ne olduğu ve uygulama geliştirme sürecinin neresinde yer alabileceğine kısaca değinecek, sonrasında geliştirdiğimiz uygulamanın herhangi bir referans olmaksızın Assembly'leri runtime esnasında açarak içlerinde aradığı fonksiyonu bulup kullanmasını sağlayacağız.

Makale

.NET ile çok uzun zamandır çalışıyor olmasına karşın Reflection'ı hiç kullanmamış, hakkında pek fikir sahibi olmayan pek çok kişi tanıyorum. Bu makalede öncelikle Reflection'ın ne olduğu ve uygulama geliştirme sürecinin neresinde yer alabileceğine kısaca değinecek, sonrasında geliştirdiğimiz uygulamanın herhangi bir referans olmaksızın Assembly'leri runtime esnasında açarak içlerinde aradığı fonksiyonu bulup kullanmasını sağlayacağız.

Reflection ile metadatayı çalışma zamanında incelememizi sağlayan mekanizmadır diyebiliriz. Hepimizin bildiği IntelliSense, reflectionın en sık kullandığımız örneklerinden biridir. Örneğin Visual Studio içinde bir class isminden sonra nokta tuşuna bastığımızda ilgili classa ait bütün metod, özellik ve olayları listeleyen bir menü açılır. Bu bilgiler elbette önceden tanımlanmış değildir ve yazdığımız kelimeye göre hazır bir listeden getirilmemektedir. Öyle olsa IntelliSense standart classlar ile sınırlı kalmak durumunda kalacaktı. Oysa kullandığımız IntelliSense noktadan önce yer alan class'a ait bütün metod, özellik ve olayların listesini siz noktaya bastığınız anda ilgili classa ait metadatadan okuyarak dinamik olarak oluşturuyor.

Base Class Library içinde System.Reflection namespacei Reflection işlemlerinde kullanacağımız classları içermektedir. Örneğin bu namespace içinde yer alan Assembly classı, fiziksel bir assembly'i temsil eder. Hemen örnekleyelim;

Assembly asm = Assembly.LoadFrom("mshowto.dll");

Yukarıdaki satırda asm nesnesi, mshowto.dll dosyasını temsil etmeye başlamıştır. Bu noktada mshowto.dll dosyasının içinde yer alan türleri okumak için aşağıdaki kodu kullanırız:

Type[] types = asm.GetTypes();

Gördüğünüz gibi her ne kadar ilk bahsedildiğinde oldukça uzak gibi görünse de, loadLibrary ve getProcAddress ile kıyasladığımızda, .net platformunda kullanımı son derece basit bir yapı reflection.

Şimdi bahsettiğimiz örnek uygulamayı geliştirmeye başlayalım.
İşe boş bir solution oluşturarak başlıyorum.Oluşturduğum solution içinde 2 adet class library projesi oluşturuyorum. Bunları clsLibSample0 ve clsLibSample2 olarak adlandırıyorum.

clsLibSample0 projesinde 2 adet public fonksiyon yer alacak. Bu fonksiyonlar çağrıldığı anki saati gösteren showTime() ve verilen 2 sayı ile, belirtilen operatöre göre işlem yaparak sonuç dönen calculate() fonksiyonu.

Bu fonksiyonların kodları şu şekilde olacak;

public string showTime()
{
return DateTime.Now.ToLongTimeString();
}

public string calculate(string v0, string v1, string op)
{
try
{
decimal _v0 = Convert.ToDecimal(v0);
decimal _v1 = Convert
.ToDecimal(v1);

switch (op)
{
case "+":
return (_v0 + _v1).ToString();

case
"-":
return (_v0 - _v1).ToString();

case
"*":
return (_v0 * _v1).ToString();

case
"/":
return (_v0 / _v1).ToString();

default
:
return "";
}
}
catch (Exception ex)
{
return
ex.Message;
 }
}

clsLibSample1 projesinde yine 2 adet public fonksiyon yer alacak. Bu fonksiyonlar çağrıldığı anki tarihi gösteren showDate() ve verilen parametreye merhaba diyen SayHello() fonksiyonu.

Bu fonksiyonların kodları şu şekilde olacak;

public string showDate()
{
return DateTime
.Today.ToLongDateString();
}

public string sayHello(string name)
{
return "Hello "
+ name;
}

Şimdi işin eğlenceli kısmına geldi sıra. Solution'a bir windows application projesi ekliyor ve Form1'in tasarımını aşağıdaki gibi düzenliyorum.

İlk olarak form1 yüklendiği anda, form1 ile aynı klasörde yer alan ve uzantısı *.dll olan tüm assembly'leri alarak, içinde yer alan class'ları ve class'ların içindeki metodları listview kontrolünde görüntülüyorum.

ArrayList mthds = new ArrayList();
private void Form1_Load(object sender, EventArgs e)
{
//listview'in ilk sütununun genişliğini ayarlıyoruz
listView1.Columns[0].Width = listView1.Width - 10;

//uygulamanın bulunduğu klasöre ait DirectoryInfo nesnesini oluşturuyoruz
DirectoryInfo d = new DirectoryInfo(Application
.StartupPath);

//uygulamanın çalıştığı klasördeki *.dll uzantılı dosyaları FileInfo[] nesnesine atıyoruz
FileInfo[] files = d.GetFiles("*.dll"
);

//bulduğumuz her dll dosyası için dönecek bir döngü oluşturuyoruz
foreach (FileInfo file in files)
{
//sıradaki dll dosyasını asm nesnesine yüklüyoruz
Assembly asm = Assembly.LoadFrom(file.FullName);

//dll dosyasının içinde yer alan type'ları listeliyoruz
Type[] types = asm.GetTypes();

//her bir type için dönecek bir döngü oluşturuyoruz,
//bu döngü içinde type'ın içinde yer alan metodları listeleyeceğiz
foreach (Type type in types)
{
//listview'a eklemek için bir listviewitem nesnesi oluşturuyoruz
//eklediğimiz bir metod değil type olduğu için ismini ve imagelist
//içinde class iconunun sırasını temsil eden 1 değerini veriyoruz
ListViewItem cItem = new ListViewItem
(type.Name, 1);

//oluşturduğumuz listviewitem nesnesini listview'a ekliyoruz
listView1.Items.Add(cItem);

//metod isimlerini saklayan arraylist'e boş bir kayıt ekliyoruz
mthds.Add("");

//class'ın içinde yer alan metodları içerecek MethodInfo arrayini oluşturuyoruz
MethodInfo[] methods = type.GetMethods();

//ve class'In içinde yer alan metodları lsitelemeye başlıyoruz
foreach (MethodInfo info in methods)
{
//gelen bir virtual metod ise listeye dahil etmiyoruz
if (!info.IsVirtual)
{
//metoda ait parametreleri içerecek ParameterInfo arrayini oluşturuyoruz
ParameterInfo[] prms = info.GetParameters();
if (prms.Length == 0)
{
//parametresiz bir metodsa listview'a ekliyoruz
ListViewItem item = new ListViewItem();
item.Text =
" " + type.Name + "->" + info.Name;
item.ImageIndex = 0;
listView1.Items.Add(item);
mthds.Add(info.Name);
}
else if (prms.Length > 0)
{
//parametre içeren bir metodsa signature kısmını oluşturuyoruz
string _prms = " (";
foreach (ParameterInfo prm in prms)
{
_prms += prm.ParameterType.Name +
" " + prm.Name + ", ";
}
_prms = _prms.Remove(_prms.Length - 2, 2) +
")"
;

//ve metod ismi ile birlikte signature'ı da listview'a ekliyoruz
ListViewItem item = new ListViewItem();
item.Text =
" " + type.Name + "->"
+ info.Name + _prms;
item.ImageIndex = 0;
listView1.Items.Add(item);
mthds.Add(info.Name);
}
}
}
}
}
}

Bulunduğumuz noktada uygulamamızı çalıştırdığımızda karşımıza aşağıdaki ekran geliyor:

Metodları listeledik. Şimdi seçtiğimiz metodu çalıştırmaya geldi sıra. Önce bir metodu listview'da seçtiğimizde ilgili textbox'a ismini getirelim:

if (listView1.SelectedItems.Count == 1)
{
//listview'da seçilen bir Item varsa ArrayList içinde seçilen Item'ın indexine karşılık gelen metodun ismini textbox1'e yazdırıyoruz
textBox1.Text = mthds[(listView1.SelectedItems[0].Index)].ToString();
}

ve uygulamanın bulunduğu klasörde yer alan *.dll dosyaları içinde runtime sırasında aradığımız fonksiyonu bularak çalıştıralım ve dönen sonucu görüntüleyelim;

private void button1_Click(object sender, EventArgs e)
{
//uygulamanın çalıştığı klasöre ait DirectoryInfo nesnesini oluşturuyoruz
DirectoryInfo d = new DirectoryInfo(Application
.StartupPath);

//klasörde yer alan *.dll uzantılı dosyaların listesini alıyoruz
FileInfo[] files = d.GetFiles("*.dll");

//her bir *.dll dosyası için dönecek bir döngü oluşturuyoruz
foreach (FileInfo file in files)
{
//sıradaki *.dll dosyasını temsil eden asm nesnesini oluşturuyoruz
Assembly asm = Assembly
.LoadFrom(file.FullName);

//assembly'nin içinde yer alan type ları alıyoruz
Type
[] types = asm.GetTypes();

//her bir type için dönecek bir döngü oluşturuyoruz
foreach (Type type in types)
{
//type'ın içinde yer alan metodları listeliyoruz
MethodInfo[] methods = type.GetMethods();

//her bir metod için dönecek bir döngü oluşturuyoruz
foreach (MethodInfo info in methods)
{
//eğer sıradaki metod, aradığımız metod ise
//(listview'da seçtiğimiz metod), bu metodu çalıştırıyoruz
if (info.Name == textBox1.Text)
{
//boş bir obje oluşturuyoruz
object res = null;
//seçilen type için bir instance oluşturuyoruz
object obj = Activator.CreateInstance(type);
//metodun parametlerini denetliyoruz
ParameterInfo
[] prms = info.GetParameters();

if (prms.Length > 0)
{
//metoda gönderilecek parametreleri kullanıcının
//listbox2'ye eklemiş olması gerekiyor
//eğer metodun beklediği parametre sayısı ile listbox2'de yer alan
//parametre sayısı eşit değilse uyarı vererek duruyoruz
if (listBox2.Items.Count == 0)
{
label1.Text =
"Bu metodu çalıştırmak için " + prms.Length.ToString() + " parametre göndermeniz gerekiyor ancak siz " + listBox2.Items.Count.ToString() + " parametre gönderdiniz.\r\nLütfen eksik parametreleri tamamlayarak yeniden deneyin.";
break;
}
//metodun beklediği parametre sayısına göre bir object array oluşturuyoruz
object[] oArr = new object[prms.Length];
//listBox2'de yer alan değerleri oluşturduğumuz array'e kopyalıyoruz
listBox2.Items.CopyTo(oArr, 0);
//metodu çalıştırarak (Invoke ederek) dönen değeri label1'e yazdırıyoruz.
label1.Text = obj.GetType().InvokeMember(info.Name, BindingFlags.Default | BindingFlags.InvokeMethod, null, obj, oArr).ToString();
}
else
{//eğer metod parametre beklemiyorsa metodu doğrudan çalıştırıyoruz
label1.Text = obj.GetType().InvokeMember(info.Name, BindingFlags.Default | BindingFlags.InvokeMethod, null,
obj,
new object[] { }).ToString();
}}}}}
// parametre girilmişse parametre listesini temizliyoruz
listBox2.Items.Clear();
}

Uygulamamızın geldiği noktayı hemen test ediyoruz:

showTime() metodu:


calculate() metodunun parametresiz kullanım denemesi:


calculate() metodu. 19, 33 ve + parametreleri ile :


sayHello() metodu:

Gördüğünüz gibi herhangi bir referans olmadan, ayrı bir assembly içinde yer alan bir fonksiyonu çalıştırarak dönen değeri almak son derece kolay bir şekilde gerçekleştirilebiliyor.

Uygulamayı (ve makaleyi) burada sonlandırıyorum ancak bir sonraki bölümün konusu hakkında şimdiden fikir vermeyi ihmal etmiyorum.
Yazı dizisinin bir sonraki bölümünde MSIL yapısını ele alacak ve çalıştırdığımız fonksiyona ait MSIL kodunu runtime'da üreterek uygulamamızın aşağıdaki çıktıyı vermesini sağlıyor olacağız.

Son bölümde ise MSIL, C# ve C++ dillerinin yapılarını daha detaylı inceleyecek ve uygulamamıza aşağıda görebileceğiniz son halini veriyor olacağız:

Kadir Sümerkent
kadirsumerkent@msn.com
http://www.sumerkent.com