C# Attribute’lara Derinlemesine Bakış

c# attribute nasıl kullanılır? Sistem attribute’ları nelerdir? Kendi attribute’ümuzu nasıl oluştururuz? Bir alanın attribute’larını bulmak

C# Attribute’lara Derinlemesine Bakış

Başlangıç

Eğer C# ile herhangi bir Back-End içeren uygulama yazdıysanız illa ki köşeli parantezlerin içine yazılan, sanki kod yazarken koda başlık düşmüşüz gibi görülen kelimelerle karşılaşmışsınızdır. Peki bunların ne olduğunu, kullanımını ve arka planda nasıl çalıştıklarını hiç merak ettiniz mi? Hadi gelin bunları derinlemesine inceleyelim ve örnekleriyle anlayalım.

Attribute Nedir?

Öznitelik veya filtre olarak Türkçeye çevirebileceğimiz Attribute’lar, ilk bakışta geliştiricilere kod hakkında bilgi verici zımbırtılar olarak görünse de aslında kodun çalışma zamanında(run time) assembly, type, class, method, property ve benzeri şeylerle etkileşime girmek ve ilişkilendirmek için kullanılan bir özelliktir.

Attribute’lar, assembly’lere programımız hakkında bilgileri içeren Metadata’ları iletmemize olanak sağlar. Derleyiciler, kullandığımız paketler veya kodumuzun ta kendisi bu bilgileri okuyarak klasik işleyişi yerine farklı yöntemlerle işini gerçekleştirebilir böylece daha kolay ve esnek şekilde kod yazabiliriz.

Şu ana kadar temel olarak Attribute’ları konuştuk. Şimdi de hadi biraz teknik açıdan bakalım.

Nasıl kullanılır:

Attribute’ların en popüler kullanım yerlerini class’lar, method’lar ve property’lerdir. Bu alanların öncesine yahut bir üst satırına köşeli parantezler içine attribute ismi yazılır.

[AttributeA] public class User{
[AttributeB]
public string name { get; set; }
public bool setPassword([In][Out] ref double x) { }
}

Bu kullanım Attribute’ü ilgili yerde aktif hale getirir.

Eğer Attribute’unuza derleyicinin just-in-time anında bilmesini istediğiniz bir metadata aktarmak istiyorsanız bunu parametrelerini kullanarak yapabilirsiniz. Normal bir constructor kullanır gibi sırayla parametre girebilir veya parametrenin ismini kullanarak atama yapabilirsiniz.

[Author("P. Ackerman", version = 1.1)]
class Users{
// P. Ackerman için bazı mantıksal işlemler.
}

Aynı alan için yukarıdan aşağıya yahut soldan sağa birden fazla attribute yazabilirsiniz. Kullandığınız mantıksal yapıya göre bunların sırası önem gösterecektir.

Attribute’ları nasıl kullanadığımızı anladıysak şimdi de sistemin bize hangi attribute’ları verdiğine bakalım ve ardından kendi attribute’larımızı nasıl yazabileceğimizi konuşalım.

Build-in attribute’lar:

Obsolete: Kodunuzda artık kullanılmasını tavsiye etmediğiniz yerleri belirtmenize yarar. Derleyici bu alanlar kullanıldığında uyarı verir.

public class Users{
[ObsoleteAttribute("Bu fonksiyon guvenlik nedeniyle artik kullanilmamaktadır. Lutfen 'guncelSifreFonksiyonu' methodunu kullanin!")]
public string tehlikeliSifreOluşturmaFonksiyonu() { }
public string guncelSifreFonksiyonu(){}
}

tehlikeliSifreOluşturmaFonksiyonu çağrıldığında terminalde göreceğimiz çıktı şu şekildedir:

Derleyicinin verdiği obsolete uyarısı

Conditional: Derleyicinin belirlediğimiz koşullar varsa o alanı çalıştırmasına olanak sağlar.

public class Users{
[Conditional("DEBUG")]
public void tumBilgileriYazdir() { }
}

tumBilgileriYazdir fonksiyonu sadece “DEBUG” modundayken çalışır. Derleyici ayarlarınıza göre hata veya uyarı fırlatabilir ya da hiçbir şey göstermeyebilir. Sadece sistem durumlarını değil define anahtar kelimesi ile derleyiciye kendi tanımlayacağınız durumları da belirtebilirsiniz.

Bkz.: https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.conditionalattribute?view=net-6.0#examples

Serializable: Sınıflarınızın serileştirilebileceğini belirtir.

