C# 9.0 İle Gelen Yeni Özellikler

Selamlar,

Bu makalede C# 9.0 ile gelen yeni özellikleri beraberce madde madde inceleyeceğiz. Son 15 yıldır, özellikle büyük kırılımların olduğu farklı bir çok versiyona şahit oldum. Örneğin “Linq” veya “Genericsler” gibi. Bence işte bu büyük kırılmalardan birinin olduğu bir versiyon ile karşı karşıyayız.

Ön Hazılık:

Ben kendi adıma, Preview versiyonu ile çalışır iken var olan sistemin bozulmaması için, Virtual Machine olarak harici bir makina kurup, aşağıdaki uygulamaları indirdim.

  1. Öncelikle .Net 5.0 buradaki  linkden indirilebilir. Bu makale itibari ile, SDK 5.0.100-rc.1versiyonu indirilmiştir.
  2. Ayrıca Visual Studio 2019 Preview buradaki linkden indirilebilir. Bu makale itibari ile, v16.8 versiyonu indirilmiştir.

“Değişim, ancak içeriden açılabilen bir kapıdır.” —Terry Neil

Hadi şimdi gelin C# 9.0’un bize kattığı yenilikleri, hep beraber inceleyelim.

1-)Top-level statements:

Özellikle Microservis uygulamalarda bolca kullandığım, Console Applicationlardaki “class” ve bir “Main()” methoda(yani bir başlangıç noktasına) gerek duyulmadan, sadece amaçlanan kod artık yazılabilmektedir. Peki başlangıç noktasını nasıl belirlenmektedir ? Tabi ki dosya’nın ismi ile (Program.cs)

Öncelikle gelin, yeni bir .Net 5.0 Console Application oluşturalım. Ve otomatik oluşan kodları aşağıdaki yeni kod ile değiştirelim.

Console Uygulamasının çalıştırılacağı Target framework seçimi:

Eskisi:

Yeni: C# 9.0

İşte bu kadar kısa :)

2-)Init-only properties:

Nesnenin sadece oluşturulması esnasında atanabildiği özellikleridir. Init tanımlı property, sonrasında sadece readonly olarak çalışılabilir. Yani değiştirilemez. Bir propertyde hem “set“, hem “Init” aynı anda kullanılamaz. Sadece biri seçilmelidir.

Aşağıda, Motor sınıfının yaratılma esnasında atanmış olduğu “init Model” property, sonradan değiştirilmeye çalışıldığında alınan hata gösterilmiştir.

“Geçmişin arabalarıyla hiçbir yere gidemezsiniz.” —Maksim Gorki

3-) Target-typed new expressions:

Tipi öncesinden belli bir nesne oluşturulurken, constructor çağrılması sırasında ayrıca tekrardan tipinin belirtilmesine gerek yoktur. Şahsen bu özellikle, yeni bir “List” oluştururken, her seferinde ayrıca tipini belirtmekten kurtulduğum için çok seviniyorum :)

Eski:

Yeni:

Örnek Kullanım: Aşağıdaki örnekte, Motor sınıfı tanımlanırken motorList propertsi “new()” Expression ile tanımlanmıştır. Ayrıca Main() methodunda, “Motor honda” gene aynı şekilde tipi bir daha yazılmaya gerek duyulmadan oluşturulmuştur. Ve son olarak “honda.motorList“‘e, yeni Model gene new() Expression ile eklenmiştir.

4-) Records: Benim adıma C# 9.0 ile gelen en önemli yenilik:

Sınıflara çok benzeyen ama sınıflara göre ayrıca bir çok artı özelliği olan yapılardır. Inheritance, yalnızca kendi tipleri arasında olabilmektedir. Yani bir “class” => “record“‘dan, ya da “record” => “class“‘dan türüyemez. Recordların esas çıkış noktası, içerisindeki datanın yönetimidir. Class’a nazaran record tanımlaması, nesneyi immutable ve value type’a çevirir.

5-) *Record with Expressions:

