.NET Core: C# 12 ve .NET 8'deki Bazı Yeni Özellikler 🚀

.NET 8 ve C# Kasım 2023 sürümü, .NET dünyasında sadece bir sürüm yükseltmesi değil, aynı zamanda ASP.NET Core proje geliştirmenin geleceğini şekillendirecek yeniliklerle dolu bir dönüm noktasıdır. Yeni sürüm, performans iyileştirmelerinden kod yazma pratiklerine, verimliliği artıran özelliklerden geliştirici deneyimini zenginleştiren araçlara kadar bir dizi yenilik getiriyor.

Murat Dinç
Devops Türkiye☁️ 🐧 🐳 ☸️
8 min readNov 24, 2023

--

Selamlar,

.NET 8 ve C# Kasım 2023 sürümü, .NET dünyasında sadece bir sürüm yükseltmesi değil, aynı zamanda ASP.NET Core proje geliştirmenin geleceğini şekillendirecek yeniliklerle dolu bir dönüm noktasıdır. Yeni sürüm, performans iyileştirmelerinden kod yazma pratiklerine, verimliliği artıran özelliklerden geliştirici deneyimini zenginleştiren araçlara kadar bir dizi yenilik getiriyor.

Makalede Primary Constructors, Collection Expressions, Default Lambda Parameters gibi dikkat çekici özelliklerin yanı sıra, daha özel alanlarda kullanılabilecek Inline Arrays ve deneysel çalışmalar için kullanabileceğimiz Experimental Attribute gibi ileri seviye konseptlere de değineceğiz. Ayrıca, C# 11'den itibaren kullanıma sunulan ancak .NET 8 ile daha geniş bir kitleye ulaşması beklenen Raw String Literals gibi özelliklerin pratik kullanımlarını inceleyeceğiz.

.NET 8 SDK

1. Primary Constructors

C# 12 ile gelen Primary Constructors özelliği, sınıfların daha sade ve temiz bir şekilde tanımlanmasını sağlamak için tasarlanmıştır. Bu yeni yapı, özellikle Dependency Injection kullanan ASP.NET Core projelerinde, constructor tanımlamalarını daha verimli bir hale getirir.

Örneğin, bir UserService sınıfınız varsa ve bu sınıf IUserRepository ve ILogger gibi bağımlılıkları inject ediyor olacak. C# 12'den önce, her bir bağımlılığı manuel olarak sınıf içinde private alanlara atamanız ve bir constructor içinde bu atamaları yapmanız gerekirdi.

public class UserService : IUserService
{
private readonly IUserRepository _userRepository;
private readonly ILogger _logger;

public UserService(IUserRepository userRepository, ILogger logger)
{
_userRepository = userRepository;
_logger = logger;
}
}

C# 12 ile birlikte, Primary Constructors özelliği sayesinde, aynı işlemi tek satırda ve sınıf tanımı içerisinde gerçekleştirebilirsiniz. Bu, hem kod tekrarını azaltır hem de constructor içindeki bağımlılığı daha okunabilir kılar.

public class UserService(IUserRepository userRepository, ILogger logger) : IUserService
{
private readonly IUserRepository _userRepository = userRepository;
private readonly ILogger _logger = logger;
}

Burada UserService sınıfı, IUserRepository ve ILogger türündeki parametreleri doğrudan alır ve bu parametreler sınıf içindeki private alanlara atanır. Bu, daha önce constructor içinde yapılması gereken işlemleri otomatikleştirir ve sınıfın tanımını sadeleştirir. Bu sayede, kodun okunabilirliği artarken, aynı zamanda tanımlama sırasında hata yapma ihtimali de düşer.

Primary Constructors, ASP.NET Core’da sıkça kullanılan design pattern’ler ile uyumlu şekilde çalışır ve geliştiricilere daha temiz bir kod tabanı yaratma fırsatı verir. Bu yeni özellik, özellikle büyük projelerde veya çok sayıda bağımlılığın yönetildiği durumlarda, kodun anlaşılabilirliğini ve yönetilebilirliğini önemli ölçüde artırabilir.

2. Collection Expressions

C# 12 ile tanıtılan Collection Expressions, koleksiyonları initialize ve definition işlemlerini daha kısa ve anlaşılır bir hale getirmek için kullanılan bir özelliktir. Bu, özellikle koleksiyonları sıkça kullanılan ve initializers ile koleksiyon nesnelerini doldurmanın yaygın olduğu geliştirmede zaman kazandırır ve kodu daha okunaklı kılar.

C# 12 öncesinde koleksiyon initialize işlemleri genellikle aşağıdaki gibi yapılırdı 👇🏻

List<User> users = new List<User>();
// veya
var users = new List<User>();
// veya
List<User> user = new();

C# 12 ile gelen bu yeni özellik sayesinde, aynı işlemi daha sade bir syntax ile yapabiliriz 👇🏻

List<User> users = [];

Bu yeni özellik, collection initialize kullanırken süslü parantezlerin ({}) yerine köşeli parantezleri ([]) kullanmanızı sağlar. Bu, özellikle koleksiyonu sabit değerlerle initialize ederken daha sade ve okunaklı bir kod yazmanıza olanak tanır.

List<User> users = 
[
new User { Username = "user1" },
new User { Username = "user2" }
];

Array initialize için de benzer bir iyileştirme yapılmıştır. Artık dizileri başlatmak için new sözcüğüne ve tip belirtimine ihtiyaç duymazsınız 👇🏻

// C# 12 öncesinde
string[] statusCodes = new string[] { "SUCCESS", "WARNING", "DANGER"};

// C# 12 ile
string[] statusCodes = ["SUCCESS", "WARNING", "DANGER"];

Yukarıdaki örnekte görüldüğü gibi, C# 12'deki Collection Expressions özelliği, koleksiyonları ve dizileri initialize işlemini daha etkili hale getirir. Bu, kodun daha az ve daha anlamlı olmasını sağlar, böylece geliştiricilerin intentlerini daha net bir şekilde ifade etmelerine olanak tanır. Özellikle sabit verilerle çalışırken veya başlangıçta belli değerlere sahip koleksiyonlar oluştururken bu yeni özellik oldukça faydalıdır.

3. Default Lambda Parameters

C# 12 ile geliştiricilere sunulan Default Lambda Parameters özelliği, lambda ifadelerinde parametreler için varsayılan değerler tanımlama imkânı getirir. Bu sayede, lambda ifadeleri daha esnek hale gelir ve bazı parametrelerin çağrıldığı yerde her defasında belirtilmesine gerek kalmaz.

Lambda ifadeleri, genellikle kısa ve özgün fonksiyonlar yazmak için kullanılır ve bu fonksiyonlar çeşitli operasyonlar için delegate olarak görev alabilirler. Varsayılan lambda parametreleri özelliği, fonksiyonlara daha fazla esneklik ve kullanım kolaylığı sağlayarak, kod tekrarını azaltmaya ve ifadeleri daha anlaşılır kılmaya yardımcı olur.

C# 12 öncesinde lambda parametrelerinin her çağrıldığı yerde açıkça belirtilmesi gerekiyordu👇🏻

var getFullName = (string firstName, string lastName) => string.Join(" ", firstName, lastName);
var fullName1 = getFullName("Murat", "");
var fullName2 = getFullName("Murat", "Dinc");

Bu örnekte, getFullName fonksiyonu iki parametre alır ve bunları birleştirerek tam adı oluşturur. Eğer bir soyadı sağlanmazsa, boş bir string geçmek zorundayız.

C# 12 ile tanıtılan varsayılan parametrelerle, lambda ifadesinde belirli bir parametrenin varsayılan bir değere sahip olmasını sağlayabiliriz 👇🏻

var getFullName = (string firstName, string lastName = "") => string.Join(" ", firstName, lastName);
var fullName1 = getFullName("Murat"); // lastName parametresi isteğe bağlı
var fullName2 = getFullName("Murat", "Dinc");

Bu durumda, lastName parametresi için varsayılan bir değer tanımlandığından, bu parametreyi çağrı sırasında belirtmek zorunda değiliz. Eğer lastName sağlanmazsa, lambda ifadesi varsayılan değer olan boş string’i kullanır. Bu, özellikle opsiyonel parametrelerle çalışırken kodun okunabilirliğini ve temizliğini artırır.

Varsayılan lambda parametreleri, kodun daha esnek ve kısa olmasını sağlarken, fonksiyonları daha az parametreyle çağırmanıza olanak tanıyarak, geliştirme sürecini hızlandırır ve kodun anlaşılmasını kolaylaştırır.

4. Alias Any Type

C# dilinde “alias” kullanımı, belirli bir tür veya koleksiyona kısa ve anlamlı bir isim vererek kodun okunabilirliğini ve yönetilebilirliğini artırmak için kullanılır. Bu yaklaşım, özellikle belirli bir türü sıkça kullanıyorsanız veya karmaşık tür tanımlamalarını sadeleştirmek istiyorsanız oldukça yararlıdır.

C# 12 ve .NET 8 ile gelen bu özellik, türleri kendi projenizin context yapısına uygun semantik isimlerle adlandırmanıza olanak tanır. Bu, özellikle domain-driven design (DDD) gibi yaklaşımlarda, türlerin iş domainine özgü anlamlarını vurgulamak için kullanılabilir.

Örneğin, bir List<User> türünü sıkça kullanıyorsunuz ve bu, projenizde belirli bir context üzerinde “Users” olarak anlam kazanıyorsa, bu tür için bir “using” alias tanımlayabilirsiniz 👇🏻

using Users = System.Collections.Generic.List<User>;

Bu alias tanımından sonra, List<User> yerine Users kullanarak kodunuzu daha anlamlı ve okunabilir hale getirebilirsiniz 👇🏻

public Users GetUsers()
{
return
[
new User { Username = "muratdinc" },
new User { Username = "derindinc" }
];
}

Bir diğer kullanım senaryosu da, birden fazla değer döndürmek istediğinizde ve bunun için her seferinde yeni bir sınıf veya struct tanımlamak istemediğinizde ortaya çıkar. Bu durumda, Tuple türlerine alias tanımlayarak, tekrarlayan tuple tanımlamalarını azaltabilirsiniz 👇🏻

using Person = System.ValueTuple<string, string>;

Bu takma adı kullanarak, metodunuzdan bir Person tuple’ı döndürebilirsiniz 👇🏻

public Person Get()
{
return ("Murat", "Dinc");
}

public List<Person> GetAll()
{
return
[
("Murat", "Dinc"),
("Derin", "Dinc")
];
}

Alias kullanımı, kodunuzun belli kısımlarını semantik olarak daha anlamlı hale getirirken, aynı zamanda tekrarlanan tür tanımlarını sadeleştirerek projenin genel anlaşılırlığını artırır. Bu, özellikle büyük ve karmaşık projelerde, türler arasındaki ilişkilerin daha açık ve net olmasını sağlar ve geliştirme sürecini kolaylaştırır.

5. Experimental Attribute

C# 12 ve .NET 8 sürümleriyle, geliştiricilerin belirli özellikleri veya kod bloklarını “deneysel” olarak işaretlemelerine olanak tanıyan yeni bir özellik sunulmuştur. Bu, Experimental Attribute kullanılarak yapılır. Bu nitelik, belirli bir kod parçasının deneme aşamasında olduğunu ve gelecekte değişikliğe uğrayabileceğini veya tamamen kaldırılabileceğini belirtir. Yani bu, işaretlenen özelliklerin veya kod bloklarının stabil olmadığını ve development ortamında kullanımlarının riskli olabileceğini gösterir.

Bu nitelik, özellikle büyük ve sürekli güncellenen kod tabanları için kullanışlıdır. Geliştiriciler, belirli özelliklerin kullanımını sınırlamak veya geliştirme sürecindeki diğer ekip üyelerini bu özelliklerin potansiyel değişikliklere açık olduğu konusunda uyarmak için Experimental Attribute kullanabilirler.

Örneğin, yeni bir ürün silme özelliği ekliyorsunuz ve bu özelliği deneysel olarak işaretlemek istiyorsunuz 👇🏻

[Experimental("Feature07")]
public void DeleteAllProducts()
{
_context.Database.ExecuteSqlRaw("DELETE FROM Products");
}

Bu özelliği kullanmaya çalışmak, derleme zamanında bir hata üretir, çünkü özelliğin deneysel olduğu ve stabil olmadığı belirtilmiştir. Eğer deneysel özelliği yine de kullanmak istiyorsanız, derleme hatasını baskılamak için uygun pragma kullanabilirsiniz 👇🏻

#pragma warning disable Feature07
_context.Database.ExecuteSqlRaw("DELETE FROM Products");
#pragma warning restore Feature07

Bu kullanım, özellikle deneysel özellikleri denemek isteyen ancak stabilite garantisi olmadığını bilen geliştiriciler için uygundur. Ayrıca, bu yaklaşım sayesinde, gelecekteki değişiklikler için kodunuzu hazırlıklı hale getirir ve gerektiğinde hızlı bir şekilde adaptasyon yapabilmenizi sağlar. Geliştiricilere sunulan bu esneklik, yenilikçi özelliklerin erken safhalarda denenebilmesine ve geri bildirimlerin toplanabilmesine olanak tanırken, stabilite ve güvenilirlikten ödün vermemeyi amaçlar.

6. Inline Arrays

Performans hassasiyeti olan senaryolar için “Inline Arrays” adı verilen bir özellik tanıtılmıştır. Bu özellik, özellikle bellek düzeni ve veri yerleşimi üzerinde daha fazla kontrol gerektiren durumlar için kullanışlıdır. Inline Arrays, derleyicinin diziyi hafızada ardışık bir blok olarak ayırmasını sağlayarak, normal dizilere göre bellek tahsisinde ve erişimde potansiyel olarak daha verimli olabilir.

System.Runtime.CompilerServices namespace'ini kullanarak tanımlanan InlineArray niteliği, belirli bir boyut için önceden bellek ayırır. Bu, genellikle sabit boyutlu bir diziye ihtiyaç duyduğunuzda ve bu dizinin performansının kritik olduğu durumlarda kullanılır.

Örneğin, aşağıdaki gibi bir MyArray yapısı oluşturabilirsiniz 👇🏻

using System.Runtime.CompilerServices;

[InlineArray(5)]
public struct MyArray<T>
{
private T _element;
}

Bu yapı, beş elemanlı bir dizi olarak işlev görür ve normal bir dizi gibi kullanılabilir 👇🏻

var users = new MyArray<User>();

for (int i = 0; i < 5; i++)
{
users[i] = new User();
}

foreach (var user in users)
{
Console.WriteLine(user.FirstName + " " + user.LastName);
}

Burada MyArray<T> yapısı InlineArray niteliği ile süslendiğinden, derleyici bu yapı için beş elemanlık bir hafıza alanı ayırır ve bu yapıyı kullanarak dizi benzeri işlemler yapılabilir.

❗ Bu özellik zor karşılaşılan durumlar için tasarlanmıştır ve belirli durumlar dışında genel kullanım için önerilmemektedir.

Inline Arrays’in kullanımı, performansı kritik uygulamalar için yararlıdır, çünkü bu özellik bellek yerleşimini optimize etmeye ve gereksiz bellek çağrılarını azaltmaya yardımcı olur. Bu, bellek bant genişliği kısıtlamaları olan sistemlerde veya çok düşük gecikme süreleri gerektiren uygulamalarda faydalı olabilir. Ancak, bu tür optimizasyonların gerekliliği, uygulamanın gereksinimleri ve performans hedefleri doğrultusunda değerlendirilmelidir.

7. Raw String Literals

C# 11 ile gelen ve .NET 7'de kullanıma sunulan Raw String Literals, özellikle çok satırlı string ifadelerin ve özel karakterler içeren metinlerin kolayca yazılmasını sağlayan bir özelliktir. Bu özellik, C# 12 ve .NET 8 ile devam eden sürümlerde de kullanılmaya devam etmektedir ve özellikle JSON, HTML veya SQL gibi belirli formatlarda metinleri kod içerisinde temsil etmek istediğinizde büyük kolaylık sağlar.

Raw String Literal, normal string literallerinden farklı olarak, escape sequence’leri (\n, \t gibi) kullanmanıza gerek kalmadan metni olduğu gibi yazmanızı sağlar. Ayrıca, $ işareti ile birleştirildiğinde string içerisinde direkt olarak C# ifadeleri yerleştirilebilir, bu da string interpolation işlemini daha da güçlendirir.

Raw String Literal kullanarak SQL sorgularını yazmak oldukça basittir:

var minPrice = 100;
var maxPrice = 1000;

var query = $"""
SELECT *
FROM Products
WHERE Price > {minPrice} and Price < {maxPrice}
ORDER BY Price DESC
""";

Bu örnekte """ ile başlayıp biten bir Raw String Literal tanımlanmış ve içerisinde birden çok satır ve C# ifadesi bulunmaktadır. C# derleyicisi bu ifadeleri uygun şekilde işler ve SQL sorgusunu oluşturur.

JSON string’leri için de benzer bir kullanım söz konusudur:

var id = 1;
var firstName = "Murat";
var lastName = "Dinc";

var json = $$"""
{
"id": "{{id}}",
"firstName": "{{firstName}}",
"lastName": "{{lastName}}"
}
""";

Burada $$""" ile kullanılan Raw String Literal, içerisinde yer değiştirme alanları ({{id}}, {{firstName}}, {{lastName}}) kullanılarak bir JSON metni oluşturmak için kullanılmıştır.

Raw String Literal, özellikle .NET 7'yi atlayıp doğrudan .NET 8'e geçen geliştiriciler için de önemlidir, çünkü bu özellikle daha önce karşılaşmadıkları bir kolaylık ve esneklikle karşılaşacaklardır. Bu özellik, karmaşık string işlemleri gerektiren durumlarda kodun okunabilirliğini ve bakımını büyük ölçüde kolaylaştırır.

Makaleyi faydalı bulduysanız takip ederek destek olabilirsiniz 🙏

Bir sonraki yazıda görüşmek üzere 😊

--

--