Derinlemesine yazılım eğitimleri için kanalımı takip edebilirsiniz...

C# 11 – Interface’ler de Static Abstract Members Tanımlama Özelliği

Merhaba,

Bu içeriğimizde C# 11 ile gelmesi beklenen ve static interface member’larının abstract olarak bildirilmesini sağlayacak olan Static Abstract Members In Interfaces özelliğini değerlendiriyor olacağız.

Herşeyden önce temelden konuyu ele alalım ve static interface member’larının neler olduğunu tekrardan hatırlayarak konuya girizgâh yapalım.

Static Interface Member’ları Nelerdir?

Biliyorsunuz ki interface’ler; class’lara klavuzluk eden bir başka deyişle class’ların imzalarını temsil eden yapılar. Interface içerisinde tanımlı olan tüm imzalar bu interface’leri uygulayan(implement eden) class’lar içerisinde oluşturulacağı taahhütü verilmekte ve böylece interface’ler aracılığıyla class’lara bir sözleşme misali davranış sergilenmektedir.

Basit olarak bir interface tanımı ve implementasyonu aşağıdaki gibidir.

interface IWorker
{
    void DoWork();
}
class Work : IWorker
{
    public void DoWork()
    {
        .
        .
        .
    }
}

Evet, zaten interface’ler hakkında bu temel bilgileri bildiğinizi biliyorum. Static interface member’a gelirsek, hatırlarsanız eğer C# 8.0’da Default Implementations In Interfaces isimlendirmesi eşliğinde interface’ler içerisinde imzaların gövdelerini tanımlayabileceğimiz bir özellik gelmişti. İşte bu mevzu bahis özelliğin iki satır önce referans edilen makalesine göz atarsanız eğer interface içerisinde default implemantasyon yapılan member’lar bir yandan da static olarak işaretlenebilmekte ve böylece static bellek üzerinden de bir erişim sağlanabilmektedir.

interface IWorker
{
    static void DoWork()
    {
        .
        .
        .
    }
}

Bakınız, interface içerisinde bir elemanın static olabilmesi için default implementation olması gerekmektedir.
C# 11 - Interface'ler de Static Abstract Members Tanımlama ÖzelliğiBu önemli! Aksi taktirde yandaki görselde görüldüğü üzere interface içerisindeki normal bir imzayı static yapmaya çalışırsak eğer compiler hata verecektir. Bu kullanım sade ve sadece default implementation durumu varsa söz konusu olabilmektedir.

Şimdi, C#’ta interface’ler ve default implementation hakkında bilgilerimizi hasbel kader tazelediğimize göre yavaştan static abstract in interfaces özelliğini derinleştirmeye başlayabiliriz.

Static Abstract Members In Interfaces Özelliği Nedir?

Normal bir interface içerisinde tanımlanmış olan herhangi bir member imzası özü itibariyle dolaylı olarak abstract‘tır. Benzer şekilde yukarıda bahsedildiği gibi default implementation söz konusu ise bu member’da fıtratı gereği virtual olarak yer edecektir.

Bunu anlayabilmek için aşağıdaki örnek kod bloğunu bu açıklama üzerinden ele alırsak eğer;

interface IWorker
{
    void DoWork();
    public void DoWork2()
    {
        .
        .
        .
    }
}

‘DoWork’ imzası normal olduğu için özü itibariyle abstract iken, ‘DoWork2’ imzası ise default implementation olduğu için virtual bir fıtrata sahiptir. Evet, C# programlama dili açısından bu member’lar yapılarına göre compiler açısından bu şekilde değerlendirilmektedirler. İsteğe bağlı bir şekilde bu member’lara uygun keyword’leri de aşağıdaki gibi fiziksel olarak yerleştirerek tanımlamalarda da bulunabilirsiniz.

interface IWorker
{
    abstract void DoWork();
    virtual public void DoWork2()
    {
        Console.WriteLine("");
    }
}

Eğer ki, default implementation olan member’ların özü itibariyle virtual olmalarını istemiyorsanız direkt olarak aşağıdaki gibi sealed keyword’ü ile işaretleyebilirsiniz. Ama bu işlemin sadece default implementation olan member’lar için geçerli olduğunu unutmayınız. Normal bir imzayı ilgili keyword’le işaretlemeniz compiler hatası almanıza sebebiyet verecektir.

