Kullanıcı Nitelikleri (UDA)
Programdaki her tanıma (yapı türü, sınıf türü, değişken, vs.) nitelikler atanabilir ve bu nitelikler derleme zamanında sorgulanarak programın farklı derlenmesi sağlanabilir. Kullanıcı nitelikleri bütünüyle derleme zamanında etkili olan bir olanaktır.
Nitelikler @
işareti ile belirtilirler ve o niteliğin atanmakta olduğu tanımdan önce yazılırlar. Örneğin, aşağıdaki kod isim
değişkenine ŞifreliKayıt
niteliğini atar:
@ŞifreliKayıt string isim;
Birden fazla nitelik ayrı ayrı belirtilebilecekleri gibi, hepsi birden parantez içinde de belirtilebilirler. Örneğin, aşağıdaki iki satırdaki nitelikler aynı anlamdadır:
@ŞifreliKayıt @RenkliÇıktı string soyad; // ← ayrı ayrı @(ŞifreliKayıt, RenkliÇıktı) string adres; // ← ikisi birden
Nitelikler yalnızca tür isminden oluşabildikleri gibi, nesne veya temel tür değeri de olabilirler. Ancak, anlamları genelde açık olmadığından 42
gibi hazır değerlerin nitelik olarak kullanılması önerilmez:
struct ŞifreliKayıt { } enum Renk { siyah, mavi, kırmızı } struct RenkliÇıktı { Renk renk; } void main() { @ŞifreliKayıt int a; // ← tür ismi @ŞifreliKayıt() int b; // ← nesne @RenkliÇıktı(Renk.mavi) int c; // ← nesne @(42) int d; // ← hazır değer (önerilmez) }
Yukarıdaki a
ve b
değişkenlerinin nitelikleri farklı çeşittendir: a
değişkeni ŞifreliKayıt
türünün kendisi ile, b
değişkeni ise bir ŞifreliKayıt
nesnesi ile nitelendirilmiştir. Bu, niteliklerin derleme zamanında sorgulanmaları açısından önemli bir farktır. Bu farkı aşağıdaki örnek programda göreceğiz.
Niteliklerin ne anlama geldikleri bütünüyle programın ihtiyaçlarına bağlıdır. Nitelikler __traits(getAttributes)
ile derleme zamanında elde edilirler, çeşitli derleme zamanı olanağı ile sorgulanırlar, ve programın uygun biçimde derlenmesi için kullanılırlar.
Aşağıdaki kod belirli bir yapı üyesinin (örneğin, Kişi.isim
) niteliklerinin __traits(getAttributes)
ile nasıl elde edildiğini gösteriyor:
import std.stdio; // ... struct Kişi { @ŞifreliKayıt @RenkliÇıktı(Renk.mavi) string isim; string soyad; @RenkliÇıktı(Renk.kırmızı) string adres; } void main() { foreach (nitelik; __traits(getAttributes, Kişi.isim)) { writeln(nitelik.stringof); } }
Program, Kişi.isim
üyesinin niteliklerini yazdırır:
ŞifreliKayıt RenkliÇıktı(cast(Renk)1)
Kullanıcı niteliklerinden yararlanırken aşağıdaki __traits
ifadeleri de kullanışlıdır:
-
__traits(allMembers)
bir türün (veya modülün) bütün üyelerinistring
türünde döndürür. -
__traits(getMember)
üyelere erişirken kullanılabilen bir isim (symbol) üretir. İlk parametresi bir tür veya değişken ismi, ikinci parametresi ise bir dizgidir. Birinci parametresi ile ikinci parametresini bir nokta ile birleştirir ve yeni bir isim üretir. Örneğin,__traits(getMember, Kişi,
,"isim"
)Kişi.isim
'i oluşturur.
import std.string; // ... void main() { foreach (üyeİsmi; __traits(allMembers, Kişi)) { writef("%5s üyesinin nitelikleri:", üyeİsmi); foreach (nitelik; __traits(getAttributes, __traits(getMember, Kişi, üyeİsmi))) { writef(" %s", nitelik.stringof); } writeln(); } }
Program, bütün üyelerin niteliklerini yazdırır:
isim üyesinin nitelikleri: ŞifreliKayıt RenkliÇıktı(cast(Renk)1) soyad üyesinin nitelikleri: adres üyesinin nitelikleri: RenkliÇıktı(cast(Renk)2)
Kullanıcı nitelikleri konusunda yararlı olan bir başka araç belirli bir ismin belirli bir niteliğe sahip olup olmadığını bildiren std.traits.hasUDA
şablonudur; değeri, nitelik bulunduğunda true
, bulunmadığında ise false
olur. Kişi.isim
'in ŞifreliKayıt
niteliği bulunduğandan aşağıdaki static assert
denetimi başarılı olur:
import std.traits; // ... static assert(hasUDA!(Kişi.isim, ŞifreliKayıt));
hasUDA
bir nitelik türü ile kullanılabileceği gibi, o türün belirli bir değeri ile de kullanılabilir. Aşağıdaki static assert
denetimlerinin ikisi de başarılı olur:
static assert(hasUDA!(Kişi.isim, RenkliÇıktı)); static assert(hasUDA!(Kişi.isim, RenkliÇıktı(Renk.mavi)));
Örnek
Niteliklerin derleme zamanında nasıl sorgulanabildiklerini görmek için bir işlev şablonu tasarlayalım. Bu şablon kendisine verilen yapı nesnesinin bütün üyelerini niteliklerine uygun olarak XML düzeninde yazdırsın
void xmlOlarakYazdır(T)(T nesne) { // ... foreach (üyeİsmi; __traits(allMembers, T)) { // (1) string değer = __traits(getMember, nesne, üyeİsmi).to!string; // (2) static if (hasUDA!(__traits(getMember, T, üyeİsmi),// (3) ŞifreliKayıt)) { değer = değer.şifrelisi.to!string; } writefln(` <%1$s renk="%2$s">%3$s</%1$s>`, üyeİsmi, renkNiteliği!(T, üyeİsmi), değer); // (4) } }
Bu şablonun işaretli bölümleri şöyle açıklanabilir:
- Türün bütün üyeleri
__traits(allMembers)
ile elde ediliyor. - Her üyenin değeri biraz aşağıda kullanılmak üzere
string
türünde elde ediliyor. Örneğin,üyeİsmi
"isim"
olduğunda atama işlecinin sağ tarafınesne.isim.to!string
ifadesidir. - Her üyenin
ŞifreliKayıt
niteliğinin olup olmadığıhasUDA
ile sorgulanıyor ve bu niteliğe sahip olan üyelerin değerleri şifreleniyor. - Biraz aşağıda göreceğimiz
renkNiteliği()
şablonu ile her üyenin renk niteliği elde ediliyor.
renkNiteliği()
işlev şablonu aşağıdaki gibi gerçekleştirilebilir:
Renk renkNiteliği(T, string üye)() { foreach (nitelik; __traits(getAttributes, __traits(getMember, T, üye))) { static if (is (typeof(nitelik) == RenkliÇıktı)) { return nitelik.renk; } } return Renk.siyah; }
Bütün bu olanaklar derleme zamanında işlediğinde xmlOlarakYazdır()
şablonu Kişi
türü için aşağıdaki işlevin eşdeğeri olarak oluşturulur ve derlenir:
/* xmlOlarakYazdır!Kişi işlevinin eşdeğeri */ void xmlOlarakYazdır_Kişi(Kişi nesne) { // ... { string değer = nesne.isim.to!string; değer = değer.şifrelisi.to!string; writefln(` <%1$s renk="%2$s">%3$s</%1$s>`, "isim", Renk.mavi, değer); } { string değer = nesne.soyad.to!string; writefln(` <%1$s renk="%2$s">%3$s</%1$s>`, "soyad", Renk.siyah, değer); } { string değer = nesne.adres.to!string; writefln(` <%1$s renk="%2$s">%3$s</%1$s>`, "adres", Renk.kırmızı, değer); } }
Programda başka açıklamalar da bulunuyor:
import std.stdio; import std.string; import std.algorithm; import std.conv; import std.traits; /* Nitelediği tanımın şifreleneceğini belirler. */ struct ŞifreliKayıt { } enum Renk { siyah, mavi, kırmızı } /* Nitelediği tanımın rengini belirler. Belirtilmediği zaman * siyah renk varsayılır. */ struct RenkliÇıktı { Renk renk; } struct Kişi { /* Bu üyenin şifreleneceği ve mavi renkle yazdırılacağı * belirtiliyor. */ @ŞifreliKayıt @RenkliÇıktı(Renk.mavi) string isim; /* Bu üye için herhangi bir nitelik belirtilmiyor. */ string soyad; /* Bu üyenin kırmızı renkle yazdırılacağı belirtiliyor. */ @RenkliÇıktı(Renk.kırmızı) string adres; } /* Belirtilen üyenin varsa renk niteliğinin değerini, yoksa * Renk.siyah değerini döndürür. */ Renk renkNiteliği(T, string üye)() { auto sonuç = Renk.siyah; foreach (nitelik; __traits(getAttributes, __traits(getMember, T, üye))) { static if (is (typeof(nitelik) == RenkliÇıktı)) { sonuç = nitelik.renk; } } return sonuç; } /* Verilen dizginin Sezar şifresi ile şifrelenmişini * döndürür. (Uyarı: Sezar şifresi çok güçsüz bir şifreleme * algoritmasıdır.) */ auto şifrelisi(string değer) { return değer.map!(a => dchar(a + 1)); } unittest { assert("abcdefghij".şifrelisi.equal("bcdefghijk")); } /* Belirtilen nesneyi niteliklerine uygun olarak XML düzeninde * yazdırır. */ void xmlOlarakYazdır(T)(T nesne) { writefln("<%s>", T.stringof); scope(exit) writefln("</%s>", T.stringof); foreach (üyeİsmi; __traits(allMembers, T)) { string değer = __traits(getMember, nesne, üyeİsmi).to!string; static if (hasUDA!(__traits(getMember, T, üyeİsmi), ŞifreliKayıt)) { değer = değer.şifrelisi.to!string; } writefln(` <%1$s renk="%2$s">%3$s</%1$s>`, üyeİsmi, renkNiteliği!(T, üyeİsmi), değer); } } void main() { auto kişiler = [ Kişi("Doğu", "Doğan", "Diyarbakır"), Kişi("Batı", "Batan", "Balıkesir") ]; foreach (kişi; kişiler) { xmlOlarakYazdır(kişi); } }
Programın çıktısı bütün üyelerin kendi renk niteliklerine sahip olduklarını ve isim
üyesinin de şifrelendiğini gösteriyor:
<Kişi> <isim renk="mavi">EpĠv</isim> ← mavi ve şifreli <soyad renk="siyah">Doğan</soyad> <adres renk="kırmızı">Diyarbakır</adres> ← kırmızı </Kişi> <Kişi> <isim renk="mavi">CbuIJ</isim> ← mavi ve şifreli <soyad renk="siyah">Batan</soyad> <adres renk="kırmızı">Balıkesir</adres> ← kırmızı </Kişi>
Kullanıcı niteliklerinin yararı
Kullanıcı niteliklerinin yararı, niteliklerin programın başka bir yerinde değişiklik gerekmeden değiştirilebilmesidir. Örneğin, bütün üyelerin şifreli olarak yazdırılması için Kişi
yapısının aşağıdaki gibi değiştirilmesi yeterlidir:
struct Kişi { @ŞifreliKayıt { string isim; string soyad; string adres; } } // ... xmlOlarakYazdır(Kişi("Güney", "Gün", "Giresun"));
Çıktısı:
<Kişi> <isim renk="siyah">Hýofz</isim> ← şifreli <soyad renk="siyah">Hýo</soyad> ← şifreli <adres renk="siyah">Hjsftvo</adres> ← şifreli </Kişi>
Hatta, xmlOlarakYazdır()
ve yararlandığı nitelikler başka türlerle de kullanılabilir:
struct Veri { @RenkliÇıktı(Renk.mavi) string mesaj; } // ... xmlOlarakYazdır(Veri("merhaba dünya"));
Çıktısı:
<Veri>
<mesaj renk="mavi">merhaba dünya</mesaj> ← mavi
</Veri>
Özet
- Programda kullanılan bütün tanımlara nitelikler atanabilir.
- Nitelikler tür isimlerinden veya değerlerden oluşabilir.
- Nitelikler derleme zamanında
hasUDA
ve__traits(getAttributes)
ile sorgulanarak programın farklı derlenmesi sağlanabilir.