D Programlama Dili – Programlama dersleri ve D referansı
Ali Çehreli

nitelik: [property, attribute], bir türün veya nesnenin bir özelliği
... bütün sözlük



İngilizce Kaynaklar


Diğer




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:

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:

  1. Türün bütün üyeleri __traits(allMembers) ile elde ediliyor.
  2. 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.
  3. Her üyenin ŞifreliKayıt niteliğinin olup olmadığı hasUDA ile sorgulanıyor ve bu niteliğe sahip olan üyelerin değerleri şifreleniyor.
  4. 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