NonSerialized: Sınıflarınızın serileştirilemeyeceğini belirtir.

DLLImport: Yolunu vereceğiniz DLL’i kodunuza almanızı sağlar.

Bunlar En çok kullanıldığını düşündüğüm attribute’lardı fakat sistemde onlarca farklı attribute kurulu olarak gelmektedir.

Custom attribute oluşturmak:

Sıradan olmayan bir kod yazıyorsanız kendi işleyişinize uygun bir attribute bulmakta zorlandığınızda özel bir attribute’a ihtiyacınız olduğunu hissettiğinizde tek yapmanız gereken System.Attribute sınıfından kalıtım alarak kişisel bir attribute oluşturmak.

Senaryomuzda otomatik olarak fonksiyon çalıştığında log’lama yapılması gereksin ve içerisine bir mesaj ile birlikte log’lanma seviyesini alsın. Eğer iki bilgi de girilmediyse kullanıcıya uyarı versin. Bu işlemi yapacak kod aşağıdaki gibi olabilir:

using System;
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class LoglayiciAttribute : Attribute {
private string dosyaIsmi = "Log.txt";
private string mesaj;
public int logSeviyesi;
public LoglayiciAttribute(string mesaj){
this.mesaj = mesaj;
logSeviyesi = 1;
dosyayaYaz();
}
public LoglayiciAttribute(){
// Kullanıcıya parametre vermediğimizde uyarı verecek kod
}
private void dosyayaYaz(){
// dosyaIsmi, mesaj ve logSeviyesi bilgilerini kullanarak dosyaya yazma islemleri
}
}

AttributeUsage ile attribute’umuzun nerelerde ve nasıl çalışabileceğini belirliyoruz. Bu örneğimizde sadece method’larda çalışmasını istediğimiz AttributeTargets.Method değerini seçtik. Birden fazla yerde kullanılmasını istiyorsanız alanlar arasında | işareti koyarak yazabilirsiniz veya AttributeTargets.All diyebilirsiniz.

Kullanımı ise şu şekildedir:

[LoglayiciAttribute("Kod kullaniciSil fonksiyonuna girdi", logSeviyesi = 2)]
public bool kullaniciSil(int id){ }

Kullandığınız C# ortamının versiyonuna göre değişmekle birlikte attribute sınıfının ismini Attribute ile bitirseniz bile kullanırken sadece Loglayici diyerek de kullanabilirsiniz.

Attribute’ları middleware gibi kullanabilir miyiz? Method’lara giden parametrelere erişebilir miyiz?

Attribute’lar metadata üretmek için kullanılan yapılardır. Normalde Web API yazarken kullanabileceğiniz attribute’larla bir method’un parametrelerine veya controller’dan gönderilen bilgilere erişebilmek pek mümkün değildir fakat ASP.NET MVC yapısının bize sunduğu ActionFilter yapısı ile dolaylı yoldan bunu başarabiliriz. Action Filter bize aşağıdaki gibi 4 ana interface sunar.

Bu interface’lerden implemente edilmiş Attribute sınıflarından kalıtım alarak kısıtlı da olsa interface’ler üzerinden gelen bilgilerle bazı işlemlerimizi yapabilir, constructor’ı içinde middleware gibi davranmasını sağlayabilir, authorization(yetkilendirme) ve benzeri şeyleri yapabiliriz.

Fonksiyon parametrelerine erişme konusuna gelirsek bu pek mümkün değil ancak Web API yazıyorsak Controller’da bulunan HttpContext’e Action Filter üzerinden erişebilir ancak attribute’a parametre olarak klonlanan verinin tipi ref veya out olmadığı için controller’a gönderilen HttpContext değiştirilemez.

Unutulmamalıdır ki bu yöntem sadece Action Filter’ların kullanılabildiği yerlerde gerçeklenebilir.

Bir tipe bağlı attribute’ları bulmak:

Buraya kadar bir Attribute’u nasıl kullanacağımızı, nasıl kendimiz oluşturabileceğimizi konuştuk peki kimin hangi attribute’a sahip olduğunu nasıl öğreniriz ve buna karşılık neler yapabiliriz isterseniz de biraz bunları konuşalım.

Yazının en başında bahsettiğimiz her alanın içine hangi attribute’lara sahip olduğu sistem tarafından içine gömülür. Doğrudan bir tipin sahip olduğu attribute’ları sorgulayarak kod üzerinde örneğin sınıfları gruplayabilir veya bir nesnenin methodlarına mantıksal işlemler yapabiliriz.

