Yazılımcı Topluluğu Tarafından Çok İstenen .Net 6.0 Apileri
Selamlar,
Bu makalede, .Net 6.0 ile hayatımıza katılan, gözden kaçırılabilecek, kod geliştirmeyi kolaylaştırabilecek developer community tarafından çokça istenen Apilerden bahsetmek istiyorum.
FileStream Kullanmadan Dosya Yazma ve Okuma:
Artık, FileStream kullanmadan dosyaların okunması ve yazılması sağlayan yeni düşük seviye bir API var. Ayrıca, multiple buffer I/0 destekleyip, belirli bir dosya ofsetinde örtüşen okuma ve yazma işlemlerini yapabilmektedir.
Aşağıdaki örnekde “SqlScripts.txt” dosyası byte[ ] dizi olarak okunmuş ve ekrana string olarak basılmıştır.
- SafeFileHandle handle: Okunacak dosyanın atandığı handel.
- RandomAccess.GetLength(): İlgili dosyanın boyutu alınır.
- RandomAccess.Read(handle, buffer,0): Ilgili dosya okunup byte [ ] dizisi olarak buffer değişkenine atanır.
- Encoding.Default.GetString(buffer): Okunan byte [ ] dizisi string’e çevrilip ekrana basılır.
1 2 3 4 5 6 7 8 9 |
using Microsoft.Win32.SafeHandles; using System.Text; SafeFileHandle handle = File.OpenHandle("C:\\Projects\\SqlScripts.txt"); long length = RandomAccess.GetLength(handle); byte[] buffer = new byte[length]; RandomAccess.Read(handle, buffer,0); Console.WriteLine("Total File :" + Encoding.Default.GetString(buffer)); |
Örnek Ekran Görüntüsü:
Process Path Ve Uygulamanın PID’sini Alma:
Artık, Enviroment sınıfı altında, uygulama path’i ve PID nosu aşağıdaki gibi alınabilir. Özellikle PID’ye bu kadar rahat erişim, monitoring işlerinde kolaylık sağlıyacaktır. Çünkü ister Windows, ister Linux ister ise OSX olsun, crash olan veya her türlü monitoring yapılacak uygulama, PID numarasından kolaylıkla erişilebilir.
1 2 |
Console.WriteLine("Application Path :" + Environment.ProcessPath); Console.WriteLine("Application PID :" + Environment.ProcessId); |
Örnek Ekran Görüntüsü:
Parallel.ForEachAsync :
Öncelikle, bu en sevdiğim yeniliklerden biri oldu. Örneğin 3 farklı kullanıcının repolarını github’dan parallel çekelim.
Örnek bir kullanıcının Repo Url’i bu şekildedir => “https://api.github.com/users/borakasmer/repos” . Aşağıda görüldüğü üzere, geri dönülen birçok node’dan sadece biri gösterilmiştir.(Her bir node, Githubdaki 1 repo’ya karşılık gelmektedir.)
Şimdi yukarıdaki alanların bir kısmına karşılık gelecek, Record Modelleri oluşturalım.
Owner / GitHubRepo Records: Aşağıda görüldüğü gibi Client’ın Repo “adı“, “url“‘i ve “id“‘si alınırken, owner node’u altında “login” ile, client’ın adı alınacaktır. Yani “Owner”, GithubRepo’nun child record’udur. Recordlar ile alakalı detaylı yazıya buradan erişebilirsiniz.
1 2 3 4 5 6 7 8 9 10 11 |
public record Owner { public string Login { get; set; } } public record GitHubRepo { public int id { get; set; } public string name { get; set; } public string full_name { get; set; } public Owner owner { get; set; } } |
İhtiyaç duyulan kütüphaneler, using ile başta aşağıdaki gibi tanımlanmıştır. Repoları Listelenecek kullanıcı listesi, Urlleri ile “userHandlers” altında toplanmıştır. Ayrıca HttpClient, Github Base Url ile oluşturulmuştur.(https://api.github.com)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
using System.Net.Http.Headers; using System.Net.Http.Json; using System.Text; var userHandlers = new[] { "users/borakasmer/repos", "users/shanselman/repos", "users/davidfowl/repos" }; using HttpClient client = new() { BaseAddress = new Uri("https://api.github.com"), }; |
- Aşağıda görüldüğü gibi github’da request limite takılmamak için, Header’a “Authorization” eklenip value olarak Github Token verilebilir. Bu durumda saatlik yaklaşık 5000 request izniniz olmaktadır.
- Buradaki önemli kısım “MaxDegreeOfParallelism = 3” yani parallel olarak çalıştırılacak Max’imum process 3 olarak atanmasıdır. Yani yukarıda tanımlı 3 user’ın repo bilgileri, parallel ve asenkron olarak çekilecektir.
1 2 3 4 5 6 7 |
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Your Token"); client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("borsoft", "78")); ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = 3 }; |
Aşağıda görüldüğü gibi “userHandlers” içindeki 3 kullanıcı içinde asenkron “Parallel.ForEachAsync()” çalıştırılmıştır.
- “userHandlers”: Yukarıda tanımlanan request çekilecek Repo Urlleridir.
- “parallelOptions”: Yine yukarıda tanımlanan Maxiumum çalışacak parallel işlem sayısını tanılamaktadır.
- “var repos = await client.GetFromJsonAsync<List<GitHubRepo>>(uri, token)”: Herbir kullanıcının Repo Listesi => List<GitHubRepo> model tipine çekilmiştir.
- “foreach (var repo in repos.Take(2))”: Çekilen tüm Repo Listesi içinden sadece 2 tanesi alınmıştır.
- “Console.WriteLine($”Repo owner: {repo.owner.Login}\nRepo Name: {repo.name}\nRepo Url: {repo.full_name}\n”)”: Kullanıcının Adı, Repo Adı ve Url bilgisi, konsol’a basılmıştır.
1 2 3 4 5 6 7 8 9 |
await Parallel.ForEachAsync(userHandlers, parallelOptions, async (uri, token) => { var repos = await client.GetFromJsonAsync<List<GitHubRepo>>(uri, token); foreach (var repo in repos.Take(2)) { Console.WriteLine($"Repo owner: {repo.owner.Login}\nRepo Name: {repo.name}\nRepo Url: {repo.full_name}\n"); } }); |
Aşağıdaki sonuç ekranına dikkat ederseniz, sonuç listesi karışık gelmektedir. Çünkü Parallel çalışan Taskların, hangisinin önce biteceği belli olmamaktadır.
Uygulamayı her çalıştırmada, karşımıza farklı bir sıralama şekli gelecektir.
Peki “MaxDegreeOfParallelism = 1” olarak atanır ise ne olur ? Her bir client, tek tek çalıştırılır. Ve böylece Parallel bir durum ortada kalmaz. Bu sefer dönen sonuçlar, aşağıdaki gibi olur. Yani sonuç listesi, userHandlers’e eklenen kullanıcı sırasından başka birşey olmaz. Çünkü her bir user, tek tek senkron çalıştırılır.
Tüm Parallel.ForEachAsync Api Kodları:
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 |
using System.Net.Http.Headers; using System.Net.Http.Json; var userHandlers = new[] { "users/borakasmer/repos", "users/shanselman/repos", "users/davidfowl/repos" }; using HttpClient client = new() { BaseAddress = new Uri("https://api.github.com"), }; client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("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"); client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("DotNet", "6")); ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = 3 }; await Parallel.ForEachAsync(userHandlers, parallelOptions, async (uri, token) => { var repos = await client.GetFromJsonAsync<List<GitHubRepo>>(uri, token); foreach (var repo in repos.Take(2)) { Console.WriteLine($"Repo owner: {repo.owner.Login}\nRepo Name: {repo.name}\nRepo Url: {repo.full_name}\n"); } }); public record Owner { public string Login { get; set; } } public record GitHubRepo { public int id { get; set; } public string name { get; set; } public string full_name { get; set; } public Owner owner { get; set; } } |
RandomNumberGenerator (Security.Cryptography):
Artık random byte dizisi üretmek, bu kadar kolay :) Dikkat ederseniz, RandomNumberGenerator ile yeni bir instance üretmeden kolayca yeni bir sayı oluşturabilirsiniz.
1 2 |
byte[] bytes = RandomNumberGenerator.GetBytes(11); Console.WriteLine($"Bytes: { Encoding.Default.GetString(bytes)}"); |
Bu da bellirli iki sayı aralığında random int bir sayı üretme örneğidir.
1 2 3 4 |
for (int i = 0; i < 5; i++) { Console.WriteLine($"Number - {i}: {RandomNumberGenerator.GetInt32(1, 100)}"); } |
LINQ Chunk:
IEnumerable’lara gelen “Chunk” helper’ı, bir diziyi guruplamaya yarar. Bence ilerde paginig için gayet yararlı olabilir
1 2 3 4 5 6 7 8 9 10 |
int counter = 1; int[] array = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; foreach(int[] chunk in array.Chunk(3)) { Console.WriteLine($"Chunk {counter++}"); foreach (var item in chunk) { Console.WriteLine(item); } } |
Aşağıda görüldüğü gibi array’i, 3 elemanlı diziler halinde 4 alt diziye ayırmıştır. Doğal olarak son dizinin eleman sayısı 1’dir.
Aşağıdaki örnekte AdminUser Record Listesi, “Users.Chunk(2)” ile 2 gruba ayrılmıştır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public record AdminUser(string Name, string Email); List<AdminUser> Users = new(); Users.AddRange(new List<AdminUser>{new("BoraKasmer", "bora@borakasmer.com"), new("EnginPloat", "engin@enginpolat.com"), new("BillGate", "billgates@microsoft.com"),new("JoeSatriani", "joe@joesatriani.com")}); int counter = 1; foreach (var chunk in Users.Chunk(2)) { Console.WriteLine($"Chunk {counter++}"); foreach (var user in chunk) { Console.WriteLine($"User: {user.Name} EMail: {user.Email}"); } } |
Linq ile gelen diğer UnionBy, DistinctBy, MaxBy, MinBy gibi yeniliklerden bu makalemde detaylıca bahsettim.
WaitAsync Geliştirmesi:
Aşağıdaki örnekde bize asenkron bir Task’ı beklemenin pratik bir yolu gösterilmştir. “WaitAsync(TimeSpan.FromSeconds(5))” ile eğer 5 sn içinde okuma işlemi bitmez ise, koda devem edecektir.
Not: Timeout süresinden sonra, asenkron çalışan kod cancel olmayacak ve arkada çalışmaya devam edecektir. Sadece, biz artık onun çalışmasını sonlandırmasını beklemeyeceğiz.
1 2 3 4 5 |
SafeFileHandle handle = File.OpenHandle("C:\\Projects\\SqlScripts.txt"); byte[] buffer = new byte[488]; RandomAccess.ReadAsync(handle, buffer, 0).AsTask().WaitAsync(TimeSpan.FromSeconds(5)); Console.WriteLine("Total File :" + Encoding.Default.GetString(buffer)); |
ThrowIfNull:
Aşağıda görüldüğü gibi null kontorlünü artık tek bir satır ile yapabilmektedir! .Net 7.0 ile bunun daha da pratikleşeceğini söyliyebilirim.
1 2 3 4 5 6 7 |
Console.WriteLine("Trim Name: " + GetUserName(null)); string GetUserName(AdminUser user) { ArgumentNullException.ThrowIfNull(user); return user.Name.ToLower().Trim(); } |
Timer Api (PeriodicTimer):
Aşağıda görüldüğü gibi 5 sn çalışıp iptal edilen yeni bir timer örneği gösterilmiştir. PeriodicTimer’ın avantajı herhangi bir callback’e ihtiyaç duymaması ve “while” içerisinde herbir tick sırasında istenen kodun rahatça çalıştırılabilmesidir. While her saniye değil, tanımlanan bekleme süresinde bir döngüye girecektir. Aşağıdaki örnekde bu süre 1sn’dir.
Bir diğer önemli konu da art arda çalışma sırasında:
- “var timer = new PeriodicTimer(TimeSpan.FromSeconds(1))” : Her saniye çalışacak şekilde bir interval ayarlanmıştır.
- “CancellationTokenSource _cts = new()“: 5sn sonra iptal etme amaçlı, “Cancellation Token” oluşturulmuştur.
- “try{} catch{}“: 5 sn içinde Token expire edildiği için, hataya düşülmektedir.
- “int dice = RandomNumberGenerator.GetInt32(1, 6)“: 1’den 6’ya kadar bir zar değeri random olarak alınmaktadır.
- “Console.WriteLine($”Dice: {dice} Time: {DateTime.UtcNow}”)“: Console’a o anki saat ve zar değeri basılmaktadır.
- “if ((DateTime.Now – now).TotalSeconds > 5) { StopAsync(); }” Eğer başlangıçtan beri 5sn geçmiş ise, “StopAsync()”methodu çağrılmıştır.
- “async Task StopAsync() { _cts.Cancel(); _cts.Dispose(); }“: 5sn sonra başta tanımlanan CancellationToken dispose edilerek hataya düşülecek, ve timer’ın durması sağlanacaktır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
var timer = new PeriodicTimer(TimeSpan.FromSeconds(1)); CancellationTokenSource _cts = new(); var now = DateTime.Now; try { while (await timer.WaitForNextTickAsync(_cts.Token)) { int dice = RandomNumberGenerator.GetInt32(1, 6); Console.WriteLine($"Dice: {dice} Time: {DateTime.UtcNow}"); if ((DateTime.Now - now).TotalSeconds > 5) { StopAsync(); } } } catch(ObjectDisposedException) { Console.WriteLine("Time is Cancel After 5 second..."); } async Task StopAsync() { _cts.Cancel(); _cts.Dispose(); } |
Geldik bir makalenin daha sonuna. Bu makalede .Net 6.0 ile gelen, gözümüzden kaça bilecek birkaç güzel yenilik ve kolaylıktan bahsetmek istedim. Yeni bir makalede görüşmek üzere hepinize hoşçakalın.
Source:
Son Yorumlar