C# ile yazılım geliştirmeye başladığımdan bu yana ne zaman başım sıkışsa ve daha esnek bir yapı tasarlamaya çalışsam imdadıma yetişen bir yöntem vardı: Reflection.
Reflection sayesinde çalıştığınız objelere ait verileri çalışma anında (run-time) okuyup düzenleyebilir hatta Reflection ile yine çalışma anında dinamik olarak mevcut sınıflarınızdan yeni objeler türetebilirsiniz.
Uzun uzun tanımını yapacak ve teorik bilgiye doymanızı sağlayacak bir blog yazısı yazmak yerine elimden geldiğince bol bol örnek vererek Reflection’ı iş başında görmenizi yardımcı olmaya çalışacağım.
Öncelikle Console Application türünde yeni bir Visual Studio projesi açarsanız aşağıda vereceğim kodları hızlı bir şekilde deneyip sonuçları görebilirsiniz.
Reflection’ın bize sunduğu fonksiyonları üzerinde denemek üzre bir User sınıfı oluşturalım.
public class User { public int Id { get; set; } public string Name { get; set; } public string Surname { get; set; } }
Console Application türünde bir proje yarattıysanız Program.cs adında bir sınıf otomatik olarak projenize eklenmiş olacaktır. Bu proje içinde User sınıfından bir obje oluşturup üzerinde oynamaya başlayabiliriz.
var user = new User { Id = 1, Name = "Ahmet", Surname = "Kakıcı" };
Bildiğiniz üzere C# dilinde bütün sınıflar Object sınıfından türetilmiştir. Bundan dolayı Object sınıfında olan ToString, Equals, GetType fonksiyonları diğer sınıflarda da mevcuttur. İşte bu fonksiyonlardan GetType fonksiyonu bize Reflection’ın kapılarını açmaktadır. GetType fonksiyonundan bize Type türünde bir nesne dönmekte ve bu sayede ilgili obje sınıfı üzerinde işlem yapabilmekteyiz.
Reflection ile bir nesnenin özellikleri üzerinde değişiklik yapmak istiyorsanız Type sınıfındaki GetProperties fonksionu ile ilgili nesneye ait özelliklerin bir listesini alabilirsiniz. Bu fonksiyon bize PropertyInfo tipinde bir dizi gönderecektir. Her bir dizi elemanı sınıfın içindeki bir özelliğin bilgilerini tutmaktadır. Hangi bilgilerin tutulduğunu merak ediyorsanız PropertyInfo sınıfını inceleyebilirsiniz. user nesnesindeki özellikleri aldığınızda aşağıdaki gibi bir görüntü elde edeceksiniz:
GetProperties fonksiyonu ilgili sınıfa ait tüm public özellikleri verecektir, bu noktada eğer bir filtreleme uygulamak istiyorsanız GetProperties fonksiyonunun BindingFlags tipinde bir parametre alan overload’u da bulunmaktadır. Bu sayede istediğiniz özellikleri filtreleyebilirsiniz. Tüm özellikleri değil de tek bir özelliği almak istiyorsanız GetProperties fonksiyonu yerine GetProperty fonksiyonuna alacağınız özelliğin adını string tipinde bir parametre ile kullanabilirsiniz.
var nameProperty = user.GetType().GetProperty("Name");
İstediğimiz özellik veya özellikleri aldığımıza göre artık bu özelliklerin değerlerini okuma ve bu değerleri güncelleme işlemlerini yapabiliriz. Bunun için PropertyInfo sınıfında bulunan GetValue ve SetValue fonksiyonlarını kullanacağız.
SetValue fonksiyonu PropertInfo üzerinden çağırıldığı için herhangi bir nesne ile birebir bağı bulunmamaktadır. Bunun için güncelleyeceğimiz özelliğin ait olduğu nesneyi SetValue fonksiyonuna parametre olarak vermemiz gerekiyor. İkinci parametrede ise özelliğe yazılacak değeri veriyoruz, son parametre ise eğer bu özellik index’li bir özellik ise ilgili özellikte bulunan index’i belirtmektedir. Bizim özelliğimiz bir string olduğu için son parametreye null veriyoruz.
var name = nameProperty.GetValue(user, null); nameProperty.SetValue(user, "ahmeTT", null); name = nameProperty.GetValue(user, null);
Yukarıdaki kodu çalıştırdığınızda name değişkenine en başta nesneyi oluştururken verdiğimiz ‘Ahmet’ değerini atayacaktır, daha sonra SetValue ile nesneyi güncelleyip aynı özelliği okuduğumuzda ise ‘ahmeTT’ değerini okuyacağız.
Eğer reflection ile nesnelere ait özelliklerden veri okuma ve yazma işlerini dinamik bir şekilde yapıyorsanız SetValue ve GetValue fonksiyonlarını sarmayalayacak bir fonksiyon yazıp güncelleyeceğiniz nesneyi, özellik adını ve özellik değerini parametre olarak alabilirsiniz. Bunu yaptığınız zaman parametre nesne ve özelliğe yazılacak değer için veri tipini object yaparsanız üzerinde çalıştığınız projeden olabildiğince bağımsız bir hale geleceksiniz.
public static void SetValue(object container, string propertyName, object value) { container.GetType().GetProperty(propertyName).SetValue(container, value, null); }
Bu sayede tip ve özelliklerle birebir uğraşmadan özelliklerinize yeni değer atama işlemini kolayca yapabilirsiniz.
SetValue(user, "Name", "ahmeTT"); SetValue(user, "Name", 5);
Yukarıdaki kodu çalıştırdığınızda ilk satırı hatasız bir şekilde çalıştırabilseniz de ikinci satırda “Object of type ‘System.Int32′ cannot be converted to type ‘System.String’” şeklinde bir hata mesajı alacaksınız. Bunun sebebi bizim özelliğimizin veri tipinin String olmasına rağmen inatla içine int32 tipinde bir değer yazmaya çalışmamız. Eğer tipi farklı olsa da bu tip bir atamayı yapmak istiyorsanız elinizde özelliğe ait tip bilgisi de mevcut olduğundan dolayı aynı şekilde tip dönüşümünü de yaparak fonksiyonumuzu aşağıdaki şekilde güncelleyebilir ve GetValue için de benzer bir fonksiyon yazabiliriz:
public static void SetValue(object container, string propertyName, object value) { var propertyInfo = container.GetType().GetProperty(propertyName); propertyInfo.SetValue(container, Convert.ChangeType(value, propertyInfo.PropertyType), null); } public static object GetValue(object container, string propertyName) { return container.GetType().GetProperty(propertyName).GetValue(container, null); }
Özellikleri düzenlemenin haricinde reflection ile nesneler üzerindeki fonksiyonları çağırabiliriz. Nesneye ait olan generic tipteki fonksiyonlara bu tipleri dinamik bir şekilde vererek de çağırabiliriz. Tabii tüm bu nesneleri string olarak adından türetmemiz de mümkündür. Bunlara ek olarak Attribute sınıfını miras alıp oluşturacağımız attribute’lar ile işaretlenmiş özellikler ve sınıflar üzerinde reflection ile işlem yapma konusunda da yazacaklarım var.
Bir sonraki yazıda görüşmek üzere.