.NET 8 İLE GELEN YENİLİKLER VOL 2.0
Selamlar,
Bu makale, .Net 8.0 ile gelen yenilikler makalesinin devamıdır.
Streaming Deserialization: Otomatik Tanımlanan Sınıfa Deserialization
Amaç bir servisten kayıt çekilirken, çekilen datanın ayrıca bir deserialize işlemine tabi tutulmadan otomatik olarak ilgili modele maplenmesidir.
Örnek amaçlı kayıt çekilecek 3th party servis: https://microsoftedge.github.io/Demos/json-dummy-data/64KB.json
İlgili C# modeli Json model, burdan oluşturulur: https://json2csharp.com
CodPlayer servisden dönen modeldir. ShortCodPlayer bizim esas ihtiyacımız olan ve geri döndüğümüz modeldir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class CodPlayers { public string name { get; set; } public string language { get; set; } public string id { get; set; } public string bio { get; set; } public double version { get; set; } } public class ShortCodPlayer { public string name { get; set; } public double version { get; set; } } |
GetAllCodPlayers(): Tüm kaydın çekildiği ve otomatik olarak CodPlayers’a maplendiği methoddur.
Aşağıda görüldüğü gibi “GetFromJsonAsAsyncEnumerable<CodPlayers>()” ile otomatik “CodPlayers”‘a gelen Json data, CodPlayers’a maplenmektedir. Geriye
“IAsyncEnumerable<CodePlayers?>” tipinde değer dönmektedir.
Aşağıda görüldüğü gibi “players” üzerinden stream olarak kayıt gelmeye devam etmektedir. “await” keyword’ünü artık foreach başında .NET 8.0 ile kullanabilmekte ve anlık akan datayı geri dönülecek esas “shortPlayList“‘e ataya bilmekteyiz. Çünkü ihtiyacımız olan sadece servisten dönen 2 kolondur. “name“ve “version“. Yani aslında “players“‘a tüm data çekilerek döngüye girilmemiş, sürekli asenkron gelen data players’a dolmakta, biz de her gelen datayı shortPlayerListe eklemekteyiz.
Son olarak gelen liste, “versiyon”‘a göre aşağıda görüldüğü gibi sıralanmaktadır.
GetAllCodPlayers():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public async Task GetAllCodPlayers() { using (HttpClient client = new()) { var shortPlayerList = new List(); var players = client.GetFromJsonAsAsyncEnumerable<CodPlayers>("https://microsoftedge.github.io/Demos/json-dummy-data/64KB.json"); await foreach (var player in players) { shortPlayerList.Add(new ShortCodPlayer() { name = player.name, version = player.version }); } shortPlayerList.OrderBy(x => x.version).ToList().ForEach(x => { Debug.WriteLine($"Name: {x.name} Version:{x.version}"); }); } } |
Result: Sonuçların bir kısmı aşağıda görüldüğü gibidir.
AsyncEnumerableExtensions:
- Peki, çekilen IAsyncEnumerable data üzerinde Linq sorgusu yazmak istersek öncelikle aşağıdaki Extension sınıfını yazmamız gerekmektedir. Çünkü IAsyncEnumerable LINQ Query desteklememektedir. İhtiyaçlara göre “SelectAsync()“, “WhereAsync()“, “OrderByAsync()” ve “ToListAsync()” extension methodları yazılmıştır.
- Bu methodlarda “yield” keyword’ü ile asenkron şekilde gelen data, streaming olarak anlık geri dönülmektedir.
- Özellikle büyük datalarda hepsinin toplu çekilip geri dönülmesi, hem performans hem de zaman kaybına neden olmaktadır. Ayrıca bir Linq Library’de giren datanın tipinin değişmemesi gerekmektedir. Örneğin “SelectAsync()“‘den sonra data tipi değişir ise, ondan sonra eklenen “ToListAsync()” extension’i çalışmayacaktır.
- Bu anlık streaming yapısını “OrderByAsync()” extension’i, aslında bozmktadır. Ama her zaman ihtiyaç duyulabilecek bir method olduğu için, kütüphaneye eklenmiştir. Sorun: Çekilecek olan datanın, sıralamanın yapılabilmesi için tamamının beklenmesi ve anlık streaming işinin durdurulmasıdır. Ancak tüm data çekildikten sonra, bir sıralama işlemi yapılabilmektedir.
AsyncEnumerableExtensions.cs:
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 |
public static class AsyncEnumerableExtensions { // Example of an asynchronous LINQ extension method public static async IAsyncEnumerable<TResult> SelectAsync<TSource, TResult>( this IAsyncEnumerable<TSource> source, Func<TSource, Task<TResult>> selector) { await foreach (var item in source) { yield return await selector(item); } } // Example of an asynchronous LINQ extension method public static async IAsyncEnumerable<TSource> WhereAsync( this IAsyncEnumerable<TSource> source, Func<TSource, Task<bool>> predicate) { await foreach (var item in source) { if (await predicate(item)) { yield return item; } } } // Example of an asynchronous LINQ extension method for ToList public static async Task<List<T>> ToListAsync(this IAsyncEnumerable<T> source) { var list = new List<T>(); await foreach (var item in source) { list.Add(item); } return list; } // Example of an asynchronous LINQ extension method for OrderBy public static async IAsyncEnumerable<TSource> OrderByAsync<TSource, TKey>( this IAsyncEnumerable<TSource> source, Func<TSource, TKey> keySelector) { var orderedItems = (await source.ToListAsync()).OrderBy(keySelector); foreach (var item in orderedItems) { yield return item; } } } |
Şimdi gelin kodlarımızı, yukarıdaki Extension methodlara göre tekrardan elden geçirelim.
GetAllCodPlayers():
- Aşağıda görüldüğü gibi öncelikle “GetFromJsonAsAsyncEnumerable()” methodu ile 3. bir endpoint’den datalar çekilmiş ve “CodPlayers” modeline otomatik Deserialize edilmiştir.
- :Çekilen CodPlayers datası, “language” kolonu “Sindhi” olanlar ile filitrelenmiştir.
- :Filitrelenen datalar içinden, bizim ihtiyacımız olan sadece “name” ve “version” alanları alınmıştır. Böylece ayrıca bir ShortCodPlayer modeline ihtiyaç duyulmamıştır.
- : Son olarak versiyon propertysine göre sıralama yapılmıştır. Bu sırada maalesef tüm datanın çekilmesi beklenmiştir. Bu nedenle anlık streaming özelliğine son verilmiştir. Sonda tüm data, Task<List<T>>? ‘e dönüştürülmüştür.
- :”await shortPlayerList” ile tüm data List<T>() olarak result’a atanmıştır. Böylece “Get
Enumerator()” call edilebilmiş ve foreach içerisinde sonuçlar ekrana basılmıştır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public async Task GetAllCodPlayers() { using (HttpClient client = new()) { var players = client.GetFromJsonAsAsyncEnumerable<CodPlayers> ("https://microsoftedge.github.io/Demos/json-dummy-data/64KB.json"); var shortPlayerList = players.WhereAsync(async p => p.language == "Sindhi") .SelectAsync(async e => new { e.name, e.version }) .OrderByAsync(n => n.version).ToListAsync(); var result = await shortPlayerList; foreach (var x in result) { Debug.WriteLine($"Name: {x.name} Version:{x.version}"); }; } } |
Result:
FrozenSet, FrozenDictionary :
İlk oluşturma süresi zahmetli olsa da, okuma hızlarında gözle görülebilir bir performans farkı bulunmaktadır. Bu listelere eklenen elemanlar Immutable‘dır.
Aşağıda örnek FrozenList ve FrozenDictionary kullanımı görülmektedir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
private static readonly string[] Summaries = ["Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"]; FrozenSet frozenSummary = Summaries.ToFrozenSet(); FrozenDictionary<string, int> dicktFrozen; dicktFrozen = Summaries.ToList().Select((key, id) => (key, id)).ToFrozenDictionary(e => e.key, e => e.id); |
Benchmark Test: Aşağıdaki benchmark testine bakıldığında, “CreateFrozenList()” diğer List ve HashSet’e göre çok daha uzun bir sürede yaratılmıştır. Ama örnek FrozenDictionary içinde => TryGetValue() ile ilgili kayıdın çekilmesi diğer List ve HasSet’e göre çok daha performanslıdır.
Burdan çıkarılacak sonuç, eğer yaratılma zamanı mesela en başta yaratılması çok dert değil ise, data okunma veya çekilme anında, özellikle büyük data kümelerinde bize büyük bir performans kazandıracaktır.
global Using:
Hepinizin bildiği “alias” namespace’e takma isim kullanımı, .Net Framework’un 1.0 versiyonundan beri vardır. Aşağıdaki örnekte alias tip kullanımına bir örnek verilmiştir. İşte global Using ise bunun Custom Tip tanımlama versiyonudur.
1 2 3 4 |
using Con= System.Console; using Env = System.Enviroment; public void GetMyName(){Con.WriteLine($"Hello Bora Kasmer. Os:{Env.OSVersion}");} |
Aşağıdaki örnekte ShoppingData tipi, Global olarak tüm projede kullanılabilecek şekilde tanımlanmıştır. Böylece ShoppingDate tipi ile, ServiceType ve Basket tipleri ayrı ayrı tanımlanma yerine tek bir tip altında toplanmaktadır.
1 2 3 4 5 |
global using ShoppingData = ( Net8Features.Models.ServiceType Type, System.Collections.Frozen.FrozenDictionary<Net8Features.Models.Product, int> Basket ); |
Örnek Kullanım Şekli: Aşağıda görüldüğü gibi data, “ShoppingData” tipinde yaratılmıştır. Ve bu ShoppingDatat tipi, başka hiçbir yerde tanımlamaya gerek kalmadan, tek bir yerde yazılıp, Global olarak kullanılmıştır.
-
- İlgili product 3th party servisten çekilerek, otomatik olarak ResultModel’e maplenmiştir.
-
- Çekilen productlar, otomatik artan id değerleri ile FozenDictionary’e cast edilip dicktProduct değişkenine atanmıştır..
-
- Global Type olarak tanımlı “ShoppingData” tipindeki data’ya yukarıda tanımlanan “dicktProduct” ve ServiceType Enum’undan “weather” propertyleri ile tanımlanmıştır.
1 2 3 4 5 6 7 8 9 10 11 12 |
//Global Using Example using (HttpClient client = new()) { var products = await client.GetFromJsonAsync<Net8Features.Models.ResultModel>("https://dummyjson.com/products/"); var dicktProduct = products.products.Select((key, id) => (key, id)).ToFrozenDictionary(e => e.key, e => e.id); ShoppingData data = new() { Type = Models.ServiceType.weather, Basket = dicktProduct }; //-------------- } |
Product ve ResultModel Sınıfları ve ServiceType Enum:
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 |
public class Product { public int id { get; set; } [DeniedValues("Bora","Cetvel","Kalem", ErrorMessage ="Gecersiz Urun")] public string title { get; set; } [Length(50, 100)] public string description { get; set; } public int price { get; set; } [Range(1, 20, MinimumIsExclusive = true, MaximumIsExclusive = true)] public double discountPercentage { get; set; } public double rating { get; set; } public int stock { get; set; } public string brand { get; set; } public string category { get; set; } public string thumbnail { get; set; } public List images { get; set; } } public class ResultModel { public List products { get; set; } public int total { get; set; } public int skip { get; set; } [AllowedValues(20,30,50,ErrorMessage ="Boyle Limit Olmaz Olsun :)")] public int limit { get; set; } } public enum ServiceType { weather = 1, weather2 = 2, } |
Switch Expression:
Aşağıda görüldüğü gibi .NET 8.0 ile bir string dizi içerisinde GroupBy Linq Query yazarken, switch tanımlaması yapılabilmektedir.
Aşağıdaki string [ ] içinde tanımlı kelimeler boyutlarına göre guruplanmış, 5’den küçük, 5 ile 8 arası ve diğerleri koşullarına göre “Name” property’si atanmış, “Id” propertysi de sıralı olarak arttırılarak atanmıştır. Select işleminin sonunda da groupResult değişkenine, ilgili result “FrozenSet()”‘e dönüştürülerek atanmıştır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
private static readonly string[] Summaries = ["Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"]; var groupResult = Summaries.GroupBy(word => word.Length switch { <= 5 => word + "(<5)", > 5 and < 8 => word + "(>5)", _ => word + "(>8)", }).Select((g, id) => new { Name = g.Key, Id = id }).ToFrozenSet(); |
Result:
ref readonly:
Amaç, referans ile verilen değişkenlerin sadece kullanılmasını ama asla değiştirilememesini sağlamaktır.
Aşağıdaki örnekte ref readonly olarak tanımlanmış id değerini değiştirilememektedir. Mesela id=5 şeklinde bir atama yapılamaz. Hatta “id.ToString()” şeklinde cast bile yapılamayacağı için, başka bir num değişkenine atanmış ve o değişken ToString() olarak RedisKey’e string bir parametre olarak atanmıştır.
1 2 3 4 5 6 |
public class RedisPersonKeyGenerator(ref readonly int id, string name) { //id = 5; int num = id; public string RedisKey => $"person:{num.ToString()}:{name}"; } |
Int ToString() Performans Geliştirmesi
Son olarak .NET 7.0 => .NET 8.0’a geçişte “Int ToString()” için muazzam bir performans artışı sağlanmıştır. Aşağıda bununla ilgili BenchMark test sonucu görülmektedir. Proje içinde bolca kullanılabilecek bu dönüşüm .Net 7.0’dan => .Net 8.0’a geçildiğinde büyük bir performans artışı olarak geri dönecektir.
Geldik bir makalenin daha sonuna. Bu makalede .Net 8.0 ile gelen diğer yeniliklerden bahsettik. Yeni bir makalede görüşmek üzere hepinize hoşçakalın.
Değerli bilgiler için teşekkürler hocam.
Ben tesekkur ederim efendim…