C# 9.0’dan var olan bir nesnenin sadece bir kaç özelliğini değiştirip, diğer özelliklerini aynen klonlayarak yaratılmasını sağlayan benim favori özelliğimdir. Bu design patternlerdan, Prototype‘ın, keyword halidir :)

Aşağıda görüldüğü gibi “blackHonda”, “honda”‘nın sadece “Color” özelliğini override edip, diğer özelliklerini Deep Copy yaparak, yani farklı bir referance tipinde aynen almıştır. Yukarıda görüldüğü gibi ekrana yazdırıldığında, color haricinde tüm özelliklerini honda’dan almış, sadece color özelliğini ezerek “Color = “‘Dark Black'” yapmıştır.

6-)Record Objects comparison Equals – ReferenceEquals :

Şu an için classlar’da kullanılamayan ama ilerde recordlarda bolca kullanacağımız iki nesnenin referans ve değer olarak karşılaştırılmasını inceleyeceğiz.

  • ReferenceEquals() methodu ile iki nesnenin bellekte tutulduğu referance adresleri karşılaştırılır. Aşağıdaki örnekte “ReferanceEquals(honda,blackHonda)” methodu sonucu, blackHonda’nın honda record’undan “with” keyword’ü ile türetilmesinden dolayı “false“‘dur. Çünkü “with” keyword’ü, yeni bir nesne oluştururken referance’ı farklı, ve var olan özellikleri override edilmedikçe deep copy şeklinde kopyalayarak oluşturur.
  • Equals() methodu ile iki nesnenin value olarak eşit olup olmadığına bakılır. Aşağıdaki örnekte colorHonda, honda’dan aynı renk kullanılarak oluşturulmuştur. Equals(colorHonda,Honda) ==> İki nesnenin de değerleri birbiri ile aynı oluğu için, sonuç True‘dur.
  • Equals(honda,blackHonda): blackHonda, honda record’undan “Color” özelliği değiştirilerek oluşturulmuştur. Bu nedenle iki nesnenin değerleri aynı değildir. Ve sonuç False‘dur.

7-) Positional records:

Positional Recordlarda, constructor ve deconstructor kullanımına hem izin verilmekte hem de çok pratik bir yolu bulunmaktadır.

İlk oluşturma sırasında bir record’u doğrudan constructor’ı ile birlikte propertylerini parametre vererek, yaratılmasını sağlıyabilirsiniz. Hem de bunu ilgili record’da, constructor tanımlamadan yapabilirsiniz :)

Eski Yöntem: Klasik bir Car record, Name ve Year init propertyleri ve son olarak Car() Constructor ve Deconstructor tanımlaması aşağıdaki gibidir.

” ‘Değişim ne zaman gerekli?’ sorusuna verilecek en iyi yanıt, gerekli hale gelmedendir.” —Claus Moller

Yeni Yöntem: Aşağıda görüldüğü gibi student record’u oluşturulurken, sanki bir methodmuş gibi () => constructor ve parametreleri hemen yanında tanımlanmıştır. “record Student(string name, string surname, int no)“. Bu tanımlama şekli sadece recordlara özeldir. Bu şekilde tanımlanan propertyler, “Init” olarak tanımlanmakda ve ilk yaratılma anındaki atanmadan sonra, readonly olarak kullanılmaktadırlar.

Yaratılma anında tanımlanan init propertyler, bir daha değiştirilemezler.

  • “public string Name => name” : Tüm propertyler, lambda expression kullanılarak atanmıştır.
  • “var bora = new Student(“Bora”, “Kasmer”, 1923)”: Çağrılma şekli her zamanki gibidir.
  • “var (name, surname, no) = bora” : Bora record’una ait tüm propertyler, local değişkenlere atılıp aşağıdaki gibi ekrana basılmıştı.

Sonuç Ekranı:

Bunu daha iyi bir hale getirmek ister isek, aşağıda görüldüğü gibi direkt property değerlerini büyük harfle atayabiliriz.

Ama bu sefer de atanmış olan bir property, değiştirilmek istendiğinde “Init-only” olarak atandığı bize hatırlatılır.

8-)Positional Recordlarda Inheritance:

recordlar, aynı classlarda olduğu gibi Inheritance özelliğine sahiptir. Klasik yöntemin zaten bilinmesinden dolayı, Positional Recordların kalıtımına örnek vermek istedim. Aşağıdaki örnekte görüldüğü gibi, Kawasaki hem Honda hem de Motor recordlarından inherit alınmıştır. Yaratılma anında, base record’un constructor propertyleri de, her record oluşturulmasında tanımlanır.

Not: Ayrca aşağıdaki örnekte, Motor record’una dikkat edilir ise, Positional Record olarak tanımlanan Color propertysi default olarak Init şeklinde tanımlandığı için, sonradan değiştirilebilmesi için tekrardan getter setter olarak tanımlanmıştır.

9-)Improved pattern matching:

Aşağıda görüldüğü gibi, motor record tipinde alınan araca göre switch pahalılık derecesi string olarak dönülmektedir.

  • “Honda h when h.Price < 30000 =>”: Eğer gelen motor Tipi Honda ise ve fiyatı 30000₺’den küçük ise, “Ucuz Honda” dönülür.
  • “Honda h when h.Price > 30000 & h.Price < 100000 =>” : Gene tipi Honda ve fiyat aralığı 30000₺ ile 100000₺ arasında ise, “Pahalı Honda” değeri dönülür.
  • Honda or Kawasaki =>” Artık “Honda or Kawasaki _ =>” şeklinde bir tanımlama yapılmasına gerek kalmamaktadır. Eğer yukarıdaki koşullar sağlanmaz ise ve gelen motor tipi Honda veya Kawasaki ise “Honda veya Kawasaki çok pahalı” değeri dönülür.
  • “_ => throw new ArgumentException()”: Eğer belirlenen motor tipi tanımsız ise hata fırlatılır.

Soru 1-)

Yukarıdaki method, aşağıdaki örnekler ile çağrıldığında alttaki gibi bir sonuç alınır. İlk motor Honda 3000<x<10000 arasında olduğu için, “Pahalı Honda” sonucu yazmıştır.” İkinci motor >100000 olduğu için, “Kawasaki hep Pahalı” sonucu yazmıştır :)

Soru 2-) : Peki aşağıdaki örneğin sonucu ne olurdu? Koşulların hiçbirini sağlamayıp sadece Honda veya Kawasaki koşuluna uyduğu için, aşağıdaki sonuca ulaşılmıştır.

Soru 3-)Son olarak aşağıdaki örnekten nasıl bir sonuç dönerdi? Bunun cevabı biraz kafa karıştırıcı ama nedeni basit. Girilen motor tipi Kawasaki iken, Honda motor sonucu nasıl karşımıza çıkmaktadır?  Çünkü Kawasaki Honda’dan miras alınarak yaratılmıştır. Yani Honda olma koşuluna uymaktadır. Ayrıca fiyat aralığı da 3000<x<10000 arasında olduğu için, “Pahalı Honda” sonucu yazılmıştır.

10-)Relational patterns:

Aşağıda görüldüğü gibi motor yılına göre vergi oranı hesaplanmıştır. Doğrudan “<” ve “>=” koşulu yazılabilmektedir. Ide gayet akıllı olduğu için en son koşulda “_ => motor.Price” hata vermektedir. Bundan dolayı yorumlanmıştır. Nedeni sondaki “< 2018” koşulu, geri kalan tüm koşulları sağlamaktadır. Bundan dolayı bir sonraki koşula gerek yoktur.

Yukarıdaki method, aşağıdaki örnek ile çağrıldığında, alttaki gibi bir sonuç ile karşılaşılır. “motor.year > 2019” olduğu için ilk koşul sağlanır. Yani “85000 * 1.2” ile çarpılır.

Convert if else statement to switch expression (Quick Actions):

Diyelim ki aşağıdaki gibi istenmeyen bir “if-else” yapınız olsun. “if”‘in üzerine gelinip, solunda çıkan ampul resmini yani “Quick Actions“‘ tıklanırsa, karşımıza yukarıdaki gibi bir menu çıkar. Bunlardan Convert to ‘switch’ expression‘ı seçilirse, yukarıdaki gibi Refactor Sanat Eserini ile karşılaşırız :) Ama bunun da bir kusuru var. O da, dikkat ederseniz son satıra “_ => motor.Price” eklemesidir. Ama az önce de anlatıldığı gibi, bu koşula hiçbir zaman gelinemeyecek ve derleme anında hata alınacaktır. İlgili koşulun kaldırılması ile uygulama derlenir. Burdan şu sonuç çıkıyor, Convert to ‘switch’ refactor algoritması Visual Studio 2019 IDE’si kadar, kodları analiz edebilecek düzeyde değildir.

11-) Covariant returns:

Aşağıda görüldüğü gibi, bu özellik ile override edilen metodun dönüş tipinin de, derived sınıf tipinde olmasına olanak sağlanmıştır. Özellikle Factory pattern’de çok işimize yarayacaktır. Peki nasıl yarayacaktır ? Tüm methodları aynı ama mesela bir methodu farklı tip(kalıtım alan sınıf tipinde) dönen bir sınıfı, inherit edemiyor yenisini yaratmak zorunda kalıyorduk. İşte tam bu noktada, dönüş tipini de override edip, bu dertten kurtulabileceğiz :)

12-) Target typed ?? and ?:

Aşağıdaki ilk örnekte C# 9.0 ile aynı nesneden türemiş yapılar arasında, compiler tarafından convert işlemi otomatik olarak yapılmakta, ve uygulama hatasız derlenmektedir. Honda ve Kawasaki recordları, ortak Motor recordundan türemiştir. Aşağıdaki örnekte honda null olduğu için, kawasaki console’a yazılmıştır.

Aşağıda görülen ikinci örnekte, kawasaki motorunun maxSpeed’i, null olarak girilmiştir.

  • Motor vehicle = honda ? kawasaki” : Tanımlanan motorun Kawasaki olmasından dolayı honda kontrolü null değer alır.
  • vehcile is Kawasaki?” : Doğrulamasında honda null olduğu için, kawasaki alınmış ve ilgili koşul sağlanmıştır.
  • ((Kawasaki)vehcile).maxSpeed ?? 0” : Kawasaki aracının maxSpeed propertysi null girildiği için, “??” null kontrolü sağlanarak “0” değeri atanmıştır.

Sonuç :

Kabaca C# 9.0 ile gelen yeniliklere göz attık. Bu sene C# 9.0 ile gelen yenilikler, diğer senelere oranla çok daha heyecan verici ve büyük gözüküyor. Bazı template tiplerinin kaldırılması, örneğin “ASP.Net Web Form”‘un kaldırılması üzse de, artık tek bir .Net tipinin olması yani .Net 5.0 ile .Net Core ve .Net Framework’ün birleştirilmesi, benim adıma gayet heyecan verici gelişmelerden sadece bir kaçı. Ayrıca Entity Framework Core 5.0 ile gelen yenilikler, örneğin Database collations’ın gelmesi ya da Flexible query/update mapping yapısı ile, query’nin => view ile çekilmesi ve update’in de=> table’a yapılarak, işem tipine göre  farklılaştırmanın sağlanması, ilk etapta konuşulabilecek değişikliklerdendir.

“Artık değişmeyecek hale geldiğin zaman, bitmiş sayılırsın.” —Bruce Barton

Bizi, daha birçok yeniliğin beklediğine emin olabilirsiniz. Yukarıda, .Net Framework’ün ilerleyen yıllara göre çıkması planlanan versiyonları gösterilmektedir.

Yeni bir makalede görüşmek üzere hepinize hoşçakalın.

Source Code: https://github.com/borakasmer/CSharp9.0.git

Kaynaklar:

Herkes Görsün:

Bunlar da hoşunuza gidebilir...

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir