C# 9.0 – Static Anonymous Functions
Merhaba,
Nasıl ki, beşeri bir dilin keskinliği, o dilin belagat ve fesahata olan yatkınlığına, belagat ve fesahata olan yatkınlığı ise ilgili dilin tarihsel dokusuna yani bir başka deyişle temas ettiği insanlık tecrübesine bağlı, benzer mantıkla çağımızın soyut zihinleri olan yazılımların kendi dünyalarındaki dillerinin gücü; kaynaklar üzerindeki hakimiyet alanlarına, hakimiyet alanları ise dilin temas edebildiği nazik noktalara bağlıdır. Buradaki metafordan yola çıkarak, ister beşeri olsun ister suni, bir dilin geleceğe hitap edecek olan tüm kelamlara sahip olabildiğini gösteren en büyük delili, davranışının her bir noktasına müdahale edilebilirlik derecesidir diyebiliriz. İşte C#, 9.0 versiyonuyla programlama dünyasında geleceğe göz kırpmakta ve gücünü, hakimiyetini ve temas edebildiği noktaları bir tık daha arttırarak yeni bir özellik ile günümüze dokunmaktadır.
Peki nedir bu özellik?
Klasik bir C# kodunu incelediğinizde yüksek ihtimal lambda expression‘ların yoğun bir şekilde kullanıldığını görmeniz kuvvetle muhtemel. Lambda ifadeleri yapısal olarak bir temsilci tarafından işaretlenmesi gereken metodun boilerplate(basmakalıp) olarak imzasının ve gövdesinin tanımlanmasından biz developer’ı kurtaran ve hızlı bir şekilde ilgili fonksiyonu oluşturarak, daha düzgün ve estetik bir şekilde kodun geliştirilmesini sağlayan yapılardır. Lakin bu şekilde developer açısından büyük avantajı olan bu lambda ifadelerinin esasında bellek optimizasyonu açısından oldukça maliyetli olabileceklerini hiç düşündünüz mü?.
Nasıl yani?
Şöyle ki, LINQ sorgularını kullanırken yahut farklı bir thread’de operasyonları ele almaya çalışırken sık sık başvurduğumuz lambda expression’lar kullanım durumlarına göre ister istemez bellekte lüzumsuz maliyetlere sebebiyet verebilmektedirler. Bu durumu aşağıdaki kod üzerinden örneklendirelim;
class Program { static void Main(string[] args) { int a = 5; Action action = () => { a *= 5; }; action.Invoke(); } }
Yukarıdaki kod bloğunu incelerseniz eğer, bir adet ‘Action’ delegasyonu ile temsil edilen lambda fonksiyonu ve bu fonksiyon içerisinde kullanılan ‘Main’ içerisinde tanımlanmış olan ‘int’ değişkenini görmektesiniz.
Eee hoca, bunun neresinde maliyet?
Görünürde maliyet yokmuş gibi bir durum olsa da, esasında bu kodu derlediğimizde maliyet ortaya çıkmaktadır. Derleme neticesinde, compiler ‘closure‘ isminde bir sınıf oluşturacak ve bu sınıf üzerinde ilgili lambda fonksiyonunu normal metot olarak ekleyecektir. Bir yandan da bu metot içerisinde erişilecek olan değişkeni, söz konusu sınıfa field olarak ekleyecektir. Tabi gerçekleştirilmesi gereken operasyonu sağlayabilmek için ilgili sınıftan bir nesne üretecek ve field’a değeri ne ise(5) verecektir. Haliyle compile sürecinde bu şekilde bir davranış sergileyecek olan yukarıdaki lambda fonksiyonunu, herhangi bir döngü yahut farklı şekillerle birden fazla kez tetiklemiş olsaydık, tetikleme sayısı kadar nesne oluşturulacağı için bu durum Garbage Collector açısından pek iç açıcı olmayacaktır. İşte maliyette tam da buradadır!
Yukarıdaki yapılan operasyonu aşağıdaki gibi ele alırsak:
class Program { static void Main(string[] args) { Action action = () => { int a = 5; a *= 5; }; action.Invoke(); } }
Böyle bir durumda ise, tanımlanan lambda expression tarafından dışarıdaki herhangi bir değişkene erişim gerçekleştirilmediğinden dolayı üretilecek olan ‘closure‘ isimli sınıfın içerisinde ilgili değişken bir static field olarak eklenecektir ve böylece ilgili kod her tetiklendiğinde esasında bellekte(statik) tutulan aynı değer döndürülecektir. Böylece önceki senaryoya istinaden bellek optimizasyonu açısından arada bayağı bir fark olduğunu söyleyebiliriz… Öyle değil mi?
Günlük hayatta lambda ifadelerini sıkça kullandığımız LINQ fonksiyonlarında büyük oranda dışarıdan alınan parametrelerle işlevler gerçekleştirilmektedir. Bu duruma bellek optimizasyonu açısından müdahale etme şansımız pek bulunmamakta ve hatta işin kolaylığından ötürü getirisinden dolayı bu maliyet göz ardı edilmektedir.
Peki hoca! Lambda expression’larda ki bu lüzumsuz alan tahsisini önlemenin yolu yok mu?
Tabi ki de kısmi de olsa bir optimizasyon şansı söz konusudur diyebiliriz. Bunun için lambda içerisinde kullanılacak ilgili değişkenleri lambda’ya parametre olarak aktarmak yeterli olacaktır.
Şöyle ki;
class Program { static void Main(string[] args) { MyClass myClass = new MyClass(); Action action = () => { myClass.MyProperty *= 5; }; action.Invoke(); } } class MyClass { public int MyProperty { get; set; } }
Konuyu daha da zenginleştirmek için yukarıdaki farklı örneği ele alırsak, lambda expression dışında tanımlanmış olan bir property’e erişim sağlandığından dolayı bu durum lüzumsuz tahsise neden oluyordu. Bu kodu aşağıdaki gibi geliştirirsek eğer;
class Program { static void Main(string[] args) { MyClass myClass = new MyClass(); Action<MyClass> action = obj => { obj.MyProperty *= 5; }; action.Invoke(myClass); } } class MyClass { public int MyProperty { get; set; } }
Görüldüğü üzere ilgili lambda ifade dışarıdan herhangi bir değişkene erişmeye çalışmadığından ekstradan bir bellek tahsisi yapılmamakta ve daha optimize bir kod inşa edilmiş olmaktadır.
Tabi burada ‘MyClass’ türüne bağlı olmaktansa daha evrensel bir şekilde bu kodu geliştirmek isteyebiliriz :
class Program { static void Main(string[] args) { MyClass myClass = new MyClass(); Lambda.Call<MyClass>(myClass, obj => obj.MyProperty *= 5); } } class MyClass { public int MyProperty { get; set; } } static class Lambda { static public void Call<T>(T obj, Action<T> action) => action.Invoke(obj); }
Böylece ‘Lambda’ sınıfı içerisindeki generic ‘Call’ fonksiyonu sayesinde türden bağımsız bir şekilde optimize edilmiş lambda operasyonlarını kullanabilmekteyiz. Burada dikkat ederseniz optimize edilmiş ileri seviye bir yaklaşım sergilenmektedir. Bu ve buna benzer yaklaşımları string.Create
gibi bazı metinsel fonksiyonlarda kullanıldığını görebilirsiniz.
LINQ sorgularında ki yersiz alan tahsislerine karşı alternatif olarak döngüleri kullanabilirsiniz. Tabi bu zahmette karar sizin! 🙂
Tüm bu optimizasyonlu yapılanmaların dışında, insanlık hallerine denk gelip farkında olmadan lambda ifadelerinin dışındaki değişkenlere erişim sağlanması ihtimallerini sıfıra indirebilmek ve kesinlikle bu tarz bellek israflarına yol açan tüm çalışmaları engelleyebilmek için esas bu makalenin konusu olan C# 9.0 ile gelen Static Lambda özelliğini kullanabiliriz.
Haliyle bir lambda ifadeyi ‘static’ keywordü ile aşağıdaki gibi işaretlersek eğer;
Action action = static () => { };
lambda dışındaki hiçbir değişkene erişilemeyeceğini derleyici açısından garanti altına almış oluyoruz.

Static Lambda tarafından dışarıda tanımlanmış bir değişkene erişmeye çalışıldığında alınan hata.
“A static anonymous function cannot contain a reference to ‘a’.”
Böylece C# programlama dilinin en esnek yapılarından biri olan lambda ifadelerinin içerisinde farkında olalım ya da olmayalım ekstradan bellekte allocation(tahsis)’lara sebep olabilen durumları ele almış ve bu durumlara karşı nasıl duruş sergileyebileceğimizi örneklerle değerlendirmiş ve istişare eylemiş olduk…
İlgilenenlerin faydalanması dileğiyle…
Sonraki yazılarımda görüşmek üzere…
İyi çalışmalar…
Hocam iyi günler yazı için çok teşekkürler yine çok faydalı bir makale olmuş. Hocam bu konudan bağımsız olarak makale içinde kullandığınız kod panelini ve yorumlar kısmını nasıl oluşturdunuz bende hazırlayacağım blog sitesine hem yorumlar kısmını hemde bazı makalelerde kullanmak üzere kod panelleri eklemek istiyorum ama pek bir kaynak bulamadım. Teşekkür ederim.
Merhaba,
Sistem olarak WordPress kullanmaktayım. Dolayısıyla yorumlar kısmı ilgili sistem tarafından sağlanmaktadır. Kod paneli için ise ‘SyntaxHighlighter’ isimli bir eklentiyi kullanmaktayım.
Kolay gelsin.