.NET 9 Yenilikler

Can Yüksel

Erdoğancan Yüksel

7 dakika 31 saniye okuma süresi

.NET 9 ile birlikte gelen yeni özellikler ile performans iyileştirmelerine odaklanılmış, kod yazma pratiklerini kolaylaştıran ve üretkenliği arttıran birçok yeni özellikler eklenmiştir.

net-9-yenilikler

.NET 9 ile gelen yenilikler geliştiricilere daha esnek, okunabilir ve performans odaklı kod yazma imkanı sunmaktadır. 12 Kasım 2024 tarihi itibari ile yayınlanan .NET 9 sürümünün 18 ay boyunca desteklenmesi planlanmaktadır. Bu makalede .NET 9 ile birlikte gelen aşağıdaki özellikler kod örnekleri ile birlikte incelenmiştir:

  • Serileştirme (Serialization)
  • LINQ
  • Koleksiyonlar (Collections)
  • Şifreleme (Encryption)
  • Yansıma (Reflection)
  • Performans

Serileştirme

NET 9'da JsonSerializerOptions ile girinti karakterini ve girinti boyutunu özelleştirme imkanı sağlanmaktadır. Ayrıca JsonSerializerOptions.Web Singleton'ı kullanılarak camelCase adlandırma politikası ile birlikte veriler web uygulamları için daha uyumlu hale kolaylıkla getirilebilmektedir.

Serileştirme için kod örneği:

// Person isimli bir record oluşturalım:
record Person (string FirstName, string LastName, int Age, Gender Gender, string Department, string Profession);

// Cinsiyet için enum kullanalım:
enum Gender
{
    Male,
    Female
}

// Kişi listesi oluşturalım:
var personList = new List<Person>();
personList.Add(new Person("John", "Rise", 31, Gender.Male, "Development", "Backend Developer"));
personList.Add(new Person("David", "Cooler", 28, Gender.Male, "Development", "Frontend Developer"));
personList.Add(new Person("Lucy", "Damon", 25, Gender.Female, "Information Technology", "IT Expert"));

// JsonSerializeOption değerlerini belirleyelim:

var options = new JsonSerializerOptions
{
    WriteIndented = true,
    IndentCharacter = '\t', //.NET 9 ile birlikte eklenen girinti karakteri
    IndentSize = 1 //.NET 9 ile birlikte eklenen girinti boyutu
};

Console.WriteLine(personData);

#Output:
[
        {
                "FirstName": "John",
                "LastName": "Rise",
                "Age": 31,
                "Gender": 0,
                "Department": "Development",
                "Profession": "Backend Developer"
        },
        {
                "FirstName": "David",
                "LastName": "Cooler",
                "Age": 28,
                "Gender": 0,
                "Department": "Development",
                "Profession": "Frontend Developer"
        },
        {
                "FirstName": "Lucy",
                "LastName": "Damon",
                "Age": 25,
                "Gender": 1,
                "Department": "Information Technology",
                "Profession": "IT Expert"
        }
]

//ASP.NET Core'un web uygulamaları için kullandığı varsayılan seçenekleri için JsonSerializerOptions.Web Singleton'ı kullanılabilir:

var personDataForWeb = JsonSerializer.Serialize(
    personList,
    JsonSerializerOptions.Web
    );

Console.WriteLine(personDataForWeb);

#Output:

[{"firstName":"John","lastName":"Rise","age":31,"gender":0,"department":"Development","profession":"Backend Developer"},{"firstName":"David","lastName":"Cooler","age":28,"gender":0,"department":"Development","profession":"Frontend Developer"},{"firstName":"Lucy","lastName":"Damon","age":25,"gender":1,"department":"Information Technology","profession":"IT Expert"}]

Yukarıdaki kod örneğinde görüldüğü üzere JsonSerializerOptions alanına .NET 9 ile birlikte IndentCharacter ve IndentSize özellikleri verilerin özelleştirilmesine imkan tanımaktadır. Ayrıca JsonSerializerOptions.Web özelliği ile birlikte web uygulamaları için camelCase adlandırma politikası hazır şekilde eklenebilir hale gelmiştir.

LINQ

.NET 9 ile birlikte gelen CountBy ve AggregateBy özellikleri ile gruplandırma işlemleri oldukça kolaylaşmıştır. .NET 9 öncesinde ise benzer bir işlem için GroupBy, Aggregate ve Count metodları kullanılmaktadır.

  • CountBy(): .NET 9 ile birlikte gelen bu metot sayesinde öğeler gruplandırılarak her grubun adet sayısı hesaplanabilmektedir.
  • AggregateBy(): .NET 9 öncesinde GroupBy ve Aggregate metotları ile yapılan belirli bir anahtara göre gruplandırılmış verilerin toplamının hesaplanması işlemi .NET 9 ile birlikte gelen bu metot sayesinde tek metot ile yapılabilir hale gelmiştir.

CountBy ve AggregateBy kullanımı için kod örneği:

// TV Dizisi bölümleri için bir record oluşturalım:
record Episode(int SeasonId, string SerieName, int EpisodeId, string EpisodeName, WatchStatus Status, long WatchDurationInMinutes);

// İzlenme durumu için enum oluşturalım :
enum WatchStatus
{
    NotWatched, // İzlenmedi
    Watching, // İzleniyor veya tam olarak izlenmedi
    Watched // İzlendi
}

// Bir kullanıcı için bölümlerin izlenip izleneme durumlarını listeleyelim:
var episodes = new List<Episode>
{
    new(1, "Dark", 1, "S1.E1 - Geheimnisse", WatchStatus.Watched, 53 ),
    new(1, "Dark", 2, "S1.E2 - Lügen", WatchStatus.Watched, 52 ),
    new(1, "Dark", 3, "S1.E3 - Gestern und heute", WatchStatus.Watched, 50 ),
    new(1, "Dark", 4, "S1.E4 - Doppelleben", WatchStatus.Watched, 53 ),
    new(1, "Dark", 5, "S1.E5 - Wahrheiten", WatchStatus.Watched, 48 ),
    new(1, "Dark", 6, "S1.E6 - Sic Mundus Creatus Est", WatchStatus.Watching, 50 ),
    new(1, "Dark", 7, "S1.E7 - Kreuzwege", WatchStatus.NotWatched, 59 ),
    new(1, "Dark", 8, "S1.E8 - Was man sät, das wird man ernten", WatchStatus.NotWatched, 58 ),
    new(1, "Dark", 9, "S1.E9 - Alles ist jetzt", WatchStatus.NotWatched, 55 ),
    new(1, "Dark", 10, "S1.E10 - Alpha und Omega", WatchStatus.NotWatched, 57 ),
};

// İzlenen bölüm sayısını CountBy() belirleyelim:
// Şartı sağlayanların Key değeri true döner
var completedEpisodesCount = episodes.Select(episode => episode.Status == WatchStatus.Watched).CountBy(episode => episode).MaxBy(x => x.Key == true).Value;

// İzlenen bölümlerin toplam izlenme süresininin toplamını AggregateBy() ile hesaplayalım
var totalWatchTime = episodes.Where(x => x.Status == WatchStatus.Watched).AggregateBy(keySelector: episode => episode.Status, seed: 0m, (totalMinutes, item) => decimal.Add(totalMinutes, item.WatchDurationInMinutes));

foreach (var pair in totalWatchTime)
{
    Console.WriteLine($"Watched Episodes Numbers: {completedEpisodesCount}, Wath Status: {pair.Key} Duration: {pair.Value}");
}

#Output:
Watched Episodes Numbers: 5, Wath Status: Watched Duration: 256

Koleksiyonlar (Collections)

System.Collections.Generic isim alanında PriorityQueue<TElement, TPriority> koleksiyon türü için kuyruktaki bir öğenin önceliğinin güncellenmesi için kullanılabilecek yeni Remove(TElement, TElement, TPriority, IEqualityComparer<TElement>) yöntemi eklenmiştir.

PriorityQueue.Remove() yöntemi için kod örneği:

// Öncelikli bir kuyruk oluşturalım.

var taskQueue = new PriorityQueue<string, int>();

// Kuyruğa öğeler ekleyelim:
taskQueue.Enqueue(element: "Task-F", priority: 5);
taskQueue.Enqueue(element: "Task-D", priority: 4);
taskQueue.Enqueue(element: "Task-A", priority: 2);
taskQueue.Enqueue(element: "Task-B", priority: 1);
taskQueue.Enqueue(element: "Task-C", priority: 3);


//Kuyruğun elemanlarını yazdırmak için yardımcı fonksiyon oluşturalım:
static void PrintQueue(PriorityQueue<string, int> queue)
{
    // taskQueue'nin öğelerini bir kopya üzerinden işlem yaparak yazdırabiliriz:
    var tempQueue = new PriorityQueue<string, int>(queue.UnorderedItems);

    while (tempQueue.Count > 0)
    {
        tempQueue.TryDequeue(out var item, out var priority);
        Console.WriteLine($"Item: {item}, Priority: {priority}");
    }
}

// Kuyruğu yazdıralım
PrintQueue(taskQueue);

#Output:
Item: Task-B, Priority: 1
Item: Task-A, Priority: 2
Item: Task-C, Priority: 3
Item: Task-D, Priority: 4
Item: Task-F, Priority: 5

// 'Task-B' öğesini kaldırmayı deneyelim. Kaldırma işlemi başarılı olursa true, başarısız olursa false döner.
bool removed = taskQueue.Remove("Task-B", out _, out _);

Console.WriteLine($"Task-B Removed: {removed}");

// Kuyruğu tekrar yazdıralım
PrintQueue(taskQueue);

#Output:
Task-B Removed: True
Item: Task-A, Priority: 2
Item: Task-C, Priority: 3
Item: Task-D, Priority: 4
Item: Task-F, Priority: 5
  • TElement: Kuyruğa eklenen öğenin türünü temsil etmektedir.
  • TPriority: Öğenin önceliğini temsil etmektedir.

PriorityQueue<TElement,TPriority>.Remove(TElement, TElement, TPriority, IEqualityComparer<TElement>) yöntemi belirtilen öğeyi (element) ve önceliği (priority) kullanarak kuyruğa gider ve eşleşen bir öğe bulursa true döner. Eşleşen öğenin bulunmaması durumunda ise false dönmektedir.

Şifreleme

Şifreleme için .NET 9 ile birlikte kriptografi alanında iki yenilik getirilmiştir.

1.CryptographicOperations.HashData() Yöntemi

CryptographicOperations.HashData sadece hash algoritmasının adını parametre olarak alarak giriş verilerini hashlemek için bunu kullanmaktadır. Bu işlemi tek seferlik bir hash yöntemi olarak alır ve bu durum performansı arttırmaktadır. Ekstra olarak algoritmaları dinamik olarak değiştirmeye imkan tanımaktadır.

CryptographicOperations.HashData() Yöntemi için kod örneği:

// C# Örneği - .NET 9'da Hashleme İşlemi
using System.Security.Cryptography;
using System.Text;

// Veriyi UTF-8 formatında byte dizisine çeviriyoruz.
byte[] data = Encoding.UTF8.GetBytes("This is the data string for hashing in .NET 9!");

// SHA256 algoritması kullanarak hash işlemi
HashAndProcessData(HashAlgorithmName.SHA256, data);

// MD5 algoritması kullanarak hash işlemi
HashAndProcessData(HashAlgorithmName.MD5, data);

// Hash'leme işlemi için bir metot tanımlıyoruz.
static void HashAndProcessData(HashAlgorithmName hashAlgorithmName, byte[] data)
{
    // Hash oluştur
    byte[] hash = CryptographicOperations.HashData(hashAlgorithmName, data);
    ProcessHash(hash);
}

// Oluşturulan hash'i işlemek için örnek bir metot
static void ProcessHash(byte[] hash)
{
    Console.WriteLine($"Generated Hash: {Convert.ToHexString(hash)}");
}

// Örnek Çıktı:
// Generated Hash: 0BD50355B8539350552AA5AE4A6B3E650E712120DB2DD20CF5C5DCFFD484CF16
// Generated Hash: BA80099E45F438C797C2005D33A43547

2.KMAC Algoritması

KMAC (Keccak Message Authentication Code), bir mesajı doğrulamak ve bütünlüğünü sağlamak için kullanılan KECCAK tabanlı güçlü bir algoritmadır.

.NET 9 ile birlikte aşağıdaki yeni sınıflar eklenmiştir:

  • Kmac128: (128-bit uzunluğundadır.)
  • Kmac256: (256-bit uzunluğundadır.)
  • KmacXof128: Genişletilebilir çıktı uzunluğu (bir seferde maksimum 128 bit)
  • KmacXof256: Genişletilebilir çıktı uzunluğu (bir seferde maksimum 256 bit)

Kod örneği:

using System.Security.Cryptography;

// Kmac128 üzerinde HashData Yöntemi
if (Kmac128.IsSupported)
{
    // KMAC için bir anahtar ve giriş verisi alalım.
    byte[] key = System.Text.Encoding.UTF8.GetBytes("MySecureKey12345");
    byte[] input = System.Text.Encoding.UTF8.GetBytes("Hello .NET 9!");

    // 32 byte uzunluğunda bir MAC(Message Authentication Code) üretelim.
    byte[] mac = Kmac128.HashData(key, input, outputLength: 32);
    Console.WriteLine($"KMAC128 Hash: {Convert.ToHexString(mac)}");
}
else
{
    Console.WriteLine("KMAC128 is not supported on this system.");
    return;
}

// #Output:
// KMAC128 is not supported on this system.

Yansıma

AssemblyBuilder System.Refrelcion.Emit alan adı altında bulunur ve programın çalışma zamanında (runtime) dinamik olarak kodlar (codes), metotlar (methods), türler (types) ve derlemeler (assemblies) oluşturulmasına imkan tanımaktadır. .NET Core sürümlerinde ve .NET 5-8 sürümlerinde dinamik derlemeler yalnızca bellekte (memory) çalıştırılabiliyor iken .NET 9 ile birlikte bu sınırlandırma kaldırılarak dinamik oluşturulan derlemeler diske kaydedileiblir hale getirilmiştir. Kalıcı AssemblyBuilder örneği oluşturmak için AssemblyBuilder.DefinePersistedAssembly API kullanılmaktadır.

Kod örneği:

// Derleme Oluşturma ve Kaydetme
using System.Reflection.Emit;
using System.Reflection;

static void CreateAndSaveAssembly(string assemblyPath)
{
    // 1. Derleme tanımlanır.
    AssemblyBuilder ab = AssemblyBuilder.DefinePersistentAssembly(
        new AssemblyName("MyAssembly"),       // Derleme adı
        typeof(object).Assembly               // Temel çalışma zamanı türleri için başvuru
    );

    // 2. Modül ve tür oluşturulur.
    TypeBuilder tb = ab.DefineDynamicModule("MyModule") // Modül tanımlanır
                        .DefineType("MyType",           // Tür adı
                                    TypeAttributes.Public | TypeAttributes.Class); // Tür özellikleri

    // 3. Yöntem tanımlanır.
    MethodBuilder mb = tb.DefineMethod(
        "SumMethod",                // Yöntem adı
        MethodAttributes.Public | MethodAttributes.Static, // Yöntem özellikleri
        typeof(int),                // Geri dönüş türü
        new[] { typeof(int), typeof(int) } // Parametre türleri
    );

    // 4. IL (Intermediate Language) kodu yazılır.
    ILGenerator il = mb.GetILGenerator();
    il.Emit(OpCodes.Ldarg_0);       // İlk parametreyi yükle
    il.Emit(OpCodes.Ldarg_1);       // İkinci parametreyi yükle
    il.Emit(OpCodes.Add);           // Parametreleri topla
    il.Emit(OpCodes.Ret);           // Sonuçla geri dön

    // 5. Tür oluşturulur.
    tb.CreateType();

    // 6. Derleme dosyaya kaydedilir.
    ab.Save(assemblyPath); // veya bir Stream'e kaydedilebilir
}

// Kaydedilen Derlemeyi Kullanma:
static void UseAssembly(string assemblyPath)
{
    // 1. Derleme yüklenir.
    Assembly assembly = Assembly.LoadFrom(assemblyPath);

    // 2. Tür ve yöntem alınır.
    Type type = assembly.GetType("MyType");         // Tür adıyla tür alınır
    MethodInfo method = type.GetMethod("SumMethod"); // Yöntem adıyla yöntem alınır

    // 3. Yöntem çağrılır ve sonuç alınır.
    Console.WriteLine(method.Invoke(null, new object[] { 5, 10 })); // Çıktı: 15
}

// Derleme oluşturulması ve kaydedilmesi:
CreateAndSaveAssembly("MyAssembly.dll");

// Kaydedilen Derlemeyi Kullanma:
UseAssembly("MyAssembly.dll");

Performans

.NET 9 ile birlikte uygulama performansını arttırmak için 64 bit JIT (Just-In-Time) derleyicisine yönelik iyileştirmeler içermektedir. Bu iyileştirmeler sayesinde kodlar daha hızlı çalışır ve daha verimli hale getirilmiştir.

1.Döngü İyileştirmeleri

İndüksiyon değişkeni (Induction Variable) bir döngüdeki yinelemelerde sürekli artan veya azalan değişkendir. Önceki .NET sürümlerinde 64 bit platformlarda bu değişkenler genellikle 32 bit olarak işlenip sonrasında 64 bit'e genişletilmektedir ve bu genişletme fazladan işlem yüküne sebep olmaktadır. .NET 9 ile birlikte indüksiyon değişkeni doğrudan 64 bit olarak işlenerek bu ekstra genişletme işlemi yükü ortadan kaldırılmıştır. Bu durum döngülerde performansı arttırmaktadır.

Kod örneği:

// Dizideki elemanları for döngüsü ile toplayan veya çarpan bir yardımcı fonksiyon yazalım:
static int ArrayOperation(int[] array, OperationType type)
{
    int sum = 0;
    int multiplicationSum = 1;
    if (type == OperationType.Aggregation)
        for (int i = 0; i < array.Length; i++) sum += array[i];
    else if (type == OperationType.Multiplication)
    {
        sum = 1;
        for (int i = 0; i < array.Length; i++) sum *= array[i];
    }
    else Console.WriteLine("Invalid operation type");

    return sum;
}


// İşlem Tipini enum olarak atayalım
enum OperationType
{
    Aggregation,
    Multiplication
}

// Kullanımı

int[] numberArray = { 1, 2, 3, 4, 5, 6 };

var sumOperation = ArrayOperation(numberArray, OperationType.Aggregation);
var multipleOperation = ArrayOperation(numberArray, OperationType.Multiplication);

Console.WriteLine($"Sum of the elements in the array: {sumOperation}");
Console.WriteLine($"Multiplication of elements in the array: {multipleOperation}");

// Output:
// Sum of the elements in the array: 21
// Multiplication of elements in the array: 720

2.Yerel AOT için Inlining Geliştirmeleri

Yerel AOT (Ahead-of-Time Compilation) kodun çalışma zamanında (runtime) değil, önceden derlenmesi demektir. .NET 9'da iş parçacığı yerel statiklerine erişim için inlining desteği arttırılmıştır. Inlining, küçük metotlar için metodun çağırıldığı yerde metot gövdesinin doğrudan çağrılan yere kopyalanmasıdır. Bu şekilde çağrı maliyeti ortadan kaldırılmış olmaktadır.

3.PGO Geliştirmeleri

PGO (Profil Destekli Optimizasyonlar) bir uygulamanın çalışma zamanındaki (runtime) davranışlarını analiz ederek derleme sürecinde analiz ettiği veriler doğrultusunda kodun optimize edilmesidir. .NET 9 güncellemesi ile PGO verilerinin kullanımı ve kontrolü daha hızlı ve verimli bir şekilde gerçekleştirebilir.

 Sonuç olarak .NET 9 ile birlikte gelen yenilikler kodlarımızda performans artışı sağlamaya yardımcı olmakta, yazılım gelişitiricilere esnek ve güvenli kodlama deneyimi sunmaktadır.