Normal şartlarda static olarak işaretlenmiş default implementation member’lara gelirsek eğer onlar özü itibariyle ne abstract‘tır ne virtual‘dır ne de sealed ile işaretlenebilir! Lakin C# 11 ile artık static interface member’ları abstract keywordü ile işaretlenebilmekte ve bu belirli bir davranış sergilenmesine sebebiyet vermektedir. Şimdi gelin bu davranışın ne olduğunu ve nerelerde kullanabileceğimiz inceleyelim…

Aşağıdaki kod bloğuna göz atarsanız static abstract member örneklerini default implementation olan static member’lar ile birlikte göreceksiniz:

interface IX
{
    //Static Abstract Member Method
    static abstract void A();
    //Static Abstract Member Method
    abstract static int B();
    //Static Abstract Member Property
    static abstract int C { get; }
    //Static Abstract Member Property
    static abstract int D { get; set; }

    //Static Member Method
    static void E() { }
    //Static Member Property
    static string F { get; }
    //Static Member Property
    static string G { get { return ""; } }
    //Static Member Field
    static string H;
}

Dikkat ederseniz ‘A’, ‘B’, ‘C’ ve ‘D’ imzaları static abstract member olarak tanımlanmışken, ‘E’, ‘F’, ‘G’ ve ‘H’ member’ları ise static member olarak tanımlanmıştır. Compiler, abstract keywordü ile işaretlenmiş tüm static member’ların gövdelerinin oluşturulmasına izin vermezken, sadece static member’ların metot olduğu sürece gövdelerini zorunlu kılmakta ve property ise opsiyonel bırakmaktadır. Biliyorum, bu katı kuralları aklımızda tutmak belki imkansıza yakın bir durum olabilir ama nacizane olarak bilinmesinde fayda görmekteyim (ne yalan söyleyim, yarım saat sonra bende unutabilirim) 🙂 Ama şunu söylemek gerekmektedir ki, yukarıdaki örnek kod bloğunda ‘A’, ‘B’, ‘C’ ve ‘D’ imzalarının C# 11 versiyonu ile kullanılabilmesi mümkündür.

Peki hoca, nedir bunlar? dediğinizi duyar gibiyim… Static abstract members özelliğinin ne olduğunu anlayabilmek için yukarıdaki örnekteki tüm tanımları tek tek tetikleyerek yorumlamaya çalışırsak eğer;
C# 11 - Interface'ler de Static Abstract Members Tanımlama Özelliği
Yandaki görselde görüldüğü üzere static member’lara interface referansı üzerinden gayet rahat erişebilirken, static abstract member’lara ise interface referansı üzerinden erişebilsek dahi compiler hata vermekte, demek ki farklı bir kullanım beklemektedir.

Burada compiler’ın verdiği hataya göz attığımızda aşağıdaki mesajı görmekteyiz:
C# 11 - Interface'ler de Static Abstract Members Tanımlama ÖzelliğiMesajın metinsel hali;

A static abstract interface member can be accessed only on a type parameter.

Hata mesajından anladığımız kadarıyla static abstract member’lara yalnızca bir tür parametresinden erişebiliyormuşuz. Yani bu iki anlama gelebilir. Birincisi, static abstract member’ların bulunduğu interface’i implemente eden bir sınıfın türünden bahsediyor olabilir ya da generic parametre türlerinden. Öyle değil mi?

Şimdi gelin ikisini de deneyip, gözlemleyelim.

İlk olarak interface’i implemente eden sınıfı inceleyelim.

class X : IX
{
    public static int C => 999;
    static int _d;
    public static int D { get => _d; set => _d = value; }
    public static void A() => Console.WriteLine();
    public static int B() => 999;
}

Görüldüğü üzere içerisinde static member’larla birlikte static abstract member’ları barındıran ‘IX’ interface’ini ‘X’ class’ına implement ettiğimizde sadece static abstract member’ların gövdeleri oluşturulmuştur. Çünkü, yukarıdaki satırlarda static abstract member’ların gövdeleri interface’de oluşturulmaz demiştik. Haliyle interface’lerdeki static yapıların imzaları implement’e edilecek sınıflarda fiziksel olarak oluşturulsun istiyorsak static abstract ile işaretlenmeleri gerektiğini öğrenmiş oluyoruz.

Haliyle aşağıdaki şekilde bir kullanım neticesinde ‘X’ referansı üzerinden bu static member’lara rahatlıkla erişim gösterebiliyoruz.C# 11 - Interface'ler de Static Abstract Members Tanımlama ÖzelliğiEvet, bildiğiniz class’lar üzerinden static member talebinde bulunmuş olduk. Ama netice itibariyle bir static member’ı interface aracılığıyla herhangi bir sınıfa zorunlu kılmak için static abstract keywordü eşliğinde interface’de tanımlamamız gerektiğini görmüş olduk.

Şimdi ise ikinci olarak, static abstract member’ların daha elverişli bir şekilde ağırlıklı kullanım alanı olabilecek generic type parametrelerindeki davranışlarını incelemeye geçebiliriz.

Yukarıda tanımladığımız interface’i aşağıdaki gibi generic bir class’a base class constraint olarak tanımlayalım.

class X<T> where T : IX
{

}

Mademki, static abstract member’lara yalnızca bir tür parametresinden erişebiliyoruz o halde bu generic ‘T’ parametresi de bize ilgili memberları getirecektir anlamına geliyor.

class X<T> where T : IX
{
    public X()
    {
        T.A();
        T.B();
        var c = T.C;
        var d = T.D;
    }
}

Ki öyle de olmaktadır. Nihayetinde ‘T’ parametresi artık bir ‘IX’ olduğundan dolayı ve ‘IX’de static abstract olarak tanımlanmış olan member’larda olduğundan dolayı ‘T’ üzerinden ilgili member’lara erişim gösterebilmekteyiz.C# 11 - Interface'ler de Static Abstract Members Tanımlama ÖzelliğiVe dikkat ederseniz eğer generic type ‘T’ parametresinden static abstract member’ların dışında static member’lara erişim gösterilememektedir.

Velhasıl, yukarıdaki ‘X<T>’ sınıfını kullanırken ‘T’ parametresine vereceğimiz concrete sınıf ‘IX’den türeyeceği için haliyle ‘IX’ içerisindeki static abstract member’ları implement etmiş bir sınıf olacağından ‘T’ üzerinden çağrılan static member’lar bu sınıftakiler olacaktır.

Misal olarak aşağıdaki ‘Y’ sınıfını;

class Y : IX
{
    public static int C => 999;
    static int _d;
    public static int D { get => _d; set => _d = value; }
    public static void A() => Console.WriteLine();
    public static int B() => 999;
}

aşağıdaki gibi ‘X<T>’ generic sınıfı ile kullanırsak;

X<Y> x = new();

‘X<T>’ sınıfı içerisinde constructor üzerinden ‘T.A()’, ‘T.B()’ vs. şeklinde tetiklenen member’lar ‘Y’ sınıfı üzerindeki static member’lar olacaktır.

Genel Kültür
Ayrıca C# 11 ile static olarak işaretlenmiş member’ların sealed keyword’ü ile işaretlenmesi de mümkündür.

Peki hoca, static abstract member’lara sahip olan bir interface’i explicit implementation nasıl yapabiliriz?
Static abstract member’ların explicit olarak implemente edilebilmeleri için ekstradan bir syntax belirlenmemiştir. Direkt olarak ilgili imzanın explicit tanımlamasına static keywordü eşliğinde bildirimde bulunulması amaca hizmet edecektir.

interface IX
{
    static abstract void X();
    static abstract void Y();
}
class X : IX
{
    static void IX.X()
    {
        .
        .
        .
    }

    static void IX.Y()
    {
        .
        .
        .
    }
}

Static Abstract Operatörlerin Generic Parametrelerdeki Rolü

Static abstract member’lar özelliği sayesinde interface’ler de operatör imzaları tanımlayarak hedef class’lara zoraki olarak operator overloading işlemi uygulanabilmektedir. Ayrıca bu operatörler generic parametreler üzerinden de faaliyet gösterebilmektedirler.

interface IX
{
    int Value { get; }
    static abstract int operator +(IX x);
    static abstract int operator -(IX x);
    static abstract int operator +(IX x, IX x2);
    static abstract int operator -(IX x, IX x2);
    static abstract long operator *(IX x, IX x2);
    static abstract decimal operator /(IX x, IX x2);
    static abstract int operator +(IX x, A a);
    static abstract int operator *(IX x, B b);
}
class A { public int a; }
class B { public int b; }

Yukarıdaki kod bloğuna göz atarsanız eğer birçok operatör imzası implementasyon sürecinde ilgili class’a zoraki uygulatılmak için tanımlanmıştır. Eğer ki bu interface’i uygulamaya kalkarsanız aşağıdaki gibi explicit olarak gerçekleştirebilirsiniz.

class X : IX
{
    public int Value => 5;
    static int IX.operator +(IX x) => x.Value + 1;
    static int IX.operator +(IX x, IX x2) => x.Value + x2.Value;
    static int IX.operator +(IX x, A a) => x.Value + a.a;
    static int IX.operator -(IX x) => x.Value - 1;
    static int IX.operator -(IX x, IX x2) => x.Value - x2.Value;
    static long IX.operator *(IX x, IX x2) => x.Value * x2.Value;
    static int IX.operator *(IX x, B b) => x.Value * b.b;
    static decimal IX.operator /(IX x, IX x2) => x.Value / x2.Value;
}

Biz artık biliyoruz ki, static abstract olan bir member’a ancak ve ancak tür tipi(referance type) üzerinden (.)nokta diyerek erişim gösterilebilmektedir. Halbuki bizler burada static abstract operatör imzaları tanımlamış bulunuyoruz ve bu imzalar verilen türlerdeki nesneler karşılığında davranış sergileyecek fonksiyonalitelere sahiptirler. Yani operatörler nesne olmadan bir anlam ifade etmemektedirler. O halde bu tanımlamanın ne anlamı var hoca? diye muhtemel haklı sorunuza karşılık konuyla ilişkili başlıkta da belirttiğim gibi generic parametreleri sunabilirim. Evet, static abstract olarak tanımlanan operatörlerin implement edildikleri sınıflar generic parametrelere base class olarak verildiği taktirde işlevsellik kazanarak geliştiricilere etkili manevralar yapma olanağı tanımaktadırlar.

Şöyle ki;

class Y<T> where T : IX, new()
{
    public Y()
    {
        T t1 = new();
        T t2 = new();

        var result1 = +t1;
        var result2 = t1 + t2;
        var result3 = t1 + new A();
        var result4 = -t1 ;
        var result5 = t1 - t2;
        var result6 = t1 * t2;
        var result7 = t1 * new B();
        var result8 = t1 / t2;
    }
}

Yukarıdaki kod bloğunu incelerseniz generic ‘Y’ isimli bir sınıf oluşturulmuştur ve ‘T’ parametresine new constraint ile birlikte ‘IX’ türünden base class constraint uygulanmıştır. Haliyle ‘T’ bir yandan tür/type olduğu için artık ‘T’ türünden olan tüm instance’lar da ‘IX’de static abstract olarak tanımlanmış operatörler 8 ile 15. satır aralığında olduğu gibi devreye girecektirler.

Harika değil mi? 🙂

Peki hoca! Bu operatörler işlevselliklerini nereden bilecekler? diye sorunuzu duyar gibiyim. El-cevap; ‘T’ parametresine verilecek olan referansın, base class constraint sayesinde ‘IX’den türeyen bir tür olacağının garantisi verilmektedir. Böylece ilgili referansın içerisine uygulanmış operatörlerin işlevsellikleri buradaki çalışmada geçerli olacaktır. Dolayısıyla bizler bu ‘T’ parametresine yukarıda oluşturduğumuz ve ‘IX’i implement ettiğimiz ‘X’ sınıfını verebiliriz.

Y<X> y = new();

Böylece ‘Y’ sınıfı oluşturulduğu an örnek amaçlı çalışmaların yapıldığı constructor’ında ‘X’ nesnesi üzerinden operatörler tetiklenecektir. Zaten bu yüzden implementasyon sürecinde ‘IX’den gelen operatorler explicit olarak ‘X’e uygulanmaktadır.

Ayrıca unutmadan explicit veya implicit operator overloading gerçekleştirmek adına interface’de imza tanımlayabilmek için aşağıdaki gibi davranış sergileyebilirsiniz.

interface IX<T> where T : IX<T>
{
    static abstract implicit operator string(T t);
    static abstract implicit operator T(string s);
    static abstract explicit operator int(T t);
    static abstract explicit operator T(int i);
    static abstract explicit operator A(T t);
    static abstract explicit operator T(A a);
}
class A { }

Bu yapının implementasyonu ise aşağıdaki gibi olacaktır. (Not : Visual Studio editörü explicit ve implicit operatörlerin imzalarını otomatik implement etmediği için imzalara uygun operatörleri manuel yazmak mecburiyetinde kalabilirsiniz. Ben kaldım :))

class X : IX<X>
{
    static implicit IX<X>.operator string(X x) => "";
    static implicit IX<X>.operator X(string s) => new();
    static explicit IX<X>.operator int(X x) => 0;
    static explicit IX<X>.operator X(int i) => new();
    static explicit IX<X>.operator A(X t) => new();
    static explicit IX<X>.operator X(A a) => new();
}

Ve netice itibariyle bu yapıyı kullanacak sınıfı da aşağıdaki gibi inşa edip, ardından örneklendirelim.

class Y<T> where T : IX<T>, new()
{
    public Y()
    {
        T t = new();

        var result1 = (string)t;
        var result2 = (T)"";
        var result3 = (int)t;
        var result4 = (T)0;
        var result5 = (A)t;
        var result6 = (T)(new A());
    }
}
Y<X> y = new();

İşte bu kadar 🙂

Static Abstract Members Özelliği Genellikle Nerelerde Kullanılır?

Static Abstract Members özelliği genellikle generic parametrelerdeki bilinmezlik durumu için gayet elverişlidir diyebiliriz. Misal olarak, aritmetik işlemler için System.Runtime.Experimental namespace’i altındaki INumber<T> interface’ini inceleyebilirsiniz.

static class Math
{
    static public void SumOrMultiplied<T>(T n1, T n2) where T : INumber<T>
    {
        var sumResult = (n1 + n2) + T.AdditiveIdentity;
        var multiplyingResult = (n1 * n2) * T.MultiplicativeIdentity;
    }
}

Bu interface sayesinde sayısal değerlerin atanamadığı ‘T’ parametresi üzerinden static abstract olarak tanımlanmış imzalar sayesinde çarpma ve toplama gibi işlemler için etkisiz elemanlara erişebiliyoruz yahut aritmetik operatörler eşliğinde matematiksel işlemleri generic boyuta taşıyarak daha evrensel hareket edebiliyoruz.

Yani anlayacağınız generic yapılarda parametre üzerinden static değerlere erişmemiz gereken her durum için static abstract özelliği artık imdadımıza yetişmektedir.

Şimdi son olarak interface’ler de ki static abstract member’larla static member’lar arasındaki farkı özet mahiyetinde ayrıştırarak içeriğimizi noktalayalım.

Static Members Static Abstract Members
Tanımlandığı interface’i uygulayan sınıfa implement edilmesini zorunlu KILMAZ! Tanımlandığı interface’i uygulayan sınıfa implement edilmesini zorunlu KILAR!
Generic parametre üzerinden ERİŞİLEMEZ! Generic parametre üzerinden ERİŞİLEBİLİR!
Interface üzerinden static olarak ERİŞİLEBİLİR! Interface üzerinden static olarak ERİŞİLEMEZ! (Not : Interface referansı üzerinde (.)nokta dediğinizde static abstract member’ın geldiğini görürsünüz ama kullanamazsınız! Hata verir.)

Nihai olarak şunu görebiliyoruz ki, artık generic yapılardaki parametreler, içlerinde static abstract imzalar barındıran bir interface türünden base class constraint’e sahip oldukları taktirde, bu parametrelerin static member’lara erişebilmesi, tetikleyebilmesi ve tanımlanmış operatörlere tabi tutulabilmesi Static Abstract Members in Interfaces özelliği sayesinde mümkün hale gelmiştir. Kutlu olsun 🙂

İlgilenenlerin faydalanması dileğiyle…
Sonraki yazılarımda görüşmek üzere…
İyi çalışmalar…

Bunlar da hoşunuza gidebilir...

Bir cevap yazın

E-posta hesabınız yayımlanmayacak.