Alanların custom attribute’larını getirmek için MemberInfo.getCustomAttributesData veya MemberInfo.GetCustomAttributes fonksiyonunu kullanmamız yeterlidir. İlk önce System.Reflection türünde olan alanın tipine erişmemiz lazım.

Tipin içinde bulunan GetCustomAttributes fonksiyonuna istediğiniz attribute tipini parametre veya generic olarak vererek arama yapabiliriz.

Attribute’a ulaştıktan sonra constructure ile içine verdiğimiz parametreler gibi normal bir sınıftan erişebileceğiniz her bilgiye erişebiliriz.

Sınıfın custom attribute’larına erişim:

Users adında bir sınıfımız olduğunu varsayalım, tipine iki farklı yöntemde erişip ardından attribute’ları sorgulayabiliriz.

// 1) typeof operatörüyle
System.Reflection.Type sinifTipi = typeof(Users);
// 2) nesnesinin getType methoduyla
Users u = new Users();
sinifTipi = u.getType();
sinifTipi.GetCustomAttributes( aranacak_Attribute_Tipi );
// sinifTipi.GetCustomAttributes< aranacak_Attribute_Tipi >()

Method custom attribute’larına erişim:

Sınıf tipine eriştikten sonra methodlarına oradan da method’un sahip olduğu attribute’lara erişebiliriz.

// Sınıfa ait tüm methodları getirir
System.Reflection.MethodInfo[] methodlar = sinifTipi.GetMethods();
// İstediğimiz methodu seçeriz ve "method" adındaki değişkene atarsak
method.GetCustomAttributes( //... )

Parametrelerin custom attribute’larına erişim:

Bir methoda eriştikten sonra parametrelerine erişebilir ve yukarıda yaptığımız işleri aynı şekilde yapabiliriz.

System.Reflection.ParameterInfo[] parametreler = method.GetParameters();
// ardından istediğimiz parametreyi seçeriz ve
// GetCustomAttributes ile ilgili işlemleri yaparız

Gerçek Dünya Problemleri ve Kullanımı

Attribute’lar hakkında yeterince bilgi sahibi olduk artık gerçek dünyada hem middleware olarak hem de metadata bilgilerini kullanarak nasıl çözümler bulabiliriz onlara bakalım.

1 ) Middleware gibi kullanıp yetkilendirme yapmak:

Yaptığım bir işte kullanıcı bazı end-pointlere sadece kendine ait SSL sertifikası da gönderildiyse kaynağa ulaşılabilmesini istedi. Burada işi middleware kullanarak ve url filtrelemesi yaparak çözebilirdim fakat bu bana Startup.cs ‘te karışık bir kod olmasına mâl olacaktı bu yüzden acaba attribute haline getirip istediğim yerlerde kullanabilir miyim dedim ve şöyle bir kod ortaya çıktı:

Bilgisayarın local’inde bulunan kullanıcıya ait tüm sertifikaları okur, interface sayesinde gelen HttpContext üzerinden kullanıcıdan yolladığı sertifikayı alır ve sistemdekilerle karşılaştırır eğer karşılığı yoksa HttpContext.Result üzerinden hata fırlatır.

2 ) Metadata olarak kullanıp API’ın route haritasını çıkarmak:

Daha önce bahsettiğimiz gibi attribute’lar sayesinde kodun belirli kısımlarına anlam yükleyebiliyoruz. Bu yüklediğimiz anlama göre objelerin içine veri eklemek, silmek, onları filtrelemek, farklı durumlarda fonksiyonları dinamik olarak çalıştırmak gibi onlarca faklı iş yapabiliriz biz ise bu örneğimizde ASP.NET yapısından hakim olduğumuz Controller attribute’larını kullanarak arka planda nasıl oluyor da bir route haritası çıkarılıyor buna bakacağız.

Biz bu örneği konudan sapmamak için sadece bir class için yapacağız fakat kodunuza “controllers” namespace’i altındaki tüm sınıfları da getirip bir döngü ile tüm controller’ları bu işe dahil edebilirisiniz.

Buradaki requestMethod, urlve method değişkenine artık istediğimiz şeyleri yapabiliriz. İstersek konsola basabilir istersek de url ‘i dinleyerek requestMethod tipinde istek geldiğinde method ‘u çalıştırabiliriz.

Kaynakça:

https://stackoverflow.com/questions/2676603/how-do-attribute-classes-work