.NET 8 İLE GELEN YENİLİKLER
Selamlar,
Bugünkü makalede, .Net 8.0 ile daha çok güncel hayatta kullanacağınızı düşündüğüm başlıca özelliklerden bahsetmek istiyorum.
Öncelikle Kurulum:
Öncelikle buradaki link’den, an itibari ile “SDK 8.0.1-rc.2” dosyasını seçtiğiniz işletim sistemine göre indirebilirsiniz. Sadece “Visual Studio 2022 (v17.8 Preview)” versiyonunda çalışmaktadır. Örneğin ben Windows 64Bit için indirdim.
Benim Setup’im aşağıdaki gibidir.
1-) GetItems():
Bir dizi içinden, belirli sayıda elemanın rastgele seçilmesini sağlayan “Random.Shared.GetItems()” methodudur. Aşağıdaki örnekde, 5 kişilik bir dizide 3 kişi rastgele seçilmiştir. Benim bu kütüphanede gördüğüm eksiklik, ayni kayit 2 defa seçilebilmektedir. Bunun için, ayrıca önlemlerin alınması gerekmektedir.
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 |
using System; using System.Linq; using GoogleMapPoint = (string, double); namespace Net8._0_Features { public class Program { public static void Main(string[] args) { List<Person> persons = new List<Person>(); persons.AddRange(new Person[] { new Person("John", "Doe", 20,("Newyork",123)), new Person("Jane", "Saw", 30,("LA",456)), new Person("Tarzan", "Jungle", 40,("Seattle",666)), new Person("Bora", "Kasmer", 45,("IST",412)), new Person("Martin", "Fowler", 50,("MC",734)) }); var randomPersons = Random.Shared.GetItems(persons.ToArray(), 3); randomPersons.ToList().ForEach(p => Console.WriteLine($"{p.Name}-{p.Surname}-{p.Age}-{p.Point}")); } } public class Person(string name, string surname, int age, GoogleMapPoint point) { public string Name => name; public string Surname => surname; public int Age => age; public GoogleMapPoint Point => point; } } |
Yukarıda dörüldüğü gibi, aynı kayıdın gelmemesi için Random zorlama çözüm aşağıdaki gibidir. Kesinlikle doğru bir çözüm değildir. Amaç, aynı olmayan yani belirtilen sayıda farklı Random eleman seçmektir. Bir de size feyz vermesidir. “Yoksa bu işlemin maliyeti, astarı yüzünden pahalıya gelmektedir.”
- “Random.Shared.GetItems(persons.ToArray(), count).Distinct()” : Distinct() extension methodu ile Random farklı elemanlar ilgili dizi içinden alınır.
- “randomPersons.Count() < count” : İstenen sayıda Random eleman alınamamış ise, eksik eleman Random olarak tekrardan alınır.
- “? Random.Shared.GetItems(persons.Except(randomPersons).ToArray(), count- randomPersons.Count()) : randomPersons“: Random seçilen elemenların haricinde kalan elemanlar içinden eksik sayıda Random eleman alınır. “”Burda da tekrardan ayni elemanlarin Random olarak gelme ihtimali bulunmaktadır. Burdaki kod da, bir sonraki adımda DEĞİŞECEKTİR :)
- “if(randomPersons.Count() < count) returnList.AddRange(personFinalList)“: Eğer random olarak seçilen sayı, istenen sayıdan az ise sonradan Random seçilen(personFinalList), ilgili listeye eklenir.
1 2 3 4 5 6 7 8 |
public static List<Person> GetRandomItemFormList(List<Person> persons, int count) { var randomPersons = Random.Shared.GetItems(persons.ToArray(), count).Distinct(); var personFinalList = randomPersons.Count() < count ? Random.Shared.GetItems(persons.Except(randomPersons).ToArray(), count- randomPersons.Count()) : randomPersons; var returnList = randomPersons.ToList(); if(randomPersons.Count() < count) returnList.AddRange(personFinalList); return returnList; } |
Final Çözüm: Burada eğer yeterli sayıda eleman bulunmaz ise bulunan elmanların haricindeki listeden, eksik elemanlar, linq query sorgusu ile çekilmektedir. Ama eksik kalan elmanlar maalesef Random olarak alınmamış olunur.
“var personFinalList2 = randomPersons.Count() < count ? persons.Except(randomPersons).Take(count – randomPersons.Count()) : randomPersons“:
1 2 3 4 5 6 7 8 |
public static List<Person> GetRandomItemFormList(List<Person> persons, int count) { var randomPersons = Random.Shared.GetItems(persons.ToArray(), count).Distinct(); var personFinalList2 = randomPersons.Count() < count ? persons.Except(randomPersons).Take(count - randomPersons.Count()) : randomPersons; var returnList = randomPersons.ToList(); if (randomPersons.Count() < count) returnList.AddRange(personFinalList2); return returnList; } |
program.cs:
1 2 3 4 5 |
Console.WriteLine("".PadRight(60, '*')); Console.WriteLine("Random Persons:"); var randomPersons = GetRandomItemFormList(persons, 3); randomPersons.ForEach(p => Console.WriteLine($"{p.Name}-{p.Surname}-{p.Age}-{p.Point}")); Console.WriteLine("".PadRight(60, '*')); |
İleride bunla ilgili mutlaka Ticket açılacak ve Unique Random seçimi yapılabilecektir. Benim burada alternatifleri göstermemdeki esas amaç, sizlere farklı bakış açıları katmaktır.
* 2-) Alias Tip:
Dikkat ederseniz yukarıda “using GoogleMapPoint = (string, double)” tanımlaması yapılmıştır. Bu C# 12 ile gelecek olan “Alias Tip” tanımlamasıdır. Person sınıfının Point propertysi, bizim custom GoogleMapPoint’dir. Daha detaylı makalesine buradan erişebilirsiniz.
*3-) Primary Constructors:
Dikkat edilecek ikinci konu, “public class Person(string name, string surname, int age, GoogleMapPoint point)” Person sinifina constructor ve local degişken tanımlamadan, ilgili propertylerin set edilmesidir. Bu işlem için C# 12 ile gelecek olan “Primary Constructor” kullanılmıştır. Daha detyalı makalesine buradan erişebilirsiniz.
Person sınıfı için yeni property tanımlama şekli aşağıdaki gibidir.
1 |
public string Name => name; |
4-) Shuffle():
Bu ikinci örneğimizde, .Net 8.0 ile gelecek olan, bir dizinin sırasını rastgele hale getirme konusudur. Shuffle(), özellikle makina öğrenimi, şans oyunları ve sınav merkezleri gibi domainler için çok kullanışlı yapılardır. Aşağıdaki örnekde bir dizi eleman, öncelikle karıştırılıp yani rastgele hale getirilip sonra da listelenmiştir.
1 2 3 4 5 |
Console.WriteLine("Shuffle Numbers:"); int[] numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; Random.Shared.Shuffle(numbers); int index = 1; numbers.ToList().ForEach(n => Console.WriteLine($"{index++}. number => {n}")); |
*5-) Keyed DI Service:
İşte geldik benim en sevdiğim .Net 8.0 yeniliğine. Bildiğiniz Dependency Injection artık değişiyor. Temel olarak, aynı servisleri farklı adlar altında birden çok kez kaydetme ve belirli bir tür/ad birleşimini enjekte etme olanağını bizlere sunuyor. Bunu mümkün kılmak için, eski dostumuz “IServiceProvider“‘ı iki yeni yöntemle genişleten yeni bir arayüz olan “IKeyedServiceProvider” hayatımıza giriyor. GetKeyedService(serviceType yazın, nesne? serviceKey); nesne GetRequiredKeyedService(serviceType yazın, nesne? serviceKey). Aslında bunlar, işleri oldukça baside indirgeyen methodlar. Bu yapıları birazdan, basit bir örnek ile detaylıca inceleyeceğiz.
1 2 |
object? GetKeyedService(Type serviceType, object? serviceKey); object GetRequiredKeyedService(Type serviceType, object? serviceKey); |
Ayrıca nesneleri kaydetmek için, örneğin singleton ömrü boyunca IServiceCollection’da anahtarları alan aşırı yüklenmiş methodlarımız var.
1 2 |
builder.Services.AddKeyedSingleton<ISqlService, SqlService>("sql"); builder.Services.AddKeyedSingleton<IOracleService, OracleService>("oracle"); |
Şimdi yeni bir WebApi projesi yaratalim. Bir tane User servisi yazalım. Sonra da, ilgili servisi Keyed DI servisi kullanarak enjekte edelim.
Model/User:
1 2 3 4 5 6 7 8 9 10 11 |
namespace Net8DependencyInjection.Models { //Create User Model public class User { public int Id { get; set; } public string Name { get; set; } public string Username { get; set; } public string Email { get; set; } } } |
Service/IUserService: UserService’de, kullanıcı listesi veya ID’ye göre tek bir User alınabilecek methodlarımızı tanımlayalım.
1 2 3 4 5 6 7 8 9 10 |
using Net8DependencyInjection.Models; namespace Net8DependencyInjection.Services { public interface IUserService { public List<User> GetUsers(); public User GetUserByID(int Id); } } |
Service/UserService: İlgili UserService, aşağıdaki gibi yazılmıştır. Random yazılmış user listesi ve Id’ye göre spesifik dönülen user EndPointleri yazılmıştı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 28 29 |
using Microsoft.Extensions.Diagnostics.HealthChecks; using Net8DependencyInjection.Models; namespace Net8DependencyInjection.Services { public class UserService : IUserService { static List<User> UserList; public UserService() { if(UserList == null) { UserList = new List<User>(); UserList.Add(new User() { Id = 1, Name = "Bora",Email="bora@borakasmer.com" }); UserList.Add(new User() { Id = 2, Name = "Emre", Email = "emre78@gmail.com" }); UserList.Add(new User() { Id = 3, Name = "Mehmet", Email = "mehmet@yahoo.com" }); UserList.Add(new User() { Id = 4, Name = "Ahmet", Email = "ahmet85@gmail.com" }); } } public User GetUserByID(int Id) { return UserList.Where(x => x.Id == Id).FirstOrDefault(); } public List<User> GetUsers() { return UserList; } } } |
program.cs: Bu kısım önemli. UserService, “AddKeyed” keyword’u ile yaşam döngüsü “Transient” olarak eklenmiştir. Ayrıca tanımlama amaçlı string keyword olarak, “user” kullanılmıştır. İlgili servis istendiğinde başka bir string keyword ile tekrardan eklenebilmektedir.
1 2 3 4 5 |
. . builder.Services.AddKeyedTransient<IUserService, UserService>("user"); . . |
Controller/UserController: Aşağıdaki örnekde “FromKeyedServices” keyword’ü ile Userservice için atanan “user” string tanımlaması ile ilgili servis method içinde parametre olarak alınmaktadır. Klasik bir sınıfın Constructor’ında parametre olarak alıma işi, artık bitti :)
Önemli Not: “FromKeyedService”‘den string bir keyword ile çağrılan servis, bir config dosyasından okunarak çağrılabilir. Bu şu demektir, istendiğinde configden ilgili string değiştirilerek çağrılan servis Runtimeda kolaylıkla degiştirilebilir. Yani örneğin “rabbitMQ” keyword’ü ile çağrılan RabbitMQ Servisi, configden ilgili string keyword “kafka” olarak değiştirilerek runtime’da Kafka servisi çağrılabilir. Böylece kuyruklama mekanizması, yeni FromKeyedServices ile basit bir string ifadenin güncellemesi ile değiştirilebilecektir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
namespace Net8DependencyInjection { [ApiController] [Route("[controller]")] public class UserController : ControllerBase { public UserController() { } [HttpGet("GetUserList")] public List<User> GetUserList([FromKeyedServices("user")] IUserService user) { return user.GetUsers(); } [HttpGet("GetUserByID")] public User GetUserByID([FromKeyedServices("user")] IUserService user,int Id) { return user.GetUserByID(Id); } } } |
6-) System.Text.Json’da Perfomans İyileştirmesi:
Artık System.Text.Json ile gelen Serialization methodlarının, Newtonsoft’a göre çok daha performanslı ve daha okunabilir olduğunu söylemek, artık hiç de yanlış olmaz :)
Aşağıdaki örnekde özellikle Interface hiyerarşisinde artık serileştirmenin yapılabildiği gösterilmek istenmiştir. IGeo => IBaseMath interface’inden, Squre ve Circile sınıfları da => IGeo interfaceinden türemiştir. Her iki geometri objesinin, Alanı ve Çevresi hesaplanmaktadı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 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 |
public interface IBaseMath { public double? Pi { get; set; } public string Type { get; set; } } public interface IGeo : IBaseMath { public int X { get; set; } public int Y { get; set; } public int? H { get; set; } public double? R { get; set; } public double? FindArea(); public double? FindPerimeter(); } public class Square : IGeo { int x, y; int? h; double? r; string type; public double? Pi { get; set; } public int X { get => x; set => x = value; } public int Y { get => y; set => y = value; } public int? H { get => h; set => h = value; } public double? R { get => r; set => r = value; } public string Type { get => type; set => type = value; } public double? FindArea() { return Math.Pow(X, 2); } public double? FindPerimeter() { return 4 * X; } } public class Circle : IGeo { double? pi; public double? Pi { get { return pi; } set { pi = value; } } int x, y; int? h; double? r; string type; public int X { get => x; set => x = value; } public int Y { get => y; set => y = value; } public int? H { get => h; set => h = value; } public double? R { get => r; set => r = value; } public string Type { get => type; set => type = value; } public double? FindArea(int x, int? h, double? r) { return pi * Math.Pow((double)r, 2); } public double? FindArea() { throw new NotImplementedException(); } public double? FindPerimeter() { return 2 * Pi * R; } } |
program.cs: Aşağıda görüldüğü gibi oluşturulan Circile ve Square şekilleri, onca hiyerarşik kalıtıma rağmen doğru bir şekilde Serialize edilebilmişdir.
1 2 3 4 5 6 7 |
IGeo circile = new Circle { Pi = 3.141592,R=2 }; var jsonCircile= JsonSerializer.Serialize(circile); Console.WriteLine("Circile Json: " + jsonCircile); IGeo square = new Square { X=5 }; var jsonSquare= JsonSerializer.Serialize(square); Console.WriteLine("Square Json: "+jsonSquare); |
program.cs(2): JsonSerializeOptions ile isternir ise çıktısı alınacak JsonString, “Snake Case(altı çizgili)” ya da “kebab-case” ile tireli gibi farklı formatlarda yazılabilir. Ayrica The JsonSerializerOptions.MakeReadOnly()
methodu ile, ilgili JsonOption’ın değiştirilemez readonly olması sağlanabilmektedir. Ayrıca “IsReadOnly” propertysi ile, ilgili JsonOption’ın read-only olup olmadığı kontrol edilebilir. Sonradan ilgili option değiştirlmeye çalışılır ise, hata alınacaktır.
1 2 3 4 |
IGeo square = new Square { X=5 }; var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.KebabCaseUpper }; var jsonSquare= JsonSerializer.Serialize(square,options); options.MakeReadOnly(); |
7-) Default Lambda Expression:
Aşağıda görüldüğü gibi C# 12 ile lambda expression default değer atamaya izin vermektedir. Son tanımlamada da, geri dönülecek değer hesaplanmaktadır. İlgili Lambda Expression, bir methodun içinde child bir method gibi kullanılmaya imkan sunmaktadır.
1 2 3 4 5 6 |
public static void Main(string[] args) { var defaultVergiOrani = (int vergi = 18) => vergi + 2; Console.WriteLine("Default Vergi Orani: " + defaultVergiOrani()); Console.WriteLine("Default Vergi Orani: " + defaultVergiOrani(28)); } |
8-) Breaking Change: C#12’de"ref"
parametresi"in"
parametresi yerine geçebilir.
Gelelim sosyal medyada tek bir tweet ile dikkat çekmeye çalıştığım ama yeterince anlatamadığım şu Breaking Change’e :)
- Car sınıfının bir Name() methodu bulunmaktadır. Parametre olarak da (in int i) verilmiştir. Buradaki “in” keyword’ü, değişkenin immutable yani değiştirilemez olmasını sağlamakda ve değişkeni referans değeri ile read-only şekilde ilgili method içine taşımaktadır.
- CarExtension olarak Car sınıfına Name() methodu, bu sefer (ref int i) parametresi ile tanımlanmıştır.
Şimdi biz bu Car sınıfının Name() methodunu çağırdığımızda, C#’ın versiyonlarına göre farklı Methodlar çağrılacaktır.
Nedeni C#11’de “ref” keyword’ü, “in” keyword’ü yerine kullanılamaz yani iki keyword tamamen birbirinden farklı algılanır ve yorumlanırlar. Bu nedenle C# 11’de “ref”, keywordü ile çağrılan “Name()” methodu “Name() Extension”‘ının çağırmakta ve console’a “Park” yazmaktadır. Ama C# 12’de “ref” keyword’ü, “in” keyword’ü yerine geçebilmektedir. Yani C# 12’de “ref”, keywordü ile çağrılan “Name()” methodu, Car sınıfına ait içinde “in” keywordü ile tanımlı parametreli Name() methodunu çağıra bilmekte, ve Console’a “Car” yazmaktadır.
1 2 3 4 5 6 7 8 |
class Car { public string Name(in int i) => "Car"; } static class CarExtension { public static string Name(this Car c, ref int i) => "Park"; } |
program.cs: İşte tam olarak C# 12 ve sonrası versiyonlarının, “ref” keywordünün “in” keywordü yerine kullanılabilmesi, C#’ın önceki versiyonları için Breaking Changes oluşturacaktır.
1 2 3 4 |
var i = 5; System.Console.WriteLine(new Car().Name(ref i)); // C#11'de "Park" yazar ama C# 12'de "Car" yazar. System.Console.WriteLine(CarExtension.Name(new Car(), ref i)); System.Console.WriteLine("".PadRight(80, '*')); |
C# 12 çıktısı:
C# 11 çıktısı:
Geldik bir makalenin daha sonuna. Bu makalede, 14 Kasımda çıkacak olan .Net 8.0 ile kodlarımızda canlı canlı kullanabileceğimiz olan ve beni en çok heycanlandıran yeni özelliklerden bahsetmek istedim. Bunların yanında birçok performans geliştirmesinin de geldiğini, belirtmeden geçmeyeyim. .Net Derleme süresi, Entity Frameworks sorgu çekme süreleri, kullanılan resourcelarda olan gözle görülür düşme ve daha birçoğu. En önemlisi de .Net 7.0’ın supportunun 6 ay sonra bitecek olması. .Net 6 Long Term Support olmasına rağmen .Net 7’nin Suppoprtunun bu kadar kısa olması, pek de alışıla gelmiş bir durum değil. Güzel haber ise, .Net 8 Long Term Support olacak ve 36 ay yani 2026’ya kadar Microsoft tarafında desteği verilecek.
Kodlarınızda .Net versiyonunu ne kadar güncel tutarsanız, en son versiyona geçişiniz de o denli kolay olacaktır. Siz ne kadar eski .Net versiyonunda kalırsanız ve ne kadar 3th Party Tool kullanırsanız, karşılaşacağınız Breaking Changeler o denli olacaktır. Unutmayin, en güncel .Net versiyonunda kodlamanız demek, en performanslı, yeni özellikler sayesinde kolay ve okunabilir bir kod geliştirebildiğiniz ve son olarak support konusunda sorun yaşamıyacağınız bir versiyonda çalışmanız demektir. Yeni bir makalede görüşmek üzere hepinize hoşçakalın.
Github: borakasmer/Net8.0-Features (github.com)
Source:
- https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8
- https://www.bytehide.com/blog/dotnet-8-preview-1
- https://modlogix.com/blog/net-8-features-and-release-date-what-to-expect/
Teşekkürler.
Ben Teşekkür ederim.
Merhaba Bora hocam,
Yazı için teşekkürler. Daha iyi bir okuma için;
Yazı fontunu ve boyutunu daha okunaklı yapsanız, göz yormadan okusak.
* Opera kullanıyorum ve okuma modunda sıkıntı yok ama resimler ve renkli yazılar belli olmuyor maalesef =(
İyi çalışmalar.
Teşekkürler Ahmet,
Geri bildirimini kesinlikle dikkate alacağım..
Emeğine sağlık hocam. Birde şu mobil kısmıyla ilgili bizi bir aydınlatsan MAUI geleceği ne olur xamarin gibi atılır mı? gibi gibi
Hocam MAUI sağlam kaynak arıyorum, önerin var mıdır?
Selamlar Bora hocam;
Öncelikle bizlerle paylaştığınız tüm bilgileriniz ve emekleriniz için sizlere sonsuuz teşekkür ederim. Lakin kendi adıma diyorum ki inşaAllah bi gün sizi anlayacağım. İki kere okuyorum üç kere okuyorum sonra bakıyorum ki 0 :D. Her şey için çoook teşekkürler hocam.
Not: bu yazılım olaylarında 4. senem sadece kendimce bi yorum yapmak istedim.
Tesekkurler. Konuyu tam anlamiyorsan ama bir başkası anlıyor ise, belki bazı temel konuların uzerinden tekrar gecmende bir fayda olabilir.
Teşekkür ederiz emekleriniz için, yine çok faydalı içerik.
Tesekkurler Emre..
Bora hocam elinize sağlık. Şunu sormadan edemiyorum, bu yeni güncellemeler gerçekten ilerleme mi? Bir önceki sürümlerden desteği çekmek için bir bahane mi? Evet güzel gelişmeler var ancak insan sürümlerin hızına yetişemiyor.
Hocam iyi çalışmalar. Mauı için de bir içeriğiniz olacak mı malum piyasada pek yok
Selamlar Yusuf,
Maui uzerine cok bir icerik üretmeyi planlamıyorum.Kendim de pek fazla kullanmıyorum.Ama guncel hayatimda kullanirsam, mutlaka makalesini yazarim..
Iyi calışmalar.
Selam Bora hocam,
Yeni yılınızı kutlarım. İnşallah yeni yılda .net8 ile gelen daha detaylı yeniliklere değindiğiniz makaleleri okuyabiliriz. Özellikle microservis açısından :)
Bora hocam KEYED DI da verdiginiz ornekte bir sey kafama takildi ama keyed la ilgisiz. GetByUserId methodunda once where ile sorgulayip sonra firstordefault yapmissiniz. Expression’i direkt olarak firstordefault a vermek verimsiz bir hareket mi oluyor. Yoksa bilmedigim bambaska bir soruna sebep oluyor mu?