C# 13 ve .Net 9 İle Gelen Önemli Yenilikler
Selamlar,
Bu makalede, Kasım ayında çıkacak olan .Net 9 ve C# 13 ile hayatımıza girecek yeni özelliklerden bahsedeceğim.
1-) Semi-Auto Properties (Field Keywor’ü):
Önceki şu makalemde bu özelliği, altı ay önce değinmiştim. Bu konu çok önemli olduğundan dolayı, tekrardan detaylı bir şekilde farklı örnekler ile açıklık getirmek istedim. Bu örneği şu sitede compile edebilirsiniz. Henüz .NET 9 Preview sürümünde çalıştırmayı başaramadım. ‘field‘ keyword’ü maalesef henüz tanımlanamıyor.
Aşağıdaki örnekte ExchnageType sınıfının Type ve Url propertyleri internal değişken tanımlanmadan, global “field” keyword’ü ile atama yapılabilmektedir. Bu da bize kod okunaklığında ve global kod yazımında büyük kolaylık sağlayacaktır. İlgili sınıfın kullanıldığı tüm yerlerde ayrıca bir değişken kullanmamak, kullanıldığı yerde var olan değişkenler ile karıştırılmasına, güvenliğin arttırılmasına ve hataların azaltılmasına yardımcı olacaktır. Aşağıdaki örnekte, bir ürünün belirtilen kura göre TL karşılığı bulunucaktır. Tek sorun var olan hali hazırdaki projelerde, implementasyon sırasında field keywordünün değişken olarak kullanılması durumunda karşımıza çıkabilir. Bu durumda field için=> “@field” şeklinde bir dönüşüme gitmek gerekebilir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
using System; using System.Xml.Linq; using System.Net.Http; using System.Threading.Tasks; using System.Linq; public enum ExchangeType { USA=1, EUR=2 } public class C { public async Task M() { var etranslate = new ExchangeTranslate(); etranslate.Type = ExchangeType.EUR; etranslate.Url = "https://www.tcmb.gov.tr/kurlar/today.xml"; var sellingRate = await etranslate.GetExchange(); var productPrice = etranslate.TranslateProductPrice(350); Console.WriteLine($"{ExchangeType.EUR} Kuru: {sellingRate} TL"); Console.WriteLine($"Ürün fiyatı: {productPrice} TL"); } } public class ExchangeTranslate { public ExchangeType Type { get => field; set { if ((int)value < 1 || (int)value>2) throw new ArgumentOutOfRangeException(nameof(value), "Value must be in ExchangeType values"); field = value; } } public string Url { get => field; set=> value = field; } public async Task GetExchange() { try { using HttpClient client = new HttpClient(); string xmlData = await client.GetStringAsync(this.Url); XDocument xmlDoc = XDocument.Parse(xmlData); // KUR için ilgili döviz bilgisini alalım var exRate = xmlDoc .Descendants("Currency") .FirstOrDefault(x => x.Attribute("CurrencyCode")?.Value == this.Type.ToString()); if (exRate != null) { string sellingRate = exRate.Element("ForexSelling")?.Value ?? "0"; return float.Parse(sellingRate); } else { return 0; } } catch (Exception ex) { return 0; } } public async Task TranslateProductPrice(float price) { var sellingRate = await GetExchange(); return price * sellingRate; } } |
2-) Yeni Lock Sınıfı:
C# 13 ile karşımıza, özellikle asenkron programlamada kullandığımız yeni bir System.Threading.Lock sınıfı çıkmaktadır. Geçmişte, kilitleme amacıyla bir nesne türü kullanılıyordu, ancak artık çoğu kilitleme için gelecekteki standart haline gelmesi beklenen bu yeni sınıfı kullanılacaktır.
Eski Yöntem:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public class News { public int Id { get; set; } public string Title { get; set; } public string Content { get; set; } public DateTime PublishDate { get; set; } private readonly object _lock = new(); public News GetNewsFromRedis(int id) { //Redis üzerinden haber getirme işlemi. Redis Boş ise veritabanindan çekilir ve redis doldurulur. var news= GetNewsFromRedis(id); if(news==null){ lock (_lock) { //Get Console.WriteLine("Lock içerisindeyiz."); news = GetNewsFromDb(id); FillRedis(news); }} return news; } } |
Yeni Yöntem: Artık nur topu gibi yeni bir Lock sınıfımız var. Ve bunun üzerinden gelen birçok yeni özellik, bizleri bekliyor.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
private readonly Lock _lock = new(); public News GetNewsFromRedis(int id) { //Redis üzerinden haber getirme işlemi. Redis Boş ise veritabanindan çekilir ve redis doldurulur. var news= GetNewsFromRedis(id); if(news==null){ lock (_lock) { //Get Console.WriteLine("Lock içerisindeyiz."); news = GetNewsFromDb(id); FillRedis(news); } } return news; } } |
Asenkron Lock: Asenkron methodlarda, lock işlemine ihtiyaç duyulursa standart “Lock” sınıfı halen bizim ihtiyaçlarımıza cevap verememektedir. Bunun için halen aşağıda görüldüğü gibi, SemaphoreSlim sınıfına ihtiyaç duymaktayız.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
private readonly SemaphoreSlim _semaphoreObject = new(1, 1); public async Task AsyncMethod(int val) { await _semaphoreObject.WaitAsync(); try { await Task.Delay(10); // No Errors } finally { _semaphoreObject.Release(); } } |
3-) params Collections
.Net 9’dan önce parametre sayısını bilmediğimiz metodlar için standart kullanım:
1 2 |
static void TotalNumbers(params int[] numbers) => Console.WriteLine($"Toplam : {numbers.Sum(x => x)}"); |
.Net 9 ile hayatımıza giren kullanım:
1 2 3 4 5 6 7 8 9 |
static void TotalSpanNumbers(params ReadOnlySpan numbers) => Console.WriteLine($"Toplam Span : {numbers.ToArray().Sum(x => x)}"); static void TotalEnumNumbers(params IEnumerable numbers) => Console.WriteLine($"Toplam Enumerable : {numbers.Sum(x => x)}"); TotalNumbers(1, 2, 3, 4, 5); TotalSpanNumbers(1, 3, 5, 7, 9); TotalEnumNumbers(9, 9, 9, 9, 9); |
4-) Swagger yerine OpenApi Dökümanı:
Aşağıda görüldüğü gibi, .Net 5.0’den beri gelen Swagger dökümanı yerine artık Web API uygulamarında default olarak çok hoşuma gitmese de artık OpenApi geliyor. İlgili sayfayı => “/openapi/v1.json” şeklinde görebilirsiniz.
OpenApi şu an için aşağıdaki kütüphane kullanıyor:
Peki istersek Swagger’ı OpenAPI yerine kullanabilir miyiz ? Tabii ki :) Scalar kütüphanesini kullanarak, aşağıdaki adımları sırası ile gerçekleştirerek .Net 9 projemize ekleyebilirsiniz:
- dotnet add package Scalar.AspNetCore –version 1.2.*
- using Scalar.AspNetCore;
-
1234567builder.Services.AddOpenApi();if (app.Environment.IsDevelopment()){app.MapOpenApi();app.MapScalarApiReference();}
5-)Task.WhenEach :
Farklı zamanlarda asenkron çalışan işlerin, bir listesine aldığımızı düşünelim. Her birinin tamamlanması anında, gecikmeden raporlanmasını isteyelim. .Net 9 öncesi “Task.WaitAll()” methodu ile, tüm görevlerin bitmesini beklediği için bu sorunumuza çözüm olamazdır. Tamamlandığında bir sonrakini raporlmak için “Task.WaitAny()” yöntemini, geçici bir çözüm olarak kullanabiliriz. Ama bu da sıralı bir akışa, yani senkronizasyona sebebiyet verir. C# 13 bu senaryoyu çözüm olarak çok daha şık ve etkili bir yaklaşım sunar: İşte karşınızda yeni Task methodu “Task.WhenEach()“. Aşağıdaki örneğe hep beraber göz atalım..
1-) Bu örnegimizde dört farklı URL’den Request çekeceğiz. Aşağıda Request sonrası, dönülecek herbir requestin result sınıfı tanımlanmıştır.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class ApiResult { public string Url { get; } public string Status { get; } public string? Data { get; } public ApiResult(string url, string status, string? data) { Url = url; Status = status; Data = data; } } |
2-) Asenkron olarak her bir URL için yapılacak Fetch işlemine ait method aşağıdaki gibidir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public static async Task FetchDataAsync(string url, HttpClient httpClient) { Console.WriteLine($"Starting fetch for {url}"); try { var response = await httpClient.GetAsync(url); var content = await response.Content.ReadAsStringAsync(); //Buraya daha karmaşık veri işleme ekleyebilirsiniz //Örneğin, JSON'un seri durumdan çıkarılması veya verilerin dönüştürülmesi return new ApiResult(url, response.StatusCode.ToString(), content); } catch (Exception ex) { Console.WriteLine($"Error fetching {url}: {ex.Message}"); return new ApiResult(url, "Error", null); } } |
3-) Aşağıdaki örnekte dört farklı URL’e request çekilmiş ve “Task.WhenEach()” methodu ile herbir task yürütülüp, asenkron olarak tamamlanan taskların sonucu ekrana basılmıştır. Bence, özellikle yüksek trafikli işlemlerde bu yeni method çokça işimize yarıyacaktır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
var urls = new List { "https://borakasmer.medium.com/a-deep-dive-into-primary-constructors-29329821c326", "https://borakasmer.medium.com/keep-ai-email-templates-with-memento-design-pattern-34023e0a929e", "https://borakasmer.medium.com/how-to-use-string-keyword-as-an-entity-in-a-linq-query-184d5e52f4ee", "https://borakasmer.medium.com/capturing-the-website-and-sending-it-as-a-pdf-via-email-at-certain-time-intervals-5a8254109eae" }; // Initialize HttpClient using var httpClient = new HttpClient(); // Her URL'den veri almak ve işlemek için görevler oluşturulur var tasks = urls.Select(async url => await FetchDataAsync(url, httpClient)).ToList(); // Her görevi sırayla yürütülür ve her biri tamamlandıktan sonra sonuç işlenir await foreach (var completedTask in Task.WhenEach(tasks)) { Console.WriteLine($"Processed result for URL {completedTask.Result.Url}: Status {completedTask.Result.Status}, Length {completedTask.Result.Data?.Length}"); if (completedTask.Result.Data != null) { Console.WriteLine("First 100 characters of data:"); Console.WriteLine(completedTask.Result.Data.Substring(0, Math.Min(completedTask.Result.Data.Length, 100))); } Console.WriteLine("------"); }; Console.WriteLine("All tasks completed."); |
Sonuç Ekranı:
6-) Yeni LINQ Methodları
CountBy:
Aşağıdaki gibi aynı markaya ait farklı motor markalarının olduğu bir liste olsun.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
(string marka, string model)[] motors = { ("Yamaha", "MT-07"), ("Ducati", "Scrambler 1100"), ("Harley-Davidson", "Iron 883"), ("Honda", "CBR600RR"), ("Triumph", "Tiger 900"), ("Yamaha", "R1"), ("Kawasaki", "Versys 1000"), ("BMW", "S1000RR"), ("Harley-Davidson", "Sportster S"), ("Ducati", "Panigale V4"), ("KTM", "Duke 390"), ("Kawasaki", "Ninja 400"), ("Triumph", "Street Triple"), ("Ducati", "Monster 821"), ("Royal Enfield", "Interceptor 650") }; |
Şimdi gelin öncelikle eski usul yöntemler ile bunları gruplayıp, toplam sayısına göre sıralayıp, ekrana basalım:
1 2 3 4 5 |
var sameBrandbikes = motors.GroupBy(x => x.marka) .Select(x => new { x.Key, Count = x.Count() }) .OrderByDescending(x => x.Count).ThenBy(x => x.Key) .ToList(); sameBrandbikes.ForEach(x => Console.WriteLine($"{x.Key} markasından {x.Count} adet motosiklet var.")); |
Şimdi gelin aynı işlemi .Net 9 ile yapalım: Aşağıda görüldüğü gibi GroupBy yapmadan “CountBy” ile tüm iş çözülmüştür.
1 2 |
var sameBrandNet9bikes = motors.CountBy(x => x.marka).OrderByDescending(x=>x.Value).ThenBy(y=>y.Key).ToList(); sameBrandNet9bikes.ForEach(x => Console.WriteLine($"{x.Key} markasından {x.Value} adet motosiklet var.")); |
AggregateBy:
Aşağıdaki örnege baktığımızda aynı kategorideki ürünlerin fiyat toplamı standart yöntemler ile hesaplanmıştır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
(string urunAdi, string turu, decimal fiyat)[] urunler = [ ("Dana Kıyma", "Et", 250.75m), ("Domates", "Sebze", 15.50m), ("Pirinç", "Bakliyat", 32.90m), ("Süt", "Süt Ürünleri", 20.00m), ("Elma", "Meyve", 10.25m), ("Bulaşık Deterjanı", "Temizlik", 45.75m), ("Yoğurt", "Süt Ürünleri", 18.00m), ("Biber", "Sebze", 12.30m), ("Tavuk Göğsü", "Et", 75.40m), ("Muz", "Meyve", 22.50m) ]; urunler.GroupBy(x => x.turu) .Select(x => new { x.Key, Total = x.Sum(y => y.fiyat) }) .ToList() .ForEach(x => Console.WriteLine($"{x.Key} kategorisindeki ürünlerin toplam fiyatı: {x.Total} TL")); |
Ayni sorunu .NET 9 ile gelen, aşağıdaki gibi AggregateBy() ile çok daha pratik bir şekilde çözelim:
1 2 |
var grup = urunler.AggregateBy(x => x.turu, decimal.Zero, (toplam, urun) => toplam + urun.fiyat).ToList(); grup.ForEach(x => Console.WriteLine($"{x.Key} kategorisindeki ürünlerin toplam fiyatı: {x.Value} TL")); |
Index
Aslında “Index” çok büyük bir özellik değil. Aşağıda görüldüğü gibi .Net sürümünün çoğunda, foreach yoluyla herhangi bir koleksiyonun indeksini kolaylıkla alabilirsiniz.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var marketList = new[] { "Ekmek", "Süt", "Yumurta", "Makarna", "Peynir", "Domates", "Patates", "Soğan" }; foreach (var (index, urun) in marketList.Select((m, i) => (i, m))) Console.WriteLine($"Ürün {index}: {urun}"); |
Şimdi gelin aynı Index listeyi bir de .Net 9.0 ile alalım:
1 2 |
foreach (var (index, urun) in marketList.Index()) Console.WriteLine($"Ürün {index}: {urun}"); |
Geldik bir makalenin daha sonun. Bu makalede, çok yakında .Net 9 ile hayatımıza girecek özelliklere bir göz attık. Makaleyi daha uzatmamak adına bahsetmediğim bir çok yeni özellik var. Onları da, sizden gelecek geri dönüşlere göre masaya yatırır ve tartışırız. Yeni bir makalede görüşmek üzere hepinize hoşçakalın.
Kaynaklar:
- https://github.com/scalar/scalar/blob/main/packages/scalar.aspnetcore/README.md#scalar-net-api-reference-integration
- https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-9/overview
- https://github.com/dotnet/core/discussions/9448
çok güzel olmuş abi , elinize sağlık
Cok teşekkür ederim Nurullah…
Elinize yüreğinize sağlık, bilgi için teşekkür ederim. CountBy ve Foreach’teki index olayına bayıldım. Bu çok güzel olmuş. Selametle..
Tesekkurler Kerim. Evet özellik GroupBy favorim:)