C# 13 İle Gelen Yenilik: ref struct Interfaces & allow ref struct
Merhaba,
Bu içeriğimizde C# 13 ile gelen ref struct interface ve allow ref struct özelliklerini inceliyor olacağız.
ref struct, performans açısından kritik uygulamalarda kullanılan ve struct‘ın bir türevi olan özel bir veri yapısıdır. Her ne kadar struct gibi ref struct‘da adından da anlaşılacağı üzere value type bir yapıya sahip olsa da farklı kullanım durumları için tasarlandıkları için farklı özellikler ve kısıtlamalarla gelmektedirler.
Misal olarak aşağıdaki kod bloğunu ele alırsak eğer;
ref struct MyStruct
{
public int x;
public int y;
}
burada ref keyword’ü ile bu struct‘ın yalnızca stack’te tutulabileceğini, herhangi bir şekilde heap’e gönderilemeyeceğini ifade etmiş oluyoruz. Yani bir başka deyişle, bu türden değerleri object olarak saklayamıyoruz. Haliyle bu bağlamda boxing işlemine tabi tutamıyor, async metotlarda kullanamıyor ve herhangi bir interface ile temsil edemiyoruz.
Temel ilke: Yalnızca stack’te yaşa, heap’e sakın kaçma!
Boxing’e tabi tutamıyoruz, çünkü, bu işlem neticesinde ilgili veri heap’e taşınacaktır. Ee ref struct olan bir veri heap’te olamayacağına göre haliyle boxing’e tabi tutulabilmesi mümkün değildir!
Zaten yukarıdaki görselden de görüldüğü üzere normal struct tanımında compiler hatası söz konusu değilken ref struct olduğu taktirde compiler hemen uyarı vermektedir.
async metotlarda kullanamıyoruz, çünkü, async metotlar heap’te yaşayan state machine’e çevrilmektedirler. Bu ne demek? diye sorabilirsiniz, hemen izah etmemiz gerekirse eğer… Evet, async tanımlanmış bir metot compiler tarafından adım adım çalıştırılabilmek için arka planda bir state machine’e dönüştürülmektedir. Tabi bu state machine’e dönüştürülme süreci, async metodun içerisindeki await keyword’leri eşliğinde gerçekleştirilir ve her await yeni bir durum‘a karşılık gelmektedir. Her bir durum işlendikten sonra kodun kaldığı yerden devam edebilmesi için her durumun state’ini tutan state machine gerekli son bilgiyi vermekte ve duruma dair işlendi işaretinde bulunarak akabinde bir sonraki durumu işleme koymaya geçmektedir. İşte bu süreçte kullandığımız state machine heap’te tutulacağından dolayı async metotlarda ref struct‘ın kullanılması pek mümkün değildir!
async Task XAsync()
{
var mystruct = new MyStruct();
Console.WriteLine(mystruct.x);
}
Tabi burada dikkat edilmesi gereken şöyle bir husus vardır. Yukarıdaki paragrafta onca anlatılan duruma karşın üstteki örnekte ref struct olarak tanımlanmış olan MyStruct‘ın rahatlıkla kullanılabildiğini göreceksiniz. Ee hani la ne oldu? dediğinizi duyar gibiyim… Tamam, sakin 🙂 onca edebiyata karşın bu kodu yazdığınızda derleyici hatası söz konusu olmayacaktır, çünkü esasında async metotlarla ilgili bahsedilen bu durum, C# 13 ve sonrasında, async metot içerisinde herhangi bir await keyword’üyle bölümleme gerçekleştirilmediği ve state machine oluşturulmadığı taktirde hata meydana gelmeyecek şekilde güncellenmiştir.
Yani demek istediğim, bu kodu aşağıdaki gibi await kullanılabilir hale getirirsek, await’ten önce tanımlanan her değişken oluşturulacak state machine içinde heap’e taşınacağından dolayı ancak bu taktirde bizleri derleyici hatası karşılıyor olacaktır.
async Task XAsync()
{
var mystruct = new MyStruct();
await Task.Delay(1000);
Console.WriteLine(mystruct.x);
}
Yani tekrar izah etmemiz gerekirse eğer buradaki mystruct değişkeni, async metodu içerisinde await keyword’ünden sonra kullanıldığı için state machine’in bir parçası haline getirilecek ve böylece heap’e taşınmak zorunda kalınacaktır. Ee bu durumda da derleyici buna izin vermeyecektir.
Buradan yola çıkarak şöyle bir özette bulunabiliriz ki, ref struct ile tanımlanmış bir veri esasında async metotlarda kullanılabilmektedir. Lakin await keyword’ünden önce tanımlanıp sonrasında kullanılmaya çalışılırsa işte o taktirde derleyici hatasıyla karşılaşılacaktır. Haliyle aşağıdaki durumlarda derleme hatası meydana gelmeyecektir;
async Task XAsync()
{
await Task.Delay(1000);
var mystruct = new MyStruct();
Console.WriteLine(mystruct.x);
}
ya da
async Task XAsync()
{
var mystruct = new MyStruct();
Console.WriteLine(mystruct.x);
await Task.Delay(1000);
}
Ve son olarak herhangi bir interface ile temsil edemiyoruz, çünkü, C#’ta bir struct’ı interface ile implement edip polimorfizm kuralları gereği o interface’in arayüzüyle temsil edersek o struct değeri heap’e almış yani yine bir boxing işlemine tabi tutmuş oluyoruz.
Misal olarak ilgili ref struct‘ın aşağıdaki gibi bir interface ile implementasyonu gerçekleştirilse,
ref struct MyStruct : IInterface
{
public int x;
public int y;
}
interface IInterface
{
}
aşağıdaki ekran görüntüsünde olduğu gibi bu veriyi interface ile referans ettiğimiz taktirde derleyici hatası alınacaktır.
Velhasıl kelam… .NET mimarisinde ref struct‘a en bilinen örnek olarak Span<T> ve ReadOnlySpan<T> türleri verilebilir.
Bu türler üzerinde de olduğu gibi ref struct, performans artırıcı özellik olarak tercih edilmektedir. Ayrıca compiler ref struct sayesinde, Garbage Collector (GC) tarafından kontrol edilemeyen, genellikle pointer’larla erişilebilen ve doğrudan bellekle etkileşen veriler olan unmanaged verileri heap’e almayarak hayati bir önlem almaktadır. Çünkü bu tarz veriler heap’e alınırsa erişim kontrolsüz şekilde uzatılmış ya da veri yine erişilebilir olsa da esasında geçersiz hale getirilmiş olabilir. Buradaki durumu bir örnek üzerinden izah etmemiz gerekirse şu benzetmeyi kullanabiliriz; diyelim ki bir kağıda herhangi bir fırının adresini yazdık. Bu adres bizim için bir pointer’dır. Fırın yıkılsa ya da taşınsa kağıttaki adres bundan etkilenmeyecek amma velakin biri o adrese giderse bomboş bir neticeyle ya da farklı bir şeyle karşılaşacaktır. İşte bu tarz bir riski C#’ta yönetebilmek için ref struct olarak tanımlanmış olan değerler heap’e taşınmaya çalışıldığında derleyici seviyesinde uyarı verilecektir.
allow ref struct
C# 13’ten önce ref struct tipleri generic tiplerde kullanılamıyordu. C# 13 ile birlikte bu davranışta desteklenmekte ve artık generic türler ref struct olarak işaretlenebilmektedir. Böylece artık generic türlerde ref struct türlere izin verilebilmektedir.
class MyClass<T> where T : allows ref struct
{
}
Nihai olarak;
ref struct özelliği sayesinde güvenli bir bağlamda struct’lar üzerinde pointer’ların avantajlarını sunmuş ve lüzumsuz yere bellek tahsisini önlemiş bulunuyoruz. Böylece C# ekibi için yine dilin cürmüne yakışır kritik bir yenilik ve geliştirmede bulunduklarını söyleyebiliriz.
İlgilenenlerin faydalanması dileğiyle…
Sonraki yazılarımda görüşmek üzere…
İyi çalışmalar…
