C# Attribute (Nitelik) Kavramı

Bir C# programında assembly’ler, tipler, üyeler, geri dönüş değerleri, parametreler ve diğer varlıklar onların davranışlarını belirleyecek değiştiriciler desteklerler. Örneğin, bir metoda erişebilirlik durumunu public, protedted, private ve internal anahtar kelimeleri belirler. C# bunlar gibi kullanıcı tanımlı bilgileri runtime zamanında programa uygulamak için genelleştirebilir yapıya sahiptir. Program bu gibi ilave bildirim bilgilerini attribute tanımlayarak belirler.

Nitelikler bir sınıf üyesi değildir, sadece ilişkilendirildikleri üyeler için ilave bilgi sağlarlar. Aşağıdaki örnek bir YardımAttribute niteliği tanımlar. Bu nitelik programdaki üyelerin ilişkilerine bir link sağlar:

using System;

namespace ConsoleApplication1
{
public class YardımAttribute : Attribute
{
string url;
string konu;

public YardımAttribute(string url)
{
this.url = url;
}

public string Url
{
get { return url; }
}

public string Konu
{
get { return konu; }
set { konu = value; }
}
}
}

Tüm attribute sınıfları .NET Framework tarafından desteklenen System.Attribute sınıfından türetilirler. Nitelikler ilişkilendirildikleri tanımlamdan önce köşeli parantezler içerisinde nitelik ismi ve herhangi bir argümanı ile tanımlanırlar. Eğer bir niteliğin adı Attribute ile bitiyorsa, o kısmı yazılmadan o niteliğe referans gösterilebilir. Yukarıdaki örneğimiz için konuşursak şu şekilde olur;

 [Yardım("<a href="http://sonergonul.net">http://sonergonul.net")]</a>
public class Oge
{
[Yardım("<a href="http://sonergonul.net&quot;">http://sonergonul.net"</a>, Konu="Göster")]
public void Göster(string text) { }
}

Bu örnek YardımAttribute niteliğini Oge sınıfına ve başka bir YardımAttribute niteliğini de sınıfın içerisindeki Göster metoduna bağlar. Attribute sınıfının public yapıcısı, nitelik bir program varlığına bağlandığında sağlanması gereken bilgiyi kontrol eder. İlave bilgiler attribute sınıfının read-write özelliklerine referans ile sağlanır (Az önceki Konu özelliği gibi).

Aşağıdaki örnek’e bakacak olursa, yansıma kullanılarak runtime zamanında programın varlığının nitelik bilgilerine nasıl erişebileceğimizi gösterir:

class Test
{
static void YardımGöster(MemberInfo uye)
{
YardımAttribute y = Attribute.GetCustomAttribute(uye, typeof(YardımAttribute))
as YardımAttribute;

if (y == null)
{
Console.WriteLine("{0} için yardım yok", uye);
}
else
{
Console.WriteLine("{0} için yardım", uye);
Console.WriteLine(" Url={0} Konu={1}", y.Url, y.Konu);
}
}

static void Main()
{
YardımGöster(typeof(Oge));
YardımGöster(typeof(Oge).GetMethod("Göster"));
}
}

Burada GetCustomAttribute() metodunu okumak istediğimiz bir niteliğin adını bildiğimiz zamanlarda kullanırız. Ve sonra da bir niteliğie ait bir referans elde ettiğimizde o niteliğin üyelerine ulaşabiliriz. Eğer okumak istediğimiz niteliğin adını bilmiyorsak GetCustomAttributes() metodunu kullanabiliriz. Bu metod, bir nesneye bağlanmış tüm niteliklerin listesini okur. Ve aşağıdaki şekilde bu metodu kullanabilirdik;

object[] att = typeof(Oge).GetCustomAttributes(false);
foreach(object o in att)
{

Console.WriteLine(o);

}

Bir nitelik yansıma (Reflection) ile çağırıldığı zaman, attribute sınıfının yapıcısı programın sağladığı bilgiyle çağırılır ve sonuç niteliği geri döndürülür. Eğer ilave bilgiler özellikler ile sağlandıysa, bu özellikler nitelik örneği döndürülmeden önce verilen değere atanırlar.

Attribute parametreleri 2 kategoriye ayrılırlar: konumsal ve isimsel. Konumsal parametreler nitelik sınıfının yapıcısında parametre olarak bulunurlar (public YardımAttribute(string url), http://sonergonul.net). İsimsel parametreler de attribute tipinin public alan ya da özelliklerinde bulunurlar (public string Konu, Konu=”Göster” gibi). Bir nitelik belirlediğinizde, o nitelik ilişkilendirdiği temel alınan nitelik yapıcısının konumsal parametrelerini içermek zorundadır. İsimsel parametreler ise opsiyoneldir. İhtiyaca göre kullanılır ya da kullanılmazlar. Ve bunların sırası önemli değildir. İsimsel parametrelerde değer atanması gerekli değildir. Varsayılan değerleri kullanılabilir.

Bir sınıf varlığına birden fazla nitelik bağlayabiliriz. Her nitelik tek bir köşeli parantez içinde virgül ile ayrılabilir ya da hepsi kendi köşeli parantezleri içerisinde yazılabilir. Aşağıdaki 3 örnek aynı görevi görür;

   [Serializable, Obsolete, Obfuscation]
public class Sınıf { }

[Serializable, Obsolete]
[Obfuscation]
public class Sınıf { }

[Serializable]
[Obsolete]
[Obfuscation]
public class Sınıf { }

Standart Nitelikler (AttributeUsage, Conditional, Obsolete)

Eğer bir nitelik sınıfınızda, tanımlanan niteliğin hangi öğe tipler için uygulanabileceğini belirtmek istersek AttributeUsage kullanmamız gerekir. Örneğin yazımızın başındaki YardımAttribute sınıfından önce;

[AttributeUsage(AttributeTargets.Method)]

şeklinde bir tanımlama yapsaydık, bu nitelik sadece metod yapılarına bağlanabilirdi. AttributeTargets.All seçeneği ile bu niteliğin tüm yapılara bağlanabildiğini gösterebiliriz. Ya da sadece enum ve sınıf tiplerine bağlanabileceğini belirtmek için aşağıdaki şekilde kullanabiliriz;

AttributeTargets.Enum | AttributeTargets.Class

Conditional niteliği ise koşullu metodlar oluşturulmasını sağlar. Koşullu metod ancak #define ile tanımlandığında çağırılır. Aksi halde bu metod geçilir. Bu nitelik System.Diagnostics namespace’inin içerisinde bulunur. Aşağıdaki kodunumuza bir göz atalım;

#define SARI

using System;
using System.Diagnostics;

class Fenerbahce
{
[Conditional("SARI")]
void Sarı()
{ Console.WriteLine("Sarı Rengi"); }

[Conditional("LACİVERT")]
void Lacivert()
{ Console.WriteLine("Lacivert Rengi"); }

public static void Main()
{
Fenerbahce t = new Fenerbahce();

t.Sarı();
t.Lacivert();
}
}

Bu kodumuzun çıktısı sadece “Sarı Rengi” olur. Yukarıdaki gibi Lacivert() metodunda herhangi bir #define tanımı yapılmadığı için bu metod çalıştırılmayacaktır. Aslında bu kadar basittir bu niteliğin kullanımı. Bu nitelik sadece metodlar üzerinde uygulanabilir. Koşullu metodlarla ilgili kısıtlamalardan birisi de bu metodların her zaman void döndürmeleri gerektiğidir.

Obsolete niteliği ise bir program öğesini kullanılmayan olarak işaretlememize olanak verir. Aşağıdaki örneğimizi inceleyelim:

using System;

class Test
{
[Obsolete("Bölme2 metodunu kullan")]
static int Bolme(int x, int y)
{
return x / y;
}

static int Bolme2(int x, int y)
{
return y == 0 ? 0 : x / y;
}

public static void Main()
{
Console.WriteLine(Test.Bolme(5, 4));

Console.WriteLine(Test.Bolme2(5, 4));
}
}

Görüldüğü üzere Bolme2 metodu ile Bolme metodunun daha gelişmiş bir versiyonunu kullandık. Bu programı çalıştırdığımızda iki adet Writeline ifadesi de çalışır ve sonuçlarını 1 gösterir. Fakat kod penceresine baktığımızda Test.Bolme üzerinde aşağıdaki şekilde bir uyarı alırız:

[deprecated] int Test.Bolme(int x, int y)

Warning: ‘Test.Bolme(int x, int y)’ is obsolete: ‘Bolme2 metodunu